Count

The 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. 

For example:

  @Count("lineitems") 




Context

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:

  1. CommitConstraint Purchaseorder.EmptyPurchaseorder as fail if purchaseorder.itemCount == 0
  2. Derive Purchaseorder.itemCount as count(lineitems)

Usage

The following screen shot declares the Purchaseorder.itemCount as the count of that Purchaseorders' Lineitem. The detailed procedure is described in the subsections below.


Define a Business Logic Component

If you have not already done so, create a Business Logic Component, shown as PurchaseorderLogic, above.

Add a Business Logic Method derive<ParentAttributeName>

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 @Count annotation

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("CountExpression")

or

  @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]

where

  1. <Children Role> is a role in the parent that returns the Child rows
  2. <ChildExpression> filters the children that contribute to the count
    1. It may reference any Child attribute, and does not require dot-notation qualification
    2. It may not reference Parent attributes of the child
      If these are required, simply define other child attributes to derive these values
    3. It is a general Boolean expression, so can include and or ( ) > etc

inMemory

persistent

attributeName

Use this to specify the Parent Attribute Name when your Logic Method Name does not encode this. 


System Operation

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.
ConditionExample
Qualification Condition Changes
Foreign Key ChangesLineitem 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


AlgorithmDescriptionUsed When
Adjustment: this is the Best Practice since it performs best by avoiding an aggregate SQL
  1. The system determines whether the child qualification has changed (including implicit changes such as insert or delete)
  2. 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
  1. All the child rows are read into memory (they may already be in cache, e.g., for another sum/count already processed)
  2. 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
  1. 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.itemCount
      • 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

TODO:

  1. Pruning: not on order.date change
  2. Adjustment: The Customer.Balance rule is optimized to minimize SQLs by using adjustment logic. The system does 'not' issue a select count query

Best Practice.

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

  1. Delete Order - the balance is reduced
  2. Make Order Ready - the balance is increased
  3. Pay Order - the balance is reduced
  4. Reassign Order to a new customer - new customer balance increased, old balance decreased (for IsReady Orders)
  5. All the various cases of Change Order:
    1. Change a Line Item Quantity
    2. Change a Line Item Part
    3. Add a Line Item
    4. Delete a Line Item

Notes

Be aware of the following.

Multiple Relationship Disambiguation

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 aggregateDefaultOverride in SBLConfig.properties:

  • 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 n or no will raise an exception; this forces clarity among client applications 

Database Initialization


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:

update customer 
set customer.balance =
  (select count(Purchaseorder.AmountUnPaid) 
  from Purchaseorder
  where (purchaseorder.customerName=customer.name))
where name > " "
Comments