This contrasts Procedural vs. Declarative approaches to Business Logic.
In procedural programming, considerable design attention is invested determine when and in what order to invoke logic, how to assure re-use, and in optimization. This manual approach to dependency management is common to a broad class of languages, including 3GLs, 4GLs, and modern object oriented languages such as Java.
These design considerations exist not only for initial build, but on each iteration cycle. Such manual design - and re-design - is costly, and provides ample opportunities for errors.
The high cost of "how"
It has often been noted that Procedural approaches require you to encode "how" to do something, rather than "what" as in a Declarative approach. A brief example will illustrate the implications.
Imagine that our Analysis uncovers that we need to compute the Customers' balance, in order to check it against the credit limit. The "what" is very simple: derive the balance as the
sum(purchaseorders.amountUnPaid where isReady = true).
The "what" is not so simple, and incurs a number of costs:
- Analysis effort: Identify related Use Cases
While we may have encountered this while analyzing Place Order, we need to be sure it is followed in the related Use Cases:
- Delete Order - the balance is reduced
- Make Order Ready - the balance is increased
- Pay Order - the balance is reduced
- Re-assign Order - old customer balance reduced, new balance increased
- Design effort: non-trivial
There are complex design decisions about how to enforce the logic - introduce a service layer? Build Data Access Objects? Utilize Hibernate events?
- Quality: compromised by manual design
Errors are inevitable in a manual process. In the example, above, we forgot the case of re-assigning a Purchase Order to a new customer: we need to decrement the old customer's balance, and increment the new customer's balance.
The implication of forgetting this is catastrophic: the database does not "add up", leading to bad business decisions and worse.
- Coding effort: a simple sum can require 4 pages of code
The agility factors here are daunting: a single decision - just one sentence - can require 8 days of effort! The root cause is Dependency Management: your code must detect every change - in every case - and arrange to trigger required logic. In a declarative approach, Forward Chaining is automatic. So, for example, a simple Sum (1 line Logic Rule) requires 4 pages of Java code.
It is a classic in applications development: should derived aggregates be stored or computed each time? Historically, this is a trade-off of speed (store the aggregate so changes are adjustments rather than aggregate queries) vs quality (bugs can compromise data integrity, as noted above). Despite our best efforts, it is inevitable that we make the wrong decisions, where these are often visible only in the late stages of a project (e.g., load testing, going live, etc).
Reversing such design decisions is very expensive, since it requires you:
- Find all the code where the decision was implemented (not so easy in hundreds of thousands or millions of lines of code)
Each maintenance (or iterative development) change requires re-design:
- Ordering: how to introduce new code, and manage the interactions with existing code
- Re-use: where must I make similar changes
- Performance: often a change invites re-visiting basic approaches, though time pressures often mean this is not feasible, so that systems performance slowly degrades over time
Spreadsheets show the way
It has been many decades since we wrote programs to perform business analytics. No one today would dream of writing a Basic program to add up a table of numbers.
Yet we do it all the time for data processing.
The breakthrough for Spreadsheets was the realization that to enable the End User to declare how their data is compute with simple derivations. The spreadsheet supplies the chaining: when any referenced data is altered, the value is recalculated.
Automated Business Logic brings the same paradigm - and the benefits of agility and transparency it confers - to Business Logic.
By contrast, Business Logic is declarative. It is un-code: uncalled, unoptimized, unordered (but not unloved!). Not only does this supplant a large volume of code, the Business Logic Engine automates the design activities described in the sub-sections below.
"What", not "how"
Instead of coding how to do something, you declare what you want to achieve. As we saw in the example above, you simply specify balance as the
sum(purchaseorders.amountUnPaid where isReady = true).
Logic can be chained: 1 rule can depend on another, even across tables. For example, the Customer balance is a chained aggregate: derivation that sums (refers to) the Purchase Order totalAmount, itself a derivation that sum Line Item amounts. And, it must be evaluated before the Credit Limit Check. So these must all be executed in the proper order to produce the correct result.
Business Logic is not invoked by direct call. Hibernate invokes the Business Logic Engine as an event, which in turn invokes your logic. This helps insure integrity: integrity you invoke is essentially elective, as opposed to Business Logic which is automatically invoked whenever any update is made - regardless of transaction source (interactive vs. message), service, etc.
Re-use is a core development objective, requiring substantial design effort in procedural approaches to maintain critical database integrity. It is certainly not automatic. Business Logic, however, operates as an Event Handler, so logic enforcement and re-use is automatic. This re-use occurs at the level of both code and design, as described below.
Code Re-use - over transaction source
Object oriented languages provide this re-use, by requiring all accessing code to use an objects method. Hibernate events enable an analogous level of encapsulation with events.
Automated Business Logic employs these to assure that all Hibernate-based updates are subjected to the Business Logic you specify. Such automatic invocation guarantees integrity regardless of transaction sources (customers get the same answer regardless of the interaction):
- Interactive business logic is not required in page actions, avoiding the anti-pattern of business logic in page actions which cannot be used for message-based transactions
- Message-based - services automatically re-use the domain logic, so that services are thin.
Design Re-use - over transaction type
Even more powerful - and much less common - is encapsulation of not just code, but design intent. This arises since your logic is declarative: you declare what the affect should be, not how (the methods that implement it). The power of this is best illustrated by a deceptively simple example.
Customer.Balance rule, perhaps declared for Place Order, simply states that its value is the sum of the unpaid orders. It does not tie this to an algorithm, enabling the system to optimize with efficient approaches such as adjustment. Nor is the rule tied to a particular method (Use Case) - it's design intent applies to all transaction types.
This design intent is declaratively encapsulated into the Order Domain Object, so that it is automatically re-used over all these related transactions:
- Delete Order - the balance is reducedPay Order - the balance is reduced
- Reassign Order to a new customer - new customer balance increased, old balance decreased (for unpaid 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
So, when you make a maintenance change to your logic, the system automatically reflects it in all the relevant transactions.
Unlike manual systems that often degrade over time due to the costs of re-optimization, Business Logic is automatically re-optimized on each change, so performance remains high. So, for example, you can alter the "stored vs computed" decision instantly, in contrast to the quandary incumbent in the procedural approach.
The real value is that these services are repeated on every logic change, resulting in Maintenance Automation.
Toward Executable Requirements
Declarative makes this possible.