We're on Facebook

Business Logic Architecture

Web frameworks continue to evolve towards an ideal of highly automated view / presentation logic for Rich Internet Applications. ORM products are now an accepted and standards-based approach for persistence.

This places increasing attention on the approach and effort for Shared Transaction Business Logic, which can represent 50% of a typical update-oriented application. Though the term Business Logic is often used, there is considerable confusion, ranging from definition to architectural approaches to appropriate tools.

This paper explores considerations and approaches for this important topic. Please see here for a description of the ABL architecture.

Abstract

This paper defines Transaction Business Logic as the
multi-table derivation, constraint and action logic required for database
integrity. We propose key criteria for evaluating
various architectural alternatives: encapsulation for active enforcement of logic, separation
to maximize interoperation with other components, performance, and business
considerations of Agility and TCO.

After surveying various alternatives, we propose Rich Active Declarative
Domain Objects, where a Business Logic Engine plugs in to ORM events for active
enforcement of declarative, multi-table logic.

What is Transaction Business Logic

Business Logic is a term often used informally,
but formal definitions have proved elusive.
So, it is appropriate to begin by defining what we mean, with an
illustrative example.

Transaction Business Logic is most easily described in the context of this
familiar and representative example, where our logic specification for placing and modifying orders can be
summarized as:

1.Constraint:
customer.balance < customer.creditLimit

2.customer.balance
= sum(orders.total where paid =
false)

3.order.total
= sum(lineitems.amount)

4.lineitem.amount
= quantity * partPrice

5.lineitem.partPrice
= copy(part.price)

Business logic elements include:

Constraints

These are the most obvious elements of business
logic - expressions that prevent bad data from being committed into the
database. These include single value
constraints (not null, in value list, in range etc), and multi-value
constraints (as in the Constraint example above the diagram). Finally, it is often required that the expressions deal with old values (reject if salary < @old(salary) -- often referred to as state transition logic).

Derivations

The most challenging element is derivations, since
these are typicallychained(dependencies require detection and properly
ordered change propagation), andmulti-table(so are performance-sensitive). In our example, the Customer.balance is multi-table (it sums that customer's unpaid
order.total), and is chained (since
the order.total is itself a derivation).

Actions

Actions include auditing, copying,
messaging, business process initiation, etc. Ideally, these are generic,
so they can be re-used over multiple Use Cases and Domain Models. For example, we might send email if a
Customers’ balance is close to the credit limit.

Such logic should be architected so that it encapsulates domain access,
automatically shared over User Interfaces, Message-based and all other types of transactions.

Why: it’s half the app

Transaction Business Logic is important since it constitutes
approximately half the code / effort for a typical transactional application.

Context: Well-Accepted Principles

We evaluate several alternatives for Transaction Business Logic
in the next section. Here, we define evaluation criteria based on several well-accepted architectural principles.

Encapsulation
– guaranteed re-use, active enforcement

Encapsulation means all the
invoking code is guaranteed to utilize the same logic – various screens,
messages, etc. Not only does this mean
transaction business logic should not be placed in individual controllers, it
also means the job of the architecture is to assure the use of the logic.

In particular, enforcement ought
not to require the caller to make “magic calls” for logic invocation. The architecture should provide automatic, active enforcement of
business logic.

Architectural
Separation – snap-in components

Maximize
Interoperation: Using existing CRUD (Create, Read, Update, Delete) APIs,
for example SQL or ORM, means that existing tools / frameworks do not require custom code for interfaces, or to participate naturally in
building RESTful services

Minimize
Interference: Domain Objects are widely used for persistence, transport,
etc. Injecting code into such objects
can affect characteristics such as serializability

Performance
is critical (pruning, adjustment)

As our example illustrates,
aggregates are integral to business logic.
Unlike simple attribute validations, such multi-table logic means that
minimizing SQL access plays a critical role in performance. It comes down to when and how the
aggregates are derived.

When means analyzing transactions to identify when there are no
changes to data on which the aggregate is dependent, and pruning SQL overhead. For example, changing an order’s date does not affect the customer’s balance, so no SQL overhead should be incurred.

Once the logic determines that
aggregates are affected, there are important strategies regarding how aggregate derivations are performed.
Application architects have long
recognized that performance denormalizations
(storing aggregate results in the parent object, such as the customer balance)
can dramatically improve performance by eliminating SQL aggregate queries, or
reading all the child rows into memory.
Such techniques enable the application to utilize adjustment logic, so, for example, changing a Line Item quantity
simply adds the delta to the Order total, which in turn adds the delta to
the Customer’s balance.

This is important when there are many
child rows per parent (orders per customer), and particularly important when
there are chained multi-level aggregates.
For example, changing a Line Item quantity should not entail adding up
all the Orders / Line Items to compute the balance.

Ideally, performance
denormalizations are a tuning parameter that can be changed as performance
characteristics become clear, without
affecting existing logic. A good
example of this is a relational database index: you can add it at any time
without recoding.

Business
Agility and TCO – Declarative wins

The purpose of a strong
architecture is ultimately to provide business value. So, it is appropriate to address that issue
directly. Business certainly has an
obvious interest in the items above, but there are always two concerns near the
top of the list:

Agility: business advantage goes to the organizations that are the fastest to respond
to new opportunities, or to change

Total
Cost of Ownership: there is obvious benefit when maintenance costs are
reduced, and when transparency increases the communication between business and
IT to reduce requirements risk. There is
nothing as expensive as a system that fails to meet user needs.

In system terms, this pretty much
boils down to the volume and complexity of the code we write.

Core of the problem: Dependency Explosion

While there are Rete / Process engines for different aspects of Business Logic, the presumption is that
Transaction Business Logic is pretty much ad hoc code. The problem is, there’s a lot of it.
Our simple exercise above, believe it or not, results in about 500 lines
of Java.

The problem boils down to coding an
explosion of dependencies. For example,
our derivation for Customer.balance is directly dependent on

Order.total

Order.paid

Order.customer (the foreign key)

And, we are far from done, because
dependencies can cascade due to chained derivation logic. So, Customer.balance is also dependent on
everything Order.total depends on

Declarative Approaches gaining acceptance: JSR 303, Grails, …

Declarative approaches are generally defined as those where you state what should happen, not how. For our purposes, the what is the 5 rules at the top. The how is the dependency automation, as well as other elements such as performance and re-use.

Declarative approaches are gaining increasing favor, due to the business value of the automation they confer. JSR 303 provides for Bean Validation, provided by various implementations such as Hibernate/JPA and Grails. While these provide minimal or no support for multi-table derivations, they do suggest an approach for specifying business logic.

Ideal: executable design (intent)

The ideal here is that the business logic is

Concise – the ultimate goal is an Executable Design Specification, where the smallest list of formal specifications runs

Transparent – the logic should also be clear enough so Business Users can read it, and collaborate with IT to spot errors, omissions, etc. This can reduce Requirements Risk.

The “rules” at the top of the page meet these requirements.

Simpler – fewer objects / less code – is better

Finally, to include a more
intuitive criterion, we suggest that simplicity is a virtue that has economic
value. In the context of Java, it is not
uncommon to require a Domain Object, a DTO (Data Transfer Object), a DAO
(Data Access Object), and Service Objects (for multiple table transaction
logic). These must be maintained and
kept in sync, reducing Agility and increasing TCO.

Alternatives for How

Using the criteria above, the sub-sections below briefly
survey alternatives for transaction business logic.

Rete Engines

Rete-based inference engines provide valuable services that enable Business Users to manage (safely selected) elements of their system, bypassing the need to contact IT. While valuable in that sense, Rete engines are quite inappropriate for the transaction processing elements of Business Logic:

Poor aggregate performance

Aggregate processing is a well-known challenge for Rete engines. The root cause is that Rete engine interfaces do not presume a transaction that can be compared to an existing database to compute adjustments, and use these to prune processing logic. So, to define a sum, they must bring all the child rows into memory. Particularly when such aggregates are Forward Chained on other aggregates, this can be prohibitively expensive.

No encapsulation

Unlike automated Business Logic where rules are automatically invoked for all updates, Rete rules require explicit calls. Integrity that is elective is not reliable, and does not meet the requirements of regulatory compliance or system integrity.

Lacking persistence integration, Rete engines do not have concepts like old values, so there is no natural way to express state transition logic. They also do not provide advanced logic such as copy and allocation.

BPM

Process Engines provide excellent value in workflow and system integration processes, where a sequence of steps is inherently manually ordered. They are a poor fit for transactional Business Logic:

Agility / TCO compromised – no dependency-based automated ordering

BPM ordering does not address the important dependency-based automated ordering, a core issue for transaction logic

No aggregate support

BPM engines focus on workflow – they are not designed to provide persistence integration with pruning and adjustment logic that are crucial for multi-table transactions.

No Encapsulation / poor separation

As described above

Visual Rule Designers

Visual Rule Designers provide a graphical (flow-chart like) way to program your business logic. While visually appealing, these are really totally procedural, suffering from all the liabilities of programming:

Agility / TCO compromised: your diagram is inherently ordered, which must be designed – and re-designed – as changes are necessary

No Encapsulation: your logic is not called without explicit invocation, so re-use is not guaranteed. Worse, re-use is of the procedural variety, not re-use of declarative logic (such as a sum, which is automatically re-used over inserts, updates and changes)

Performance not automated: such engines typically require you to manually invoke SQL calls. This is a very low level of abstraction and obviates the possibility of automated optimizations

Optimization: such procedural approaches require you to manually code optimization techniques such as pruning or adjustment, which must be reconsidered on each maintenance change

Triggers

Database triggers provide excellent encapsulation. However, they incur all the disadvantages of manual dependency management, implemented in a non-standard programming language often without debugging tools.

xDBC injection

Technologies such as Spring provide injection to intercept designated calls, such as JDBC/ODBC calls. This can provide encapsulation. But these are typically not integrated with ORM services for caching, so performance suffers.

Service Layer (multi-table, discoverable)

A very common approach to business logic is to build a Services Layer. Often designed to resolve complex design issues (is aggregate adjustment a responsibility of the child Order, or the parent Customer?), this involves creating explicit APIs for each Use Case (e.g., addOrder, deleteOrder, modifyOrder etc).

These clearly “publish” the Use Cases and public interfaces for the system. However:

Agility / TCO compromised: Service Layers tend to be large. For example, the Service Layer for our simple example required around 500 lines of Java to deal with all the related Use Cases. In particular, the main source of code was extensive Dependency Management as described in Dependency Explosion, above.

No Encapsulation: your logic is not called without explicit invocation, so re-use is not guaranteed.

Performance not automated: your code is responsible for optimizations described above such as pruning and adjustment. These can dramatically increase maintenance costs, since these must be reconsidered for each change. As with SQL indices, organizations ought to be free to introduce / remove performance denormalizations without rewriting.

Rich Domain Model: ORM Domain Objects as Data Access Objects

Domain layer proponents regard a Fat Service Layer to be an anti-pattern (“Anemic Data Objects”), since business logic is not encapsulated. It is often proposed that the Domain Objects be extended with Data Access Object “crud” methods. While this provides encapsulation, there are many issues not addressed:

Architectural separation is not provided, since logic may interfere with the use of Domain Objects as transfer objects, and framework automation of existing persistence APIs is unavailable since these bypass the Domain logic methods

Business Agility is not provided since such Domain methods must be manually coded, and directly deal with the Dependency Explosion, optimization, and multi-object logic

Rich Active Domain Model (via ORM Events)

An elegant approach for addressing these concerns is to leverage ORM events for transaction business logic. This provides:

Encapsulation of logic: any Domain Object update invokes the logic

Excellent separation: frameworks that automate ORM calls can be used directly

This approach is so attractive that proponents have characterized the Fat Services Layer as an anti-pattern (so-called Anemic Domain Objects), as contrasted to a Rich Data Model that encapsulates business logic. ORM events confer a Rich Data Model with the benefits noted above, but there are drawbacks:

Poor Agility / TCO: the event handlers still must provide the dependency management to analyze what has changed, and recompute dependent derivations (which can, as we saw above, chain)

Moreover, experience has shown that the subtleties of ORM event technology make even the simplest logic (e.g., auditing) very tricky

Rich Active Declarative Domain Model

We can now summarize how Automated Business Logic results in
Active Domain Objects can fit into your overall architecture using a Rich Domain Model approach:

Separation
by Event-based Injection, and

Encapsulation
for Active Enforcement

Business
Logic Engine automates multi-table business logic

These are further developed in the
sub-sections below.

Event-based Injection: Separation per use of existing APIs

While injection is usually associated with compile-time injection of logic, you get the same effect by plugging into ORM events. This provides the Architectural Separation:

Maximize Interoperation: frameworks and tools that automate calls to ORM events can be fully leveraged, without any custom code to invoke services

Minimize Interference: no impacts to Domain Objects (e.g., serializability for use as Transfer Objects)

So, for example, MVC-oriented interactive apps simply make the usual calls to persist data, and logic execution is automatic.

Encapsulation: automated re-use, active rule enforcement

Encapsulation of logic is conferred since any ORM updates automatically invoke the relevant logic. While encapsulation is traditionally implemented by Object technology, we achieve the same re-use using ORM Event Injection.

Note that re-use is automated. This is in sharp contrast to traditional approaches, which achieve re-use only by careful object design.

Note we are defining re-use in the broadest possible manner, where it applies regardless of transaction type (interactive or message), or (per automated dependency management) Use Case.

Declarative Logic Engine: Agility, TCO

The Logic Engine is driven by declarative rules, such as those noted in the example above. This reveals the fundamental advantage: you replace 500 lines of code with 5 rules. This can confer strategic business advantage.

Logic can be presented in multiple ways:

Files

Classes (using annotations, methods, DSL grammars, etc)

Database rows (enabling runtime logic changes)

Regardless of how the logic is specified, the responsibilities of the Logic Engine are described in the sub-sections below.

Dependency Management: ordering, optimizations

Declarative means the system assumes responsibility for Dependency Management

Change Detection

Change Propagation – with Forward Chaining

Ordering

Dependency Management applies both initially, and when the logic is changed. This brings automatin advantages to maintenance, not just development.

Automated Dependency Management takes re-use to an entirely new level, moving beyond re-use ot code to re-use of intent. So, for example, the rules shown at the top of this page are automatically re-used over multiple Use Cases: new order, delete order, re-assign order to different customer, etc.

Full Support for constrained multi-table derivations

The set of Business Logic constructs includes all the items noted above, particularly including optimized multi-table derivations.

ORM Integration: state transitions, pruning, cache utilization

ORM integration means that the Business Logic Engine has access to both the transaction changes, and the original data. This enables the engine to make the original data available (e.g., salary < @old(salary)) for state transition logic, and to perform intelligent pruning of rules whose dependent data has not changed.

Extensible (Action rules)

Ideal implementations provide extensibility at two levels:

Ad hoc: Developers will always need to provide procedural logic for the exceptions, with full access to existing libraries

Generic: Systems Developers should be enabled to provide new reusable rule types, enabling them to extend the services of the Business Logic Engine.

Optional Thin Service Layer (e.g., to publish APIs)

There is a fair bit of confusion in the industry regarding where to put business logic: in the domain objects, or in a service layer. Domain layer proponents regard Fat Services with Anemic Data Objects to be an anti-pattern, since they don't encapsulate business logic. Service layer proponents express concerns about how to embed multi-table logic - is the adjustment of a Customers' balance a responsibility of the Purchase Order, the Customer, or some mediator object?

Rich Active Declarative Domain Objects not only provide automation, they can dramatically simplify this quandary. Multi-table logic is automated - from the "what" based design objective - including its implementation design. That is what we mean by Rich Active Declarative Domain Objects - domain objects that actively enforce your logic by acting in concert with related objects on the basis of declarative rules.

So, for the vast majority of your transactions, Rich Active Declarative Domain Objects address the multi-table requirements (and complexity) traditionally associated with a Service Layer. You may still elect to provide a Service Layer, for example to publicize APIs, or to address transactions that are not fully REST-based. But even in these cases, you will find that the Service Layer will be thin: it needs only to choreograph updates to the Domain Layer, since it can be assured these will enforce the multi-table business logic.

Conclusion

This paper has presented a Rich Active Declarative Domain Model architecture for Transaction Business Logic, providing excellent architectural separation, active encapsulation of logic, enterprise-class performance, and strong business value in Agility and TCO. These concepts can be applied to a number of different platforms: Java vs. .NET, various client topologies, and message-based transactions.