Skip to content

Commit

Permalink
Merge pull request #70 from infor-cloud/programmingstandardUpdate
Browse files Browse the repository at this point in the history
Updates to 'Programming Standard'
  • Loading branch information
filiphakansson1 authored May 7, 2024
2 parents 6203e3f + 8853682 commit 0d8c4b4
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 39 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 12 additions & 5 deletions docs/documentation/api-specification/database-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ void readRecord() {
### Read multiple records
To read multiple records a database action should be defined along with a `Closure<?>` that defines how each read record
will be processed/used.
When reading multiple records, the 'readAll' function including 'pageSize' should be used. 'pageSize' is referred to as 'nrOfRecords' in the examples below.

Example:

Expand Down Expand Up @@ -136,7 +137,7 @@ Closure<?> releasedItemProcessor = { DBContainer container ->

### Read with selection
Database API has support for expressions and filters, to use the feature, an instance of `ExpressionFactory` should be
retrieved to build the filter and then used to build the DBAction. The rest is identical to a normal operation.
retrieved to build the filter and then used to build the DBAction. The rest is identical to a normal operation.

Example:

Expand All @@ -154,8 +155,10 @@ public void main() {
.build();
DBContainer container = query.getContainer();
container.set("MMCONO", currentCompany);
container.set("MMSTAT", "20");
query.readAll(container, 2, releasedItemProcessor);
container.set("MMSTAT", "20");
int nrOfKeys = 2;
int nrOfRecords = mi.getMaxRecords() <= 0 || mi.getMaxRecords() >= 10000? 10000: mi.getMaxRecords();
query.readAll(container, nrOfKeys, nrOfRecords, releasedItemProcessor);
}
Closure<?> releasedItemProcessor = { DBContainer container ->
Expand Down Expand Up @@ -191,7 +194,9 @@ public void main() {
mi.write();
}
}
query.readAll(container, 3, readCallback)
int nrOfKeys = 3;
int nrOfRecords = mi.getMaxRecords() <= 0 || mi.getMaxRecords() >= 10000? 10000: mi.getMaxRecords();
query.readAll(container, nrOfKeys, nrOfRecords, readCallback)
}
```

Expand Down Expand Up @@ -235,7 +240,9 @@ void deprecateItems() {
DBContainer container = query.getContainer();
container.set("MMCONO", currentCompany);
container.set("MMSTAT", "20");
query.readAllLock(container, 2, updateCallBack);
int nrOfKeys = 2;
int nrOfRecords = mi.getMaxRecords() <= 0 || mi.getMaxRecords() >= 10000? 10000: mi.getMaxRecords();
query.readAllLock(container, nrOfKeys, nrOfRecords, updateCallBack);
}
Closure<?> updateCallBack = { LockedResult lockedResult ->
Expand Down
2 changes: 1 addition & 1 deletion docs/documentation/api-specification/interactive-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class SampleExtension extends ExtendM3Trigger {
public void main() {
String WHLO = interactive.display.fields.WRWHLO
if(WHLO == '001'){
interactive.display.showOkDialog("You are in warehouse " + WHLO);
interactive.showOkDialog("You are in warehouse " + WHLO);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion docs/documentation/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ General XtendM3 limitations.
### M3 Program calls
It is not possible to call M3 programs from an extension. Only M3 APIs can be called from extensions.

### Database read on user-defined sorting orders
### Database read on user-defined sorting orders
It is not possible to build query on standard tables using user-defined sorting order or index created using CRS021.

## Extending
Expand Down
172 changes: 140 additions & 32 deletions docs/documentation/programming-standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,29 @@ Guideline for writing secure, optimized and scalable extensions.
---

## Extension Types
Extension can be created in any of the four types:
There are five types of extensions, below is a short description of each type.

### Trigger Extensions
Trigger extensions are used to hook or inject our own code in a specific method in M3 Java through an extension point. These extensions extend the ExtendM3Trigger class.
Trigger extensions are used to hook or inject our own code in a specific method in M3 BE via extension points.

### Utility Extensions
Utility extensions are used to create an extension program with collection of methods that can be called in other extensions. These extensions extend the ExtendM3Utility class.
### Transaction Extensions
Transaction extensions are used to create custom M3 API transactions.

`utility.call(String utility, String method, Object... arguments)`
### Batch Extensions
Batch extensions are used for long-running jobs and can be executed/scheduled via *SHS010MI*.

### Transaction Extensions
Transaction Extensions are used to create custom M3 API transactions. These extensions extend the ExtendM3Transaction class.
### Utility Extensions
Utility extensions are used to create an extension program with collection of methods that can be called in other extensions.<br>
`utility.call(String utility, String method, Object... arguments)`

### Table Extensions
Table Extensions are used to create dynamic table. In BE 15.x version, this is the equivalent of the MAK custom tables. It is possible to add columns, assign data types, unique keys, and indices.
Table extensions, or Dynamic Tables, are custom database tables.<br>
It is possible to add columns, assign data types, unique keys, and indexes.

## Indentation and Formatting
Tab size: 2 spaces
Indent size: 2 spaces
Continuation indent: 4 spaces
Tab size: 2 spaces
Indent size: 2 spaces
Continuation indent: 4 spaces
Charset: utf-8

XtendM3 uses Groovy programming and ending code lines with semicolons are optional.
Expand All @@ -48,7 +51,8 @@ XtendM3 uses Groovy programming and ending code lines with semicolons are option

### Extensions
Extension names should be a valid Java class name.
There is no restriction on extension names and will run with no error when compiled. However, when creating extensions, these naming conventions should be followed for readability and maintenance:
There are no restrictions on extension names, and they will run with no error when compiled.<br>
However, when creating extensions the naming conventions below should be followed for readability and maintenance.

__Module__
`SMS`
Expand Down Expand Up @@ -83,19 +87,21 @@ __Description__


#### Trigger Extensions
`Format: <Module>_<Type>_<EXT ID>_<Description>`
Format: `<Module>_<Type>_<EXT ID>_<Description>`

Example:<br>

Type | Program | Extension name
------------ | -------------| -------------
Interactive | APS450 | FIM_IW_1784_ValidateInvoice
------------ | -------------| -------------
Interactive| APS450| FIM_IW_1784_ValidateInvoice
Fnc Batch| APS450Fnc|FIM_Fnc_1784_ValidateInvoice
Batch| OOLINEPI| SLS_Batch_1784_ValidateCO
MI Batch| MMS100MI| SCE_MI_1784_ValidateDO

#### Utility Extensions
`Format: <Description>Utils`
Format: `<Description>Utils`

DateUtils
Example: `DateUtils`

#### Transaction Extensions
- EXT9XXMI is reserved for standard extensions and should not be used
Expand Down Expand Up @@ -138,20 +144,56 @@ validateType
- Variables should be in lowerCamelCase and constants in CAPITAL_CONSTANTS

## Extension Structure
- Global variables and main method are declared at the beginning of the program as compared to M3 Java where these are declared at the end of the program
`<to add example>`
- Global variables and main method are declared at the beginning of the program as compared to M3 Java where these are declared at the end of the program:<br>
```groovy
public class ExampleTransaction extends ExtendM3Transaction {
private final DatabaseAPI database;
private final LoggerAPI logger;
private Integer globalInteger;
private String globalString;
public ExampleExtension(DatabaseAPI database, LoggerAPI logger) {
this.database = database;
this.logger = logger;
}
public void main() {
globalInteger = 111;
globalString = "example"
}
}
```

### Utility Extensions
- Utility extensions cannot contain global variables
- It is not possible to declare and create instance of API in utility extensions, instead API should be passed as parameters in utility methods `<to add example>`
- It is not possible to declare and create instance of API in utility extensions, instead API should be passed as parameters in utility methods:<br>
```groovy
public class ExampleUtility extends ExtendM3Utility {
public String exampleGet(String miParamter, MICallerAPI miCaller) {
String returnValue;
Map<String, String> parameters = ["EXPA": miParameter];
Closure<?> response = { Map<String, String> response ->
returnValue = response.get("EXPA");
}
miCaller.call("EXT000MI", "Get", parameters, response);
}
}
```

## Programming Practices
### Logging
- LoggerAPI is used for logging messages
- It is encouraged to add logs in extensions to help in debugging and troubleshooting issues particularly on non-development environments (TRN, PRD) where there is restriction on modifying extensions
- Use proper level of logging – If extension is to be deployed in PRD, it should be limited to DEBUG level. Using INFO/WARNING/ERROR/TRACE on production environment can cause huge log size and impact MT Cloud environment
- Avoid logging values of all returned fields in a database query
- Logs can be accessed through Administration tool - Business Engine Jobs `<to add screen>`
- Logs can be accessed through Administration tool - Business Engine Jobs

<img src="../../../assets/attachments/programming-standard/businessEngineJobsLogging.PNG" width="950">

#### Log levels
- warning
Expand All @@ -161,9 +203,10 @@ validateType
- debug

### Database Access
- It is recommended to use standard API if transactions are available instead of direct database access on WRITE/UPDATE/DELETE where the program uses multiple tables to update. This could cause corrupt data if any on the logic, validation, or database update was missed or incorrectly updated
- It is recommended to use standard APIs if transactions are available instead of direct database access on WRITE/UPDATE/DELETE where the program uses multiple tables to update. This could cause corrupt data if any on the logic, validation, or database update was missed or incorrectly updated
- Make sure when using readLock that it is released at the end of the extension to avoid blocking another program access
- When reading table with partial keys, verify that this will not return too many records
- When reading table with partial keys, verify that this will not return too many records. Include _nrOfRecords_ parameter when performing _readAll_ API calls
- It is not allowed read more than 10,000 records per read. If there is a need to read more than 10,000 records, contact the review team with a good reasoning as to why it's needed
- When reading table, if possible, specify only the required column fields and avoid selectAllFields on querying tables particularly on tables which contains many fields

### Loops
Expand All @@ -173,20 +216,55 @@ validateType
#### In Memory
- SessionAPI is used to store information in key-value mapping that can be accessed between multiple extensions in the same session
- Make sure to use unique identifier as key names to avoid conflicts with other extensions when adding content to the cache
- Reassess when there is too many information that needs to be stored in a session. It may be more practical to add the information using XtendM3 tables instead
- Reassess when there is too much information that needs to be stored in a session. It may be more practical to add the information using XtendM3 tables instead

#### In Customer Extension Table
- Customer extension table refers to CUGEX1, CUGEX2 and CUGEX3 tables and can be accessed through standard API, CUSEXTMI
- Customer extension table should not be used for high volume data and high number of reads, updates, and deletes instead replace with XtendM3 tables

#### In Dynamic Database
- Dynamic Database or XtendM3 table is the equivalent of MAK custom table
- Table name uses prefix EXT and can have specific columns and logical keys
`to add example`
- Table name uses prefix EXT and can have specific columns and logical keys
```groovy
void read() {
DBAction query = database.table("EXTMIT")
.index("00")
.selection("EXITNO", "EXITDS")
.build();
DBContainer container = query.createContainer();
container.set("EXCONO", 999);
container.set("EXDIVI", "GGG");
container.set("EXSTAT", "30");
container.set("EXRESP", "KFHAKANSSON");
if(query.read(container)) {
String itemNumber = container.get("EXITNO");
String itemDescription = container.get("EXITDS");
mi.outData.put("RES1", itemNumber);
mi.outData.put("RES2", itemDescription);
mi.write();
}
}
```

#### In Text/XML/JSON File
- TextFilesAPI is the equivalent of MvxTextFile in M3 Java which can be used to read, write, or delete file
`to add example`
```groovy
public void readRecord(String folder, String record) {
textFiles.open(folder);
textFiles.read(record, "UTF-8", readFile);
}
Closure<?> readFile = { BufferedReader reader ->
List<String> header = resolveFields(reader.readLine());
while((line = reader.readLine()) != null) {
reader.println(line);
}
readResult = header.toString();
mi.outData.put("RESU", readResult);
mi.write();
}
```
- More examples available here: [TextFilesAPI](https://infor-cloud.github.io/xtendm3/docs/documentation/api-specification/textfiles-api)

### API Call
- M3 API can be run using IonAPI or MICallerAPI
Expand All @@ -207,10 +285,14 @@ validateType
### Date and Time
- Use classes [LocalDate](http://docs.groovy-lang.org/latest/html/groovy-jdk/java/time/LocalDate.html), [LocalTime](http://docs.groovy-lang.org/latest/html/groovy-jdk/java/time/LocalTime.html), [LocalDateTime](http://docs.groovy-lang.org/latest/html/groovy-jdk/java/time/LocalDateTime.html) if required to retrieve current date or manipulate date or time
`int entryDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")).toInteger()`
`int entryTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HHmmss")).toInteger()`
`int entryTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HHmmss")).toInteger()`

### Variable Types
- It is not allowed to use *__def__* statements in XtendM3, the type should be known
- If the type is unknown, use *__Object__*

## Testing
Extension approval requires submission of approved Unit test and Functional test documents
Extension approval requires submission of approved Unit test and Functional test documents

### Local
- Extensions can be locally tested using [XtendM3 SDK](https://github.com/infor-cloud/xtendm3-sdk-java) which provides the interfaces for the internal APIs available to XtendM3 Extensions. This SDK can be used to build, test and debug Extensions locally without needing to have any M3 environment up and running
Expand All @@ -219,7 +301,14 @@ Extension approval requires submission of approved Unit test and Functional test
### Live
- If the program extension has been activated in the tenant, it automatically runs and the changes to the program are seen by the users. During unit testing, it is recommended that the extension should execute only for specific users until it is verified working to avoid interrupting other users when running the same program
- Provided is an example of validating extension to run for specific user/s
`to add example`
```groovy
private boolean isEnabled() {
if (program.getUser() != "USERNAME") {
return false
}
return true
}
```

## Version Controlling
More details are provided at [Version Controlling](https://infor-cloud.github.io/xtendm3/docs/documentation/version-controlling/) page
Expand All @@ -234,5 +323,24 @@ Refer to the example of an [XtendM3 Extension Repository](https://github.com/inf
Any git provider that the customer prefers GitHub, GitLab, Bitbucket and etc.

## Documentation
- Extension JavaDoc on top of extension classes is required
- Extension Methods JavaDoc on top of methods (except for main) is recommended
- Extension JavaDoc on top of extension classes is required
- Example:

```groovy
/****************************************************************************************
Extension Name: EXT000MI/transactionName
Type: ExtendM3Transaction
Script Author:
Date:
Description:
* Description of script functionality
Revision History:
Name Date Version Description of Changes
Revision Author 2022-01-01 1.0 Descriptive text here
Revision Author 2022-02-02 1.1 Outlining what's been updated
Revision Author 2022-03-03 1.2 In the current revision
******************************************************************************************/
```

- Extension Methods JavaDoc on top of methods (except for main) is recommended

0 comments on commit 0d8c4b4

Please sign in to comment.