Building Logic Tutorial - Grails Logic

This is Part II of the Tutorial to create a project, from scratch, with Business Logic.  It presumes you have completed Part I - the creation of a project with domain objects, tests data, and a default User Interface.

This tutorial introduces the following key concepts: 
  • @Logic - how to define Logic Classes and Logic Annotations
  • Complex multi-table logic - how your logic addresses complex, multi-table logic
  • Automated Dependency Management - how logic dependencies are detected and automated
  • Object Declarative - how you can use Groovy/Java for complexity and extensibility
  • Assured Re-use / Integrity - how the Business Logic Engine ensures your logic is re-used
  • Logic Debugging - how to verify your logic is operating properly


Introduction

See here for a summary of the project (repeated below as required).  Recall we will not focus on the Data Model (which is trivial), or the User Interface.  In fact, we have utilized Grails defaults for those elements.  You can perform a similar tutorial in a wide variety of other IDEs and frameworks.

Introduce Business Logic

Tip: Key Concepts

Watch for boxes like this - they call your attention to the key concepts.
In the prior step, we have created a full application.  Grails has enabled us to create our domain model and user interface with remarkably little effort.  But we have no business logic - our data is not properly computed or validated.

The sub-sections below illustrate how to configure a Grails project for business logic, and add our first business logic class.

Configure GORM to call ABL

As we'll discuss further below, ABL operates as a Hibernate/JPA (GORM) event listener.  We now configure that (refer to the screen shot below):

First, open the existing conf/DataSource.groovy, and insert the line shown below:

hibernate.current_session_context_class = 'com.autobizlogic.abl.session.CurrentSessionContextProxy'


Next, create the ABLConfig.properties file as follows:
  1. Right click src/groovy, and

  2. Create a New > Other > File called ABLConfig.properties, and 

  3. Paste the contents below into the file
# Integrate GORM/abl session handling 
currentSessionContextClass = org.springframework.orm.hibernate3.SpringSessionContext


When you complete these two steps, your project should look like this:


This activates the Hibernate/JPA to publish transaction events to the ABL Logic Engine, which will find and execute your logic as updates are submitted to JPA.

Configure BuildConfig to add the Business Logic library

We must also include the ABL library into our project.  Add the following lines to your BuildConfig.groovy file at the 2 insertion points shown below:

mavenRepo "http://resources.automatedbusinesslogic.com/maven2"

compile 'com.autobizlogic.abl:autobizlogic:2.1.6'


Add Explicit Transaction Handling

We need to ensure that changes are transactional, with proper constraint handling.  Our imported Controllers contained most of the required code, but some was commented out.  Now that we have configured for ABL, we can enable all the required code as follows:
  1. Open abltutorial/controllers/abltutorial/Transactional.groovy
  2. Uncomment the import at the top of the file
  3. Uncomment the following code:



Verify you have the code as shown below.  The diagram shows that we have altered the default controllers to invoke shared logic in Transactional.groovy, which executes the transaction handling enabled above:


This should compile without errors.  In some cases, you may need to:
  1. Select the abltutorial project
  2. Right-click > Grails Tools > Refresh Dependencies
If you still see errors, see the Grails section in the Trouble Shooting Guide.

We're now all configured - let's add a logic class.

Add a Logic Class

 
We will now add some preliminary - not yet complete or correct - logic:
  1. Select src/groovy, and right-click to...

  2. Create a New > Groovy Class

    • Specify the Package as businesslogic

    • Specify the Name as CustomerLogic

  3. Paste or enter the code shown at right
 


package
 businesslogic

import abltutorial.Customer
import com.autobizlogic.abl.annotations.*

public class CustomerLogic {

    @Sum("purchaseOrders.amountTotal")
    public void deriveBalance() { }
}



This is our first utilization of @Logic:
  • Annotations are used to specify Business Logic (similar to annotations you already use for Hibernate/JPA persistence)

  • These annotations are placed in Logic Classes (Business Logic Components) that parallel your domain classes (so, the Customer Domain Class has a CustomerLogic class)

  • Here our logic is a Derived Sum (there are other types of logic); the method name indicates that the derivation is applied to the balance attribute
This should compile cleanly, and look like this when you are finished:



Test Logic

You can test the derivation logic as follows:
  1. Start the Application:
    • Right click the abltutorial project, 
    • Debug As > Grails Command (run-app))
  2. Click abltutorial.CustomerController
    • note that the Balance values are 0
  3. Click Home
  4. Click abltutorial.PurchaseOrderController
  5. Open the first one
    • use the hyperlink on left as shown here -->
    • Click Edit
    • Set Amount Total = 100, and
    • Update
  6. Click Home > List All Customers
    • note that the first Balance is now 100
Feel free to experiment with making other changes - add a PurchaseOrder, delete one, etc.  You will not destroy the test data - it is reloaded each time you start the server.

@Logic

@Logic

This example illustrates we can specify logic simply by defining Logic Classes (such as CustomerLogic) that contain Logic Annotations (such as the sum logic).
We have completed a common logic requirement - a multi-table derivation.  We did not need to determine an architectural approach (DAOs?  Service Objects?), nor write and maintain the code.

All we needed to do was specify Logic Classes (such as CustomerLogic), which contain @Logic annotations for logic such as:

 
@Sum("purchaseOrders.amountTotal")



Typical Multi-table Logic

The application is certainly not complete - the Order's amountTotal should be computed, not entered by the business user.

Let's recall our requirements, which we'll implement in the steps below:
 

Notes...

The customers balance represents the sum of the ready orders that are unpaid 

These steps will not take long, but if you want to just go get the code, you can find it here.

Compute amountTotal

Requirement 3 states that PurchaseOrder.amountTotal is the sum of the LineItem's amount.  To define this logic, we simply add a similar @Logic annotation:

  1. Create a PurchaseOrderLogic Groovy class in the businesslogic package

    • Add the import: import com.autobizlogic.abl.annotations.*

  2. Paste in the code shown at right


@Sum("lineItems.amount")
public void deriveAmountTotal() { }

Multi-table Chaining

Without any further specification on our part, our two rules chain:
  • altering a LineItem.amount adjusts the PurchaseOrder.amountTotal..

  • which chains to adjust the Customer.balance, per the prior logic
Now, any change to LineItem.amount will adjust our amountTotal.  You can start the application and verify this, or just proceed with the remaining logic.  The callout at the right observes how this new logic automatically chains with the existing logic.

In fact, you can try a number of tests - the business logic is automatically re-used overall all of these Use Cases:
  • Delete a Purchase Order
  • Delete a LineItem
  • Insert a new LineItem
  • Change LineItem amounts
Note you'll manually enter the LineItem.amount.  This is clearly incomplete, and we'll address it momentarily.

Declaring sums and counts is a fundamental pattern of business logic.  Your existing data must be correct for proper results.  Let's briefly consider how:
  • Reloaded test data: In our case, we are reloading our test data each time.  This load process is layered on Hibernate (not in the Controllers), so our reloaded data reflects our new logic -- sums and counts (and other derivations) will be properly initialized.  (This will be apparent after the amount derivations are defined in the next section).
  • Existing data: If you are using existing data, you need to initialize the existing data to conform to your new rules.  ABL provides Recompute to automate this process.

Compute amount

Of course, LineItem.amount is not supposed to be entered directly.  As stipulated in Requirements 4 and 5, it is derived as the price * quantity, where the price comes from the Product.  We define that logic as follows:

  1.  Create the LineItemLogic class as above

    • Add the import: import com.autobizlogic.abl.annotations.*

  2. Paste in the following:

@Formula("productPrice * qtyOrdered")

public BigDecimal deriveAmount() { } 

@ParentCopy("product.price")
public void deriveProductPrice() { }


Cascade Option

We could have derived amount as product.productPrice * qtyOrdered, but that would instruct the system that subsequent changes to the price were to be cascaded to previously entered LineItems.  

That is not the correct business objective, so we use the @ParentCopy logic annotation.

Refine Customer balance

As we noted above, the Customer's balance is not exactly the sum of all that customer's orders - Requirement 2 states we only want to sum the unpaid and ready orders.  To define that logic:

  1.  Edit your CustomerLogic class, and alter the balance like this:

@Sum
("purchaseOrders.amountTotal where paid = false and ready = true")


Add the checkCredit Constraint

Finally, Requirement 1 means we need to ensure that the resultant balance does not exceed the creditLimit. To implement this requirement:

  1.  Still in CustomerLogic, ensure that the resultant balance does not exceed the creditLimit by pasting in the following:

@Constraint(value="balance <= creditLimit", 

    errorMessage="Customer {name} exceeds credit limit")
public void checkBalance() { }


Executable Requirements

We are virtually done, so let's take stock.  Observe that the @Logic Annotations below correspond virtually identically to the requirements (ignoring #6 which we'll address later):


500 lines of code

These 5 simple @Logic annotations represent 500 lines of code.

The key elements are described in the following sections.



Automatic Re-use across Use Cases

These requirements are enforced over all the Use Cases that touch this data, including:
    • Test data loading (note the Customer balances are now properly initialized)
    • Adding and changing new PurchaseOrder and their LineItems
    • Making an PurchaseOrder ready, or paid
    • Reassigning a PurchaseOrder to a different Customer
    • Changing LineItem quantities, or Products
    Not only does this reduce the implementation effort, it improves quality since you can't "forget" to invoke logic for a corner case (e.g., reassign PurchaseOrder increases new balance, but does not decrease old balance).

    Dependency Management - alter Item Product and Quantity

    Automated multi-table dependency management is one of the most powerful elements of ABL.  To illustrate, execute this scenario:


    1. Start the Application:
      • Right click the abltutorial project, 
      • Debug As > Grails Command (run-app)
      • As noted above, this reloads your test data
    2. Click Customers
      • Observe that Alpha's Balance is 35
    3. Click Home > LineItems, Edit first row as follows: 
      • Change the Qty Ordered = 2
      • Product = Product 3
      • Update
    4. Click Home > Customers
      • Observe that Alpha's Balance is adjusted to 655





    Dependency Management

    Just like in a spreadsheet, the system recomputes data when referenced attributes are changed.  

    Dependency management can chain, including across tables, thereby automating complex multi-table transactions.
    The system performed this complex, multi-table transaction by automating dependency management:
    • Analyze the transaction to check for alterations to attributes referenced by rules
      • re-execute these derivations, 
      • but prune (skip) derivations where referenced attributes are not changed
    • Chain: see if any derived attributes are referenced in still other attributes - process as above
    So we can follow the chain of logic dependencies for our change to the LineItem Quantity / Product:
    1. Since LineItem.productPrice is derived as @Copy(Product.Price), and our Product (foreign key) has changed, the ABL engine updates the productPrice
    2. Since LineItem.amount is derived as qtyOrdered * productPrice and our productPrice has changed, LineItem.amount is recomputed
      • This illustrates dependency-based ordering - derivations are performed in an order reflecting their dependencies
    3. Since PurchaseOrder.amountTotal is derived as @Sum(LineItems.amountTotal) and the amountTotal has changed, PurchaseOrder.amountTotal is recomputed
      • This illustrates multi-table chaining - derivations such as sums reference data in related objects; when these are changed, the derivation is updated (more on performance in a moment)
    4. Similarly, since Customer.balance is derived as @sum(PurchaseOrders.amount where paid=false and ready=true), the changed amount causes the system to adjust the balance by the change in the amount
      • This is not quite trivial - the old LineItem was for 1 Hammer @ $10, now replaced by 2 Drills @ $315 - a difference of $620 (hence the balance adjustment).

    Automated Dependency Management confers significant value in agility and TCO:
    • Development: the bulk of your business logic code is typically dependency management.  As illustrated by this example, the 5 annotations replace 500 lines of java code

    • Maintenance: maintenance typically is more about the archaeology of deciphering dependencies so new code can be inserted correctly.  With automated dependency management, you simply add/change your logic, relying on the automated dependency management of the Business Logic Engine to optimize and order the resultant logic.

    Logic Execution, Debugging and Performance


    Plug-in Architecture

    Event-based logic injection means
    • No recoding - in this tutorial, there was no need to instrument Spring Web Apps to invoke logic

    • Assured re-use / integrity - logic execution is thus assured

    Hibernate/ORM Integration

    The Business Logic Engine operates not by your direct call, but rather by listening for Hibernate/JPA events.  

    Logic Debugging

    As developers, we spend a considerable amount of time staring at the screen, wondering "what is it doing?".  As developers, we designed business logic for transparent execution:
    • Logging: the Business Logic Engine can be configured to generate log tracing for every rule that fires, with full depiction of the Domain Object attribute state, and logic chaining (nesting)

    • Debugging: for Logic Methods (introduced below), you can use your debugger to stop in rule execution, examine variables, step into/over etc.

    • Test Tools: we provide tools you can use to see all changes to your database, and verify proper changes as jUnit asserts; these can assist in Test Driven Development
    For more information, please see Logic Debugging.

    Logic Performance

    Logic Execution is optimized to prune and optimize database access.  For example, aggregate processing uses adjustment logic - 1 row updates, rather than reading all the aggregated data (particularly important for chained aggregates).


    Object Declarative - complexity and extensibility

    Logic Annotations are very powerful.  You will find you can address most transactional business problems in an easy and natural manner.

    But surely not all.  We have therefore designed ABL so that in addition to declarative annotations, your Logic Classes can include Logic Methods -  Java / Groovy Methods to:
    • address complex logic, including the utilization of your existing Java libraries

    • provide Extensible Logic - build new generic services
    The last concept is particularly important.  The full download includes the BusLogicExt project, a library of such extended logic.  This is provided both for your use (these represent the automations of patterns we have seen), and as an illustration of how to build your own extensions (for patterns you detect).

    Dependency Management is fully supported for Logic Method derivations, through byte code analysis to detect references.

    We will now illustrate this to address the familiar pattern of auditing: whenever the customers credit limit is altered, we want to insert a row recording that fact.  

    Install BusLogicExt

    BusLogicExt contains just such a rule.  We install it into our project as follows:
      1. Download BusLogicExt-1.0.jar
      2. Copy the jar into the lib folder

      3. Edit your conf/BuildConfig, and add the runtime line shown below

        dependencies {
            // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.

            // runtime 'mysql:mysql-connector-java:5.1.16'


            compile 'com.autobizlogic.abl:autobizlogic:2.1.2'
            runtime 'mylib:BusLogicExt:1.0'                      // <=========

        }


    Specify Auditing Logic

    We add our logic to the CustomerLogic class.  In this case, it will be a logic method, which requires reference to the Domain Object (here, the customer),  so that our Logic Methods can issue code as as customer.getCreditLimit().

    We therefore need to insert the following code.  The Business Logic Engine uses the annotation to inject the domain instance object prior to calling your logic:

    @CurrentBean
    Customer customer// injected by BusLogicEngine

    @OldBean
    Customer customerOld;



    Similarly, we need to access some information about the state of transaction (also injected):

    @LogicContextObject
    public LogicContext logicContext



    The logic method for auditing can be pasted into CustomerLogic:

    /**
    * Insert row into child EmployeeAudit on salary change.
    */
    @Action
    public void actionAuditCustomer() {
       if (logicContext.getInitialVerb() == Verb.UPDATE && customer.creditLimit != customerOld.creditLimit)  {
           println "CustomerLogic#actionAuditCustomer creating audit record"
                CustomerAudit audit = InsertIntoFrom.insertChildFrom({new CustomerAudit()}, logicContext)
       }

    }


    You will need to resolve various imports.  Use STS services, or paste in the following:

    import abltutorial.Customer
    import abltutorial.CustomerAudit
    import com.autobizlogic.abl.annotations.*
    import com.autobizlogic.abl.logic.LogicContext
    import com.autobizlogic.abl.businesslogic.InsertIntoFrom
    import com.autobizlogic.abl.logic.Verb


    Finally, refresh dependencies by following the steps below, and verify that there are no errors:
    1. Select the abltutorial project
    2. Right-click > Grails Tools > Refresh Dependencies
    Whether you entered the code directly, or copied from this page, it should look something like this:



    Note we've inserted a breakpoint at line 34.  If you do so, you will see the debug information (the customer row contents) if you select the injected variables.  You will also note that the breakpoint is hit multiple times during the Loader process.  (This is a useful feature - your test data is subjected to possibly new business logic each time you start the server).

    We can now test our logic as follows:
    1. Start the Application:
      • Right click the abltutorial project, 
      • Debug As > Grails Command (run-app)
    2. Click the CustomerController
      • Edit the first Customer
      • Alter the Credit Limit to 901
      • Update
    3. Click the CustomerAuditController, and 
      • Notice the row there (you may also have noticed the logic tracing in the Console Log)

    Next Steps

    Thanks for going through the Tutorial!  Typical next steps:
    • Download the full product - it's open source, and free for development

    • Review the Case-based Training - this tutorial is the "Hello World" of business logic... the training shows that automation scales to complex problems such as a Bill of Materials Explosion, or the allocation of a Payment to a set of outstanding Orders.

    • Review the system Architecture, particularly how it confers a Rich, Active, Declarative Object-oriented architecture for your systems

    • ABL supports advanced services for administering your logic, including

      • Dynamic Logic enables you to reload changed logic without restarting your JVM

      • Recompute services enable you to verify whether your existing data conforms to your logic annotations, and (where possible) to recompute your data per the logic annotations

      • Logicdoc to capture requirements transparently for sharing with Business Users, with full traceability into the underlying logic
    ċ
    BusLogicExt-1.0.jar
    (35k)
    Val Huber,
    Apr 21, 2012, 8:34 AM
    Comments