Recommended Programming Practices
Recommended Programming Practices
Business processes, once implemented, are used for very long time – perhaps 10 years or more. For any large-size organization, changing a business process is expensive and risky, so the processes tend to “stick around”. However, business processes are not cast in stone. They do get changed all the time – but usually in a piecemeal fashion. The software is frequently customized to meet specific customer demands, or to support new requirements. Consequentially, the business logic code that you create today will have a very long life – but it will have to be maintained and updated. By some estimates, software maintenance costs a lot more than the initial development. This means that you need to put quality into design and coding.
Choose the right implementation for the business logic operation
Depending on the application functional requirements, you should implement the business operation as a message rule, a scheduled rule, or a job:
Use Case | Implementation |
---|---|
The operation is started by the user and takes no more than 10 seconds. |
Implement as a message rule . |
The operation is started by the user and takes up to few minutes. |
Implement as job , use with a job progress dialog. |
The operation is started by the user and may take more than few minutes. |
Implement as job , use with the Single Job view. |
The operation should be performed automatically, at scheduled time intervals. |
Implement as a scheduled rule . |
Choose the right code structure
For simple business operations, the service method can contain all code that implements the operation.
For more complex business operations, the service method code may become too large, difficult to understand, and difficult to maintain (the "500-line method"). In such cases, you should extract parts of the implementation into helper methods, each responsible for certain task on the overall business operation. The helper methods can be defined in the same service class, but they should be declared as "private", so that they will not be exposed to the views:
public class CostService {
public void createScheduledCosts(List<String> costIds) {
List<DataRecord> recurringCosts =
findRecurringCosts
(costIds);
for (DataRecord recurringCost : recurringCosts) {
createScheduledCosts
(recurringCost);
}
}
private List<DataRecord>
findRecurringCosts
(List<String costIds) {
...
}
private void
createScheduledCosts
(DataRecord recurringCost) {
...
}
}
If the code in helper methods can be called from multiple service methods, or even from multiple service classes, you should move these helper methods into their own helper class. In this case, you can say that a helper class "encapsulates" details of business logic, making the service methods much simpler to program.
public class CostService {
public void createScheduledCosts(List<String> costIds) {
List<
RecurringCost
> recurringCosts = findRecurringCosts(costIds);
for (RecurringCost recurringCost : recurringCosts) {
recurringCost.createScheduledCosts();
}
}
private List<
RecurringCost
> findRecurringCosts(List<String costIds) {
...
}
}
// in RecurringCost.java
public class RecurringCost {
public
void
createScheduledCosts
() {
...
}
}
Design services as stateless components
Service classes should not use any instance-level or class-level variables. All necessary data for the execution is provided by the Web Central in the service context. To understand why it is so, consider the service lifecycle:
- The view calls the workflow rule by sending an HTTP request.
- Web Central verifies that the user has authority to execute the workflow rule.
- Web Central creates a new instance of the service class specified in the Workflow Rules table.
- Web Central creates service execution context, obtains a database connection from the JDBC connection pool, and starts a new transaction.
- Web Central calls the service method, which performs the requested operation.
- Web Central commits (or rolls back, in case of failure) the database transaction and returns the database connection to the pool.
- Web Central sends the workflow rule response (or the error information, in case of failure) to the view.
It is possible that two or more users will call the same workflow rule at about the same time. In this case, there will be multiple HTTP requests, and multiple instances of the same service class will be executed in parallel by the JVM. Non-final static (class) variables are shared between all instances. Because multiple instances of the same service class may be running in parallel, static variable and static methods are not safe to use without proper synchronization.
Service methods can create other objects, for example:
RecurringCost cost = new RecurringCost();
These objects are local method variables, and exist only while the method is executing. After the workflow rule request is complete, these objects will be garbage-collected.
Store workflow process state in the database
Services cannot preserve any workflow state variables between consequent workflow rule requests, because Web Central always creates a new service instance for each request.
- Workflow state can be saved to and loaded from the database. This method is reliable – if the user closes the browser in the middle of the process, the last known state is still saved and the user will be able to continue the work later.
- Workflow state can be stored on the client. This method is unreliable – if the browser crashes, or if the user clicks on the Back button by mistake, the state will be lost.
Put process information into the same record
If you need to store additional information about the process (approved by, approved on date, completed by, completed on date, etc.) include this information as fields in the record being processed (e.g. the Work Request, the Move Order, the Archibus User Account Transaction table, etc.) This gets the right level of detail in process reporting and is easily configured using the usual presentation layer tools -- no awkward joins or sub-queries on huge amounts of generic process log information which is not significant to the process.
Use status codes
Use “hierarchical” status codes for sub-states (e.g. Approved, Approved -- Accepted by Vendor) that result from looping back to the same form or from optional sub-processes. In this way, you can adjust the process without having to redesign the presentation layer forms or views.
Allow repeated execution
Design all status codes and rules so that they can be called repeatedly without altering the validity of the data. For instance, if a rule notifies an approver of an urgent item, record the fact that the message has been sent in a sub-state of the Status Code.
Use logging correctly
Log as much information as can be helpful to troubleshoot services during development and production. Select correct logging level as follows:
- ERROR: not recommended to use; throw ExceptionBase instead; exception will be logged by Web Central.
- INFO: use for information that can be used to verify normal execution, such as important data values or major business logic decisions.
- DEBUG: use for comprehensive debugging information that can be used during development.
Strive for well-designed, readable code
The code should be easy to read. Time invested in writing clear, well-structured code will pay off during debugging and maintaining the code later.
- Use self-describing variable and method names, for example yearlyFactorEscalation instead of escalation or even x .
- Use standard Archibus code style.
- Comment the code.
You should use following code comments:
-
File-level comments:
- Functional description of the class/module.
- Author (and current owner, if different from author).
- Major version changes.
-
Method-level comments:
- What does the method do.
- Input parameters and return values.
-
Inline code comments:
- For complicated logic or calculations: intended behavior and effect.
- For SQL queries: what do they produce, and why SQL was necessary.