Multi-tier Domain Layer (Anti-Pattern)

Its not uncommon to separate out presentation and services in separate tiers even if in near future there might be only one application accessing the service. When services and presentation application are built at the same time, the services pretty much evolve with it. This presents a tempting architectural option of using domain objects for persistence, data transfer objects between tiers and model for presentation. The reason being efficiency as not only one doesn't need three parallel hierarchy of classes one also doesn't need to do to-and-fro mapping between them. Most often this choice is made with only the knowledge of benefits and not the downsides.

Let us assume a very simple banking application, domain of which has Customer, Account and Transaction. One customer can have multiple accounts and the customer performs multiple transactions using his account. The domain model for which might look like:

class Customer {

String customerId

Name name

    List<Account> accounts
    Address address
    Country country
}

class Account {

    Money balance
    List<Transaction> transactions
    Date openedOn
    AccountType accountType

Personnel bankPersonnel

}

class Transaction {

DateTime date

    Money amount
    TransactionType type
}

Coupling between serialization and persistence

We have three functionality involving customer naming customer profile, accounts summary and transactions. In each of these I want to access different information as laid out in following data structures.

CustomerProfile {

    Name name
    int age
}

AccountsSummary {

    Name name
    List<AccountSummary> accounts
}

AccountSummary {

    string number
    decimal balance
}

CustomerTransactions {

List<AccountTransaction> accountTransactions

}

AccountTransaction {

    decimal balance
    List<Transaction> transactions
}

Transaction {

    long transactionId
    DateTime transactionDate
    decimal amount
    TransactionType type
}

Since we are not using DTO and want to avoid mapping of data we would implement this by creating a single service operation which provides the super set of all of above. The client of the service would call it every time and picks up on its end whatever it needs. The code below is lot less work than tedious works of defining data transfer objects, mapping domain objects to it and defining separate methods for each operation.

class CustomerService {
    CustomerRepository customerRepository
    public CustomerService(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository
    }
    public Customer getCustomer(String customerId) {
        try {
            Customer customer = customerRepository.load(customerId)
            return customer
        } finally {
            customerRepository.dispose()
        }
    }
}

Clearly this is sub-optimal from the performance perspective as all the data would be loaded from database and serialized to the client.

At first glance it might seem like that the service is returning all the transactions, which can be avoided by loading only the last 3 months transaction. Even if one might be able to get away with that what about fields like country, personnel on the customer object. They would be full fledged objects, having the details which completely irrelevant to the three operations we have seen.

There is another issue if one is using something like hibernate and we choose to use lazy-loading and n+1 query optimization. If we assume that some associations are lazy, then in CustomerService.getCustomer method, the customer object graph is only partially loaded. In other words accounts, transactions etc would get loaded only when accessed. This access happens when the object needs to be serialized to xml/json but by then session is already disposed. Your web-service framework may or not may not provide a hook/mechanism to solve this, hence its important to understand this.

Anaemic Services

Even if the domain layer encapsulates all the business logic (as you may like it to), the fact that it is ubiquitous presents a choice. The choice of whether the presentation layer should invoke these or the service layer. When architecture doesn't enforce this one would end up doing it incorrectly. Lets see an example of a opening an account.

class CustomerController {
    CustomerService customerService
    void openAccount(UserSession userSession, OpenAccountRequest openAccountRequest) {
        Customer customer = customerService.getCustomer(userSession.customerId())
        Account newAccount = customer.newAccount(openAccountRequest.accountType(), openAccountRequest.openingBalance())
        customerService.save(customer)
    }
}
class Customer {
    public Account newAccount(AccountType accountType, Money openingBalance) {
        Account account = new Account(accountType)
        account.debit(openingBalance)
        this.accounts.add(account)
    }
}
class Account {
    public void debit(Money amount) {
        this.transactions.add(Transaction.createDebit(Date.now(), amount))
    }
}

Via this very simple example, we can see that CustomerService is just acting as a remoting layer over database. This defeats the purpose of having a service layer, since if another client (like presentation application) wants to open an account would need to repeat the steps performed by CustomerController. In this case the CustomerController should have delegated the entire openAccount responsibility to the customer service. In a scenario like ours where there is a single client, this is not very obvious. But this is also aggravated by sharing of domain model between service and presentation layer.

Compromised domain and presentation model

When the domain layer is reused in presentation and service layer the domain model needs to be understood by multiple technology/frameworks. A domain model which has to cater to all of these (and sometimes incompatible) requirements might not be very effective after all. Lets look at some of these.

  • Certain serialization mechanism expect the objects to have certain access level on fields, methods and constructor. These may demand your domain objects to have more liberal access levels than you might like. e.g. I might not like my domain classes to have an public empty constructor or have getter/setters for every field.
  • Some data binding frameworks may require one to define some methods in you class so that they can be displayed in user interface. In some rich client frameworks (WPF, Silverlight) the best way to utilize power of the data-binding is to raise events when a field value is changed. When data binding to table/grid, one may need to flatten the object hierarchy. When domain model can bound to the user interface for editing, it implies that the object can be in invalid state and each field can be set independently. This gets complicated when one have to start worrying of order in which fields are populated in your domain object.
  • The object relational mapping might use some data structures which might not be compatible with the serialization. The serializer might not take care of object references in the same way as required by the OR mapping framework. (e.g. what happens after deserialization when two accounts have the same BankPersonnel, do we get the same object or different objects)

The list might differ for you but it is not uncommon to face these difficulties in a complex system implementation. Infact these can be quite nightmarish, based on my experience.