Architecture patterns: domain model and friends

By Robert Raszczynski

Imagine a workshop of a racing team. The very first thing you will notice is that everything has its own place; spanners hanging on walls sorted by size, other tools placed in drawers, divided by their purpose, bolts and nuts placed in separate dividers and once again sorted by size. Everything is labeled, clean and in order.

Now imagine how would it be to work in such an environment, where every single item has its specific place. I reckon that after the first few days, you would be able to point to where everything is with your eyes closed! Such a degree of segregation and organisation makes our lives much easier and it's a pleasure to work with.

Architectural and design patterns help software architects to break systems into smaller, more maintainable sections organised by their functionality and usage. The biggest benefit of patterns is that someone has already solved problems we may face and by utilising patterns such as Transactional Script, Domain Model, or Data Mapper in your application it gives us, as developers, some good guidelines on how it should be designed.

Now take a a look at the code in your current project. Is it as well organised as that workshop? Is every class grouped by its purpose, in a nicely organised directory structure?

I suspect most of us will answer 'just about' to these questions. Most web applications are built of various subsystems, where each one is responsible for different things, such as reading from and writing to a database, retrieving data from a feed, and so on.

It's exactly the same as in the workshop; different parts of the system have different purposes hence they should be grouped in layers. Think about a cake and how it is layered, with sponge at the bottom, jam, and cream on the top.

Subsystems, with different responsibilities, are arranged in layers, where each one rests on the layer below it. Higher layers use APIs or services provided by the lower layer, however this lower layer is completely unaware of the fact that there is a layer above it, and it hides another lower placed layer from the layer above. Such architecture encourages separation and decreases coupling between different parts of a system.

There are three major layers in software architecture:

  • presentation: the layer contains all the logic responsible for handling interactions between the systems and a user, whether by using GUI or command line. The layer is responsible for displaying information to the user and collecting user input that is passed to domain logic.
  • domain: the domain logic layer is also referred as business logic. This layer is the workhorse of an application, it does all the work the application needs to do. The domain logic does calculations on users’ input or stored data. It passes the data to data source layer so it can be persist, furthermore, the layer validates input passed from the presentation layer.
  • data source: the layer’s purpose is mainly to communicate with other systems, such as databases, messaging systems and transaction managers. In web applications the major communication will be with different database systems.

Now that we know what types of layers we can find in software architecture, let's have a look at how we can organise domain logic and data sources layers.

Organising domain logic

There are three main design patterns that help us to organise domain logic; these are transaction scripttable module, and domain model. The following sections will take each of these patterns in turn and examine their suitability for application in various situations.

Transaction script

The transaction script pattern is the simplest solution for organising domain logic. There is a collection of scripts that are executed on a server, and every script handles a single procedure that corresponds to a specific request made by a user or another system. The procedure may involve taking user input from the presentation layer, validating it and then storing the data in a database or passing it to external services.

If you have been using PHP for a while you may remember the way websites were built in the late 90s and around the beginning of the millennium. You had a massive set of scripts, named as guestbook.php, add_entry.php, and view_entry.php, and each of them did one job. In our example scripts, we would present all guest book entries, add a new entry to the guest book, and display one single entry.

Transaction script is an ideal solution for systems with only a small amount of logic, because it is very easily understood by developers, it has little performance overhead, and it performs well with an elementary data source layer when put into action with patterns such as Row Data Gateway or Table Data Gateway.

However, you will discover transaction script's disadvantages as soon as the complexity of your domain logic increases. Quite often various transactions must perform similar actions, which leads to duplication of code across them. Some of the code can be refactored into separate classes or functions, however it gets harder and harder to keep the application's structure well-organised as the complexity grows.

Table Module

The next design pattern for organising domain logic is Table Module. It presents a database-centric approach, with all the business logic organised around database tables.

In Table Module a single class encapsulates all the domain logic for all records stored in a table or view. It's important to note that there is no translation of data between objects and rows, as it happens in Domain Model, hence implementation is relatively simple when compared to the Domain Model pattern (which is next on the list).

Table Module provides an interface to perform operations on all the data, so every time there is a need to work on a particular row, a method expects some kind of identity reference to be passed as one of the parameters.

As mentioned before, Table Module is much simpler than Domain Model, making it an ideal middle ground solution between Transaction Script and Domain Model. With Table Module you gain much more structure and lower level of code duplication comparing to Transaction Script, because the domain logic is organised around tables rather than procedures.

Table Module is an ideal candidate when an application makes heavy use of tabular data, which normally is a result of a SQL call, because it supports a Record Set pattern very well. However, if your application requires direct instance to instance relationship, then Domain Model will be much more appropriate choice as it better handles complicated domain logic.

Domain Model

The third and more complex solution for organising domain logic is a design pattern called Domain Model.

The pattern provides an object-oriented way of dealing with complicated logic. Instead of having one procedure that handles all business logic for a user action there are multiple objects and each of them handles a slice of domain logic that is relevant to it.

When you start building a model of your application's domain, which in fact is an abstract representation of reality, you will find that initial classes are organised essentially around the nouns in the domain.

For instance, when you build an e-commerce solution you will end up with classes such as CustomerBasket,Category and Product. These are the first things you will discuss when thinking about the domain.

In a nutshell, each of the classes will incorporate both the data, mainly represented in form of attributes, and all the behaviour that is related to that data. Additionally, Domain Model objects are usually in a one-to-one relationship with records in database tables.

Domain Model is built out of small, loose objects with elegant interfaces, that stay completely free of any external dependencies. Persisting data in Domain Model can be done by implementation of the Data Mapper pattern.

This is a great fit, but it will raise the level of complexity. It helps to deal with highly complex business logic in well organised manner, however it also requires an object-oriented paradigm shift from developers wanting to use it.

In fact the Domain Model pattern has a very steep learning curve and the process itself may be very frustrating and time consuming for people new to a rich object model. They can find themselves in a situation where they spend more time jumping from class to class looking for the behaviour they are interested in.

Taking a decision on whether to use Domain Model is highly dependent on the complexity of the business logic. Domain Model handles increasing complexity very well, however, it may take months before developers are fully confident working with the pattern.

Choosing the right solution for domain logic of your application

So far we have looked at three architecture design patterns that will help you to organise domain logic. However, the big question is which pattern you should pick for your application.

Unfortunately, there is no straight answer. The decision-making process will be mainly based on judging the complexity of the application's domain logic. The rule will be that you would pick Transaction Script for applications with a lower complexity level, and Table Module or Domain Model for applications where the business logic is more demanding.

In general you wouldn't use Domain Model pattern for systems with simple domain logic, because the cost of understanding it and complexity of data source exceeds the benefit of this pattern.

At the same time you need to consider changing requirements and how they may influence your application. Transaction Script and Table Module will definitely hit a wall much faster than Domain Model when the complexity of the business logic is increased by adding more features.

Below is a chart representing the relationship between complexity of domain logic and effort needed to introduce new features.

" "

Above: a graph showing the relationship between effort and complexity for different domain logic architecture design patterns. Source: Patterns of Enterprise Application Architecture by Martin Fowler.

Remember that even after you take your decision there may be new requirements that could change the application's complexity. In such circumstances it may be worthwhile to upgrade your chosen pattern, for instance refactor Transaction Script to Domain Model. However it won't make sense, in most situations, to go from Domain Model to Transaction Script.

Remember also that there is no rule to say you have to pick one pattern only; you can combine them in your design. It is common to delegate some business logic to Transaction Script and then use Domain Model or Table Module for other domain logic.

Organising data source layer

The main purpose of the data source layer is to communicate with other systems and to retrieve/persist data. When working with PHP you will find that the data source layer will usually connect to some sort of database. It is desirable, as a good coding practise, to separate database access and all SQL queries from domain logic and place it in separate classes.

In most cases classes are organised around the table structure of the database, with each class corresponding to a table. It’s worth mentioning that your choice of data source pattern will be affected by which design solution you chose for the domain logic.

Let's have a look at some options:

Gateway

When classes are based on table structure of the database, they act as a gateway to the table, encapsulating the SQL and database connection from any domain logic or presentation. There are two types of gateways: Row Data Gateway and Table Data Gateway.

Row Data Gateway

A class implementing Row Data Gateway pattern contains only data source logic, such as database access and simple conversion from database to in-memory types. An object looks exactly like a record in a table or a view, with each column mapped to a property. Row Data Gateway should never include any of the business logic. If that's the case then it really an Active Record pattern instead.

Table Data Gateway

An instance of Table Data Gateway handles and encapsulates all the SQL used to access and modify all the rows in a single table or a view. The class usually provides a simple interface that includes multiple methods for finding data based on a condition, as well as some insert, update and delete methods. Each of the methods will map input parameters into SQL queries and then execute them against database connection.

Table Data Gateway always creates a record set when it returns data retrieved from database, even when there was just a simple find by primary key method executed. That makes the pattern very well suited to work with the Table Module pattern.

Editor's Note: Keep reading for code examples including Table Data Gateway.

Data mapper

Very often, domain objects will have some attributes, for instance collections, that are not part of the database schema. This mismatch requires some sort of mechanism that helps structuring and transferring data between the domain logic and the database.

This is exactly what Data Mapper does. It is a layer that separates the business logic from the database by moving data from one to the other. A simple implementation of Data Mapper would just map table columns to equivalent attributes of a domain class.

The great benefit of using Data Mapper is the fact that domain objects don't need to know that there is a database present, making it easy to introduce changes to database structure or domain logic. Additionally, the entire Data Mapper layer can be easily substituted with mocks and stubs for testing purposes.

Data Mapper is mostly used together with Domain Model, and we will go on to look at a code example of using these together.

Patterns in practise

I would like to present an example showing how you can combine Data Logic and Data Source using design patterns mentioned in the article. I will mainly focus on Domain Model, Data Mapper and Table Data Gateway patterns. For the purpose of this example lets assume that we need to build ecommerce web application.

Domain Model

While analysing requirements you will notice that there are few nouns you talk about repeatedly. It’s your Customer, a Product the customer looks for, and a Basket where the product gets added to by the customer. These three are your domain specific classes.

Let's start writing some code:

namespace Store;
 
class Customer
{
    protected $id = 0;
    protected $name;
    protected $address;
}
namespace Store;
class Product
{
    protected $id = 0;
    protected $name;
    protected $description;
    protected $price;
}
namespace Store;
class Basket
{
    protected $id = 0;
 
    /**
     * @var ArrayObject collection of products added to basket
     */
    protected $products;
    protected $customer
}

Each of the above classes will include some business logic specific to its domain. The Customer class may have methods for managing shipping address and financial details, the Product class may have methods that calculate taxes or discounts, and so on. The key is to keep these classes small and compact.

Data Mapper

As mentioned before, Data Mapper is used to move data between layers. For the purpose of this example we will have three database tables: customers, products, and baskets.

Based on our domain model we know that we need to feed data into three objects, so the mappers we create will map data from the customers table to the StoreCustomer class, from products to StoreProduct, and lastly, from baskets to StoreBasket. Below is a starting point for StoreDataMapperCustomer data mapper; all the other classes will look pretty similar at this early stage.

namespace StoreDataMapper;
 
class Customer
{
    /**
     * @var ArrayObject
     */
    private $identityMap;
 
    /**
     * @var StoreDataGateway
     */
    private $gateway;
 
    /**
     * By using dependency injection it is much easier to unit test your mapper
     *
     * @param StoreDataGateway $gateway
     * @param ArrayObject $identityMap
     * @return void
     */
    public function __construct(StoreDataGateway $gateway, ArrayObject $identityMap)
    {
        $this->gateway = $gateway;
        $this->identityMap = $identityMap;
    }
}

As you can see, constructor takes two parameters, first is an instance of StoreDataGateway that implements the Table Data Gatewaypattern. I decided to use the pattern here because it provides additional separation, however it is entirely up to you how to handle SQL. The second parameter is instance of ArrayObject that acts as an Identity Map, which is used by the mapper’s find methods to ensure that every object is loaded only once.

Let's have a look how it works:

namespace StoreDataMapper;
 
class Customer
{
    ...
 
    /**
     * finds customer’s row by primary key
     *
     * @param int $id
     * @return StoreCustomer
     */
    public function find($id)
    {
        if ($this->identityMap->offsetExists($id)) {
            return $this->identityMap->offsetGet($id);
        }
        // By using Table Data Gateway we hide away all SQL
        $data = $this->gateway->find($id)->getFirstItem();
 
        $customer = new StoreCustomer;
 
        // Simple mapping of data from Db to Domain Model
        $customer->setId($id);
        $customer->setName($data['name']);
        $customer->setAddress($data['address']);
 
        // Store customer object in identity map
        $this->identityMap->offsetSet($id, $customer);
 
        // And return Customer object
        return $customer;
    }
}

The first thing the find method does is a check whether data has been already loaded from database. If not, the mapper gets the data by using Table Data Gateway and maps it to the domain object.

In my example it's a very simple mapping which links column to attribute, however you can implement more sophisticated, metadata based mapping should you need it.

Let's have a look at how data is persisted when using Domain Model and Data Mapper.

namespace StoreDataMapper;
 
class Customer
{
    ...
 
    /**
     * save
     *
     * @param StoreCustomer $customer
     * @return void
     */
    public function save(StoreCustomer $customer)
    {
        $data = array();
 
        // Simple mapping of domain model to table columns
        $data['name'] = $customer->getName();
        $data['address'] = $customer->getAddress();
 
        if ($customer->getId() > 0) {
            $this->gateway->update($data, $customer->getId());
        } else {
            $this->gateway->insert($data);
        }
    }
}

Data Mapper has a save() method that expects an object of the domain model, in this case StoreCustomer. Once again the mapper prepares data and passes it to the database using a Table Data Gateway pattern. The code here is for Customer only but the concept applies to all domain model objects.

I hope that above example will help you analyse the requirements of your next project, and if you decide that these patterns are a good fit, that you will be able to introduce Domain Model, Data Mapper and Table Data Gateway easily. Are you using these patterns or planning on using them for the next project?