Count Business Logic Rule declares a Parent Domain Objects attribute to be maintained as the count of a designated Children Role's rows, with an optional qualification to filter child objects.
Computing the Purchaseorders's itemCount is part of the Use Case Place Order no Empty Orders requirement, which illustrates one of our key Patterns - using counts as an existence check. The logic is declared as follows:
fail if purchaseorder.itemCount == 0
The following screen shot declares the
Purchaseorder.itemCount as the count of that Purchaseorders' Lineitem. The detailed procedure is described in the subsections below.
If you have not already done so, create a Business Logic Component, shown as
Add a Business Logic Method
This need not have content - it will only be called for debug purposes.
By convention, we recommend the method name shown in the title. This designates the attribute being derived,
itemCount in the example above. If you would rather use a different name for the method, you can specify the
attributeName value in the annotation.
Precede the method with the
Unlike other Business Logic Rule methods, the method body has no semantic significance. It is provided for debug purposes only. All of the Business Logic is specified by the annotation parameters, described in the following sub sections.
There are two different forms - the simplest and most common (see the example at the top of this page)
@Count(value="countExpression", inMemory=[True | False],
persistent=[True | False], attributeName="ParentAttributeName")
value (Count Expression)
The value is the default (only) parameter in the simpler form. It is an expression of the following form:
<Children Role> [where ChildExpression]
<Children Role> is a role in the parent that returns the Child rows
<ChildExpression> filters the children that contribute to the count
- It may reference any Child attribute, and does not require dot-notation qualification
- It may not reference Parent attributes of the child
- If these are required, simply define other child attributes to derive these values
- It is a general Boolean expression, so can include
and or ( ) > etc
Use this to specify the Parent Attribute Name when your Logic Method Name does not encode this.
Count is a core Business Logic Rule. While simple and natural to state, they imply significant performance and re-use implications as described in the sub-sections below.
Business Logic Execution triggered by Child changes
As noted above, your Logic Method is never called. The Business Logic Runtime reads the
@Count annotation, and executes the following logic in response to Child inserts, updates and deletes.
Determine whether adjustment(s) are required
The Business Logic Engine analyzes the Child changes to determine when adjustments are required:
- Note: If none of the conditions below are met, no adjustment (or access) of the Parent is made.
|Qualification Condition Changes|
|Foreign Key Changes||Lineitem is assigned to a different Purchaseorder.|
|Child is inserted or deleted|
Execute Adjustment (or Recompute)
If the prior step determines the parent must be altered, the parent is retrieved. The parent is not retrieved (the SQL is pruned).
How the parent change is made can occur in three different ways
|Adjustment: this is the Best Practice since it performs best by avoiding an aggregate SQL|
- The system determines whether the child qualification has changed (including implicit changes such as insert or delete)
- If so, increments/decrements Parent count field
parent field is not transient
|In Memory: use for transient attributes with many sums/counts along the same relationship|
- All the child rows are read into memory (they may already be in cache, e.g., for another sum/count already processed)
- The Business Logic Engine then counts the relevant rows
|InMemory is used if so specified|
|SQL: use for transient attributes and Parent can have many children|
Select Count... is issued for each aggregate
|used for transient attributes and |
InMemory is not specified
Forward Chain to Parent Logic
Once the parent is altered, the Business Logic Engine must Forward Chain to run the Parent's logic. This process can nest several levels as explained in Context:
- The Client inserts, updates or deletes a LineItem
- That change adjusts
- Purchaseorder Business Logic Constraints are executed
From this description, note a transaction may result in multiple executions of the Business Logic for a specific row. Also note that Hibernate caching means that the adjustment nesting above (which may occur for each Lineitem in a Purchaseorder) will read/adjust the Purchaseorder and Customer only once.
Performance: pruning and adjustment
- Pruning: not on order.date change
- Adjustment: The Customer.Balance rule is optimized to minimize SQLs by using adjustment logic. The system does 'not' issue a
select count query
Performance denormalizations (ala relational index)
Automatic Re-use over related transactions
The Customer.Balance rule, while declared for Place Order, is encapsulated into the Order Domain Object, so that it is automatically re-used over all these related transactions
- Delete Order - the balance is reduced
- Make Order Ready - the balance is increased
- Pay Order - the balance is reduced
- Reassign Order to a new customer - new customer balance increased, old balance decreased (for IsReady Orders)
- All the various cases of Change Order:
- Change a Line Item Quantity
- Change a Line Item Part
- Add a Line Item
- Delete a Line Item
Be aware of the following.
Defaults and Alterability
Unlike Formulas where client code can alter values, the semantics for sums/counts preclude direct client setting of values. You can control how this performs with the property
- Missing or non-negative means ignore updates, and force values to 0 on insert
- This option can reduce exceptions when introducing Business Logic into projects that already directly manipulate sums and counts - the system will simply manage these per your rule specification, ignoring program code
- Negative value means throw exception
- Specifying a value such as
no will raise an exception; this forces clarity among client applications
Adjustment logic depends on the database containing the correct values. Therefore, when you create or alter a non-transient count definition, you must create the column in your database, and initialize it to the correct value. Consider updates statements such as:
set customer.balance =
where name > " "