Blog‎ > ‎

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.


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 addresses only save (transaction) processing.  It complements other familiar forms of business logic such as presentation logic (data retrieval / update, screen flow), Business Process Flow, and Decision Logic commonly associated with Rete engines.

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( where paid = false)

3. = 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 typically chained (dependencies require detection and properly ordered change propagation), and multi-table (so are performance-sensitive).  In our example, the Customer.balance is multi-table (it sums that customer's unpaid, and is chained (since the 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

Architectural separation means adding additional services so they interoperate with, and minimize impacts on, existing layers.  For example, in a Java stack:

  • 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.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 depends on
  • Lineitem.amount
Not done yet.  Lineitem.amount is dependent upon
  • Lineitem.qtyOrdered
  • Lineitem.part (the foreign key)
  • Part.price
So, to reduce code volume / complexity, our approach had better provide automated dependency management.  This directly affects Agility (how long it takes to build the code) and TCO (since maintenance costs are substantial, and dominated by dependency-based ordering).

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.

  • Poor separation
Rete engine logic must be explicitly called, which means extra code is required for frameworks that otherwise automate standard SQL/ORM persistence.

  • Inadequate Expressive Power
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.


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)

  • Poor Separation: substantial manual code is required to invoke the logic

  • 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


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.
  • Poor Separation: substantial manual code is required to invoke the service logic, compromising framework SQL/ORMs automation
  • 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
  • Active, automated enforcement is not provided since direct calls to ORM updates (e.g., by frameworks) bypass 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:
  • No Performance automation: pruning, adjustment etc are still manual coding (and re-coding) exercises
  • 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.


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.