Business logic is the most important part of software. It’s the reason the software is being implemented in the first place. A system’s user interface can be sexy, and its database can be blazingly fast and scalable. Still, if the software is not useful for the business, it’s nothing but a technology demo.
In the first part of this book, we saw how important it is for all stakeholders to communicate in the ubiquitous language and to have a shared understanding of the problem domain. The system’s source code should also “speak” the ubiquitous language and be designed according to the business domain’s shared model. But how do we implement it in such a way?
As we saw in Chapter 2, not all business subdomains are created equal. Different subdomains have different levels of strategic importance and complexity. This chapter begins our exploration of the different ways of modeling and implementing business logic code. We will start with two patterns suited for rather simple business logic: transaction script and active record.
“Organizes business logic by procedures where each procedure handles a single request from the presentation.”
Patterns of Enterprise Application Architecture1
A system’s public interface can be seen as a collection of business transactions that the consumers can execute as shown in Figure 5-1. These transactions can retrieve information managed by the system, modify it, or both. The transaction script pattern organizes the system’s business logic by procedures, where each procedure implements a request from the system’s public interface. Effectively, the system’s public operations are used as encapsulation boundaries.
Each procedure is implemented as a simple, straightforward procedural script. It can use a thin abstraction layer for integrating with storage mechanisms, but it is also free to access the databases directly.
The only requirement procedures have to fulfill is transactional behavior. Each operation should either succeed or fail but can never be in an in-between state. If a process fails for some reason, the system should remain consistent—hence the pattern’s name, transaction script.
Here is an example of a transaction script that converts batches of JSON files into XML files:
DB.StartTransaction(); var job = DB.LoadNextJob(); var json = LoadFile(source); var xml = ConvertJsonToXml(json); WriteFile(destination, xml.ToString(); DB.MarkJobAsCompleted(job); DB.Commit()
The transaction script pattern is well adapted to the most straightforward problem domains, where the business logic resembles simple procedural operations. For example, in extract-transform-load (ETL) operations, each operation extracts data from a source, applies transformation logic to convert it into another form and loads the result into the destination store. This process is shown in Figure 5-2.
The transaction script pattern naturally fits supporting subdomains where, by definition, the business logic is simple — supporting subdomains. It can also be used as an adapter for integrations with external systems — generic subdomains, or as a part of an anticorruption layer (more on that in Chapter 9).
The main advantage of the transaction script pattern is its simplicity. It introduces minimum abstractions and minimizes the overhead both in runtime performance and in understanding the business logic. That said, this simplicity is also the pattern’s disadvantage. The more complex the business logic gets, the more it’s prone to duplicate business logic across transactions, and consequently, inconsistent behavior — when the duplicated code goes out of sync. As a result, transactions should never be used for core subdomains, as this pattern won’t cope with the high complexity of a core subdomain’s business logic.
The simplicity of the transaction script earned it a dubious reputation. Sometimes, the pattern is even treated as an anti-pattern. After all, if a complex business logic is implemented as a transaction script, sooner than later it’s going to turn into an unmaintainable big ball of mud. It should be noted, however, despite the simplicity, the transaction script pattern is ubiquitous in software development. The next three business logic implementation patterns that we are going to discuss in this and the next two chapters, all include a transaction script -- either implementing the business logic, or as orchestrator for other components carrying out the business logic.
“An object that wraps a row in a database table or view encapsulates the database access and adds domain logic on that data.”
Patterns of Enterprise Application Architecture
Like the transaction script pattern, active record supports cases where the business logic is simple. Here, however, the business logic may operate on more complex data structures. For example, instead of flat records, we can have more complicated object trees and hierarchies, as shown in Figure 5-3.
Operating on such data structures via a simple transaction script would result in lots of repetitive code. The mapping of the data to an in-memory representation would be duplicated all over.
Consequently, this pattern uses dedicated objects to represent complicated data structures: active records. Apart from the data structure, these objects also implement data access methods for creating, reading, updating, and deleting records—the so-called CRUD operations. As a result, the active record objects are coupled to an object-relational mapping (ORM) or some other data access framework. The pattern’s name is derived from the fact that each data structure is “active”; i.e., it implements data access logic.
As in the previous pattern, the system’s business logic is organized in a transaction script. The difference between the two patterns is that in this case, instead of accessing the database directly, the transaction script manipulates active record objects. When it completes, the operation has to either complete or fail as an atomic transaction:
public class CreateUser { public void Execute(userDetails) { try { DB.StartTransaction(); var user = new User(); user.Name = userDetails.Name; user.Email = userDetails.Email; user.Save(); DB.Commit(); } catch { DB.Rollback(); throw; } } }
The pattern’s goal is to encapsulate the complexity of mapping the in-memory object to the database’s schema. In addition to the persistence responsibility, the active record objects can contain business logic, for example, validating new values assigned to the fields or even implementing business-related procedures that manipulate an object’s data. That said, the distinctive feature of an active record object is the separation of data structures and behavior (business logic). Usually, active records’ fields have public getters and setters that allow external procedures to modify the active record’s state.
Since, essentially, an active record is a transaction script that optimizes access to databases, this pattern can only support relatively simple business logic such as CRUD operations which at most validate the user’s input.
Accordingly, as in the case of transaction script, the active record pattern lends itself to supporting subdomains, integration of external solutions for generic subdomains, or model transformation tasks. The difference between the patterns is that active record addresses the complexity of mapping complicated data structures to a database’s schema.
The active record pattern is also known as anemic domain model antipattern. Or, in other words, an improperly designed domain model. I prefer to restrain from the negative connotation of the words “anemic” and “antipattern.” This pattern is a tool. Like any tool, it can solve problems, but it can potentially introduce more harm than good when applied in a wrong context. There is nothing wrong with using active records when the business logic is simple. Furthermore, using a more elaborate pattern when implementing simple business logic will also result in harm by introducing accidental complexity. In the next chapter, you will learn what a domain model is and how it differs from active record.
Note: It’s important to stress that in this context “Active Record” relates to the design pattern and not the “Active Record” framework. The pattern name was coined in Martin Fowler’s book “Patterns of Enterprise Applications Architecture”. The framework came later as one way to implement the pattern. In our context, we are talking about the design pattern and the concepts behind it, not a specific implementation.
In this chapter, we covered four different patterns for implementing a system’s business logic:
This pattern organizes the system’s operations as simple, straightforward procedural scripts. The procedures ensure that each operation is transactional—either it succeeds or it fails. The transaction script pattern lends itself to supporting subdomains, with business logic resembling simple ETL-like operations.
When the business logic is simple but operates on complicated data structures, you can implement those data structures as active records. An active record object is a data structure that provides simple CRUD data access methods.
1 Fowler, M. (2002). Patterns of Enterprise Application Architecture. Boston, MA: Addison-Wesley Educational.
3.12.36.147