Tag: OOP

In the context of Object-Oriented software engineering, a “design pattern” describes a frequently recurring and effective solution to a commonly encountered problem. The utility of formalising and sharing design patterns is to provide a set of “battle-tested” designs as idiomatic solutions for classes of problems, and to increase the shared vocabulary among software engineers working on collaboratively developed software. The term was coined in the seminal book by Gamma et al [5], which named and described a set of common design patterns. The lexicon of design patterns grew from the initial set specified in the book, as the notion gained in popularity [6], [17].

Following the increasing popularity of design patterns as a concept, the idea of “design anti-patterns” entered popular discourse [7][8]. As implied by the name, an anti-pattern is the opposite of a pattern; while it too describes a recurring solution to a commonly encountered problem, the solution is typically dysfunctional or ineffective, and has negative impacts on the “health” of the software (in terms of maintainability, extensibility, robustness, etc.). Anti-patterns serve a similar purpose to patterns; the description of the anti-pattern might illustrate a typical implementation of the anti-pattern, explain the context it generally occurs in, and show how the implementation results in problems for the software.

A potential problem with the concept of a design anti-pattern is that it might discourage critical thought about the applicability of the pattern. A design that may be inappropriate in some contexts may be a sensible decision in others; a solution might be discarded after being recognised as an anti-pattern, even though it would be a good fit for the problem at hand.

I contend that such an anti-pattern is the Anaemic Domain Model (ADM), described by Martin Fowler [1] and Eric Evans [2]. The ADM is considered by these authors as a failure to model a solution in an Object-Oriented manner, instead relying on a procedural design to express business logic. This approach is contrasted with the Rich Domain Model (RDM) [1], [20], in which classes representing domain entities encapsulate all business logic and data. While the ADM may certainly be a poor design choice in some systems, it is not obvious that this is the case for all systems. In this blog post I will consider the arguments against the ADM, and contend that in some scenarios, the ADM appears be an reasonable choice of design, in terms of adherence to the SOLID principles of Object-Oriented design, established by Robert Martin [3], [4]. The SOLID principles are guidelines which seek to balance implementation simplicity, scalability, and robustness. Specifically, by contrasting an ADM design with an RDM design for a hypothetical problem, I will attempt to show that ADM is a better fit for the SOLID principles than the RDM solution. By doing so, I hope to demonstrate a contradiction in the received wisdom with regard to this anti-pattern, and consequently suggest that implementing an ADM is a viable architectural decision.

Why is the Anaemic Domain model considered by some to be an Anti-Pattern?

Fowler [1] and Evans [2] describe an ADM as consisting of a set of behaviour-free classes containing business data required to model the domain. These classes typically contain little or no validation of the data as conforming to business rules; instead, business logic is implemented by a domain service layer. The domain service layer consists of a set of types and functions which process the domain models as dictated by business rules. The argument against this approach is that the data and methods are divorced, violating a fundamental principle of Object-Oriented design by removing the capability of the domain model to enforce its own invariants. In contrast, while an RDM consists of the same set of types containing necessary business data, the domain logic is also entirely resident on these domain entities, expressed as methods. The RDM then aligns well with the related concepts of encapsulation and information hiding; as Michael L. Scott states in [9], “Encapsulation mechanisms enable the programmer to group data and the subroutines that operate on them together in one place, and to hide irrelevant details from the users of an abstraction”.

In an RDM, the domain service layer is either extremely thin or non-existent [20], and all domain rules are implemented via domain models. The contention is that domain entities in a RDM are then entirely capable of enforcing their invariants, and therefore the system is sound from an Object-Oriented design perspective.

However, the capability of a domain entity to enforce local data constraints is only a single property in a set of desirable qualities in a system; while the ADM sacrifices this ability at the granularity of the individual domain entities, it does so in exchange for greater potential flexibility and maintainability of the overall implementation by allowing the domain logic to be implemented in dedicated classes (and exposed via interfaces). These benefits are particularly significant in statically typed languages such as Java and C# (where class behaviour cannot simply be modified at run-time) for improving the testability of the system by introducing “seams” [10], [11] to remove inappropriate coupling.

A Simple Example

Consider the back end of an e-commerce website in which a customer may purchase items, and offer items for sale to other customers across the globe. Purchasing an item reduces the purchaser’s funds. Consider the implementation of how a customer places a purchase order for an item. The domain rules state that the customer can only place an order if they have enough funds, and the item must be available in region for that customer. In an RDM, a Customer class would represent the domain entity for the customer; it would encapsulate all the attributes for the customer, and present a method such as PurchaseItem(Item item). Like Customer, Item and Order are domain models representing purchasable items and customer orders for items respectively. The implementation of the Customer (in pseudo-C#) might be something like;

The domain entities here implement the Active Record pattern [17], exposing Create/Read/Update/Delete methods (from a framework/base class) to modify records in the persistence layer (e.g., a database). It can be assumed that the PurchaseItem method is invoked in the context of some externally managed persistence layer transaction (perhaps initiated by the HTTP request handler/controller, which has extracted a Customer and an Item from the request data). The role of the Customer domain entity in this RDM is then to model the business data, implement the business logic operating on that data, construct Order objects for purchases, and interface with the persistence layer via the Active Record methods; the model is Croesus-like in its richness, even in this trivial use case.

The following example demonstrates how the same functionality might be expressed using an ADM, in the same hypothetical context;

At first glance, the ADM is arguably worse than the RDM; there are more classes involved, and the logic is spread out over two domain services (IPurchaseService and IItemPurchasableService) and a set of application services (IOrderFactory, ICustomerRepository and IOrderRepository) rather than resident in the domain model. The domain model classes no longer have behaviour, but instead just model the business data and allow unconstrained mutation (and therefore lose the ability to enforce their invariants!). Given these apparent weaknesses, how can this architecture possibly be better than the altogether more Object-Orientation compliant RDM?

The reason that the Anaemic Domain Model is the superior choice for this use case follows from consideration of the SOLID principles, and their application to both of the architectures [12]. The ‘S’ refers to the Single Responsibility Principle [13], which suggests that a class should do one thing, and do it well, i.e., a class should implement a single abstraction. The ‘O’ refers to the Open/Closed Principle [14], a similar but subtly different notion that a class should be “open for extension, but closed for modification”; this means that, in so far as possible, classes should be written such that their implementation will not have to change, and that the impact of changes is minimised.

Superficially, the Customer class in the RDM appears to represent the single abstraction of a customer in the domain, but in reality this class is responsible for many things. The customer class models the business data and the business logic as a single abstraction, even though the logic tends to change with higher frequency that the data. The customer also constructs and initialises Order objects as a purchase is made, and contains the domain logic to determine if a customer can make an order. By providing CRUD operations through a base class, the customer domain entity is also bound to the persistence model supported by this base implementation. By enumerating these responsibilities it is clear that even in this trivial example, the RDM Customer entity exhibits a poor separation of concerns.

The ADM, on the other hand, decomposes responsibilities such that each component presents a single abstraction. The domain data is modelled in “plain-old” language data structures [18], while the domain rules and infrastructural concerns (such as persistence and object construction) are encapsulated in their own services (and presented via abstract interfaces). As a consequence, coupling is reduced.

Contrasting the flexibility of the RDM and ADM architectures

Consider scenarios in which the RDM Customer class would have to be modified; a new field might be introduced (or the type of an existing field may need changed), or the Order constructor may require an additional argument, or the domain logic for purchasing an item may become more complex, or an alternative underlying persistence mechanism might be required which is unsupported by the hypothetical DomainEntity base class.

Alternatively, consider scenarios in which the ADM types must change. The domain entities which are responsible for modelling the business data will only need modified in response to a requirements change for the business data. If the domain rules determining if an item is purchasable become more complex (e.g., an item is specified to only be sold to a customer above a certain “trust rating” threshold), only the implementation of IsItemPurchasableService must change, while in the RDM the Customer class would require changing to reflect this complexity. Should the ADM persistence requirements change, different implementations of the repository [17], [19] interfaces can be provided to the PurchaseService by the higher-level application services without requiring any changes whereas in the RDM, a base class change would impact all derived domain entities. Should the Order constructor require another argument, the IOrderFactory [5] implementation may be able to accommodate this change without any impact on the PurchaseService. In the ADM each class has a single responsibility and will only require modification if the specific domain rules (or infrastructural requirements) which concern the class are changed.

Now consider a new business requirement was added to support refunds for purchases with which a customer is unsatisfied. In the RDM, this might be implemented by adding a RefundItem method to the Customer domain entity, given the simplistic argument that domain logic related to the Customer belongs as a member function of the Customer domain entity. However, refunds are largely unrelated to purchases, for which the Customer domain entity is already responsible, further mixing the concerns of this type. It can be observed that in an RDM, domain entity classes can accumulate loosely related business logic, and grow in complexity. In an ADM, the refund mechanism could be implemented by introducing a RefundService class, solely concerned with the domain logic for processing refunds. This class can depend on the narrow set of abstractions (i.e., interfaces of other domain and infrastructural services) required to implement its single concern. The new RefundService can be invoked at high level (in response to some refund request), and this new domain behaviour has been implemented without impacting any of the existing functionality.

In the example, the ADM solves the problem of bundling unrelated concerns into the same module identified in the RDM by taking advantage of the ‘I’ and ‘D’ in SOLID, namely the Interface Segregation Principle [15] and the Dependency Inversion Principle [16]. These principles state that an interface should present a cohesive set of methods, and that these interfaces should be used to compose the application (i.e., the domain service layer in the ADM). The interface segregation principle tends to result in small narrowly focussed interfaces such as our IItemShippingRegionService and IIsItemPurchasableService, as well as abstract repository interfaces; the dependency inversion principle compels us to depend on these interfaces, to decouple the implementation of a service from the details of the implementation of another.

The Anaemic Domain Model better supports Automated Testing

As well as more flexible and malleable application composition, adoption of these principles allows the ADM to extract the indirect benefits over RDM of simpler automated testing; this is because highly cohesive, loosely coupled components which communicate via abstract interfaces and are composed via dependency injection allow for trivial mocking of dependencies. This means that in the ADM it is simple to construct a scenario in an automated test which might be more complicated to construct in an RDM, so the maintainability of the automated tests is improved; the effect of this is that automated testing has a lower cost, so developers will be more inclined to create and maintain tests. To illustrate this, consider the example above, such that unit tests are to be written for the IsItemPurchasable.

The (current) domain rules for an item being purchasable are that the customer has sufficient funds, and is in a region that the item ships to. Consider writing a test that checks that when a customer has sufficient funds but is not in a shipping region for the item, the item is not purchasable. In the RDM this test might be written by constructing a Customer and an Item, configuring the customer to have more funds than the item costs, and configuring the customer region to be outside the regions the item ships to, and asserting that the return value of customer.IsItemPurchasable(item) is false. However, the IsItemPurchasable method depends on the implementation details of the ShipsToRegion method of the Item domain entity. A change to the domain logic in Item might change the result of the test. This is undesirable, as the test should be exclusively testing the logic of the customer’s IsItemPurchasable method; a separate test should cover the specifics of the item’s ShipsToRegion method. As domain logic is expressed in the domain entity, and the concrete domain entity exposes the interface to the domain logic, implementations are tightly coupled such that the effects of changes cascade, which in turn makes automated tests brittle.

The ADM, on the other hand, expresses the IsItemPurchasable domain logic on a dedicated service, which depends on an abstract interface (the ShipsToRegion method of IItemShippingRegionService). A stubbed, mock implementation of IItemShippingRegionService can be provided for this test, which simply always returns false in the ShipsToRegion method. By decoupling the implementations of the domain logic, each module is isolated from the others and is insulated from changes in the implementation of other modules. The practical benefits of this are that a logic change will likely only result in the breakage of tests which were explicitly asserting on the behaviour which has changed, which can be used to validate expectations about the code.

Refactoring the RDM to apply SOLID tends to result in an ADM

A proponent of the RDM architecture might claim that the hypothetical example provided is not representative of an true RDM. It might be suggested that a well implemented Rich Domain Model would not mix persistence concerns with the domain entity, instead using Data Transfer Objects (DTO’s) [18, 17] to interface with the persistence layer. The inclusion of directly invoking the Order constructor might be viewed as constructing a straw man to attack; of course no domain model implementation would bind itself directly to the constructor of another object, using a factory is just common sense [5]! However, this appears to be an argument for applying the SOLID principles to the application level infrastructural services, and disregarding the SOLID principles for domain design. As the hypothetical RDM is refactored to apply the SOLID principles, more granular domain entities could be broken out; the Customer domain entity might be split into CustomerPurchase and CustomerRefund domain models. However, these new domain models may still depend on atomic domain rules which may change independently without otherwise affecting the domain entity, and might be depended on by multiple domain entities; to avoid duplication and coupling, these domain rules could then be further factored out into their own modules and accessed via an abstract interface. The result is that as the hypothetical RDM is refactored to apply the SOLID principles, the architecture tends towards the ADM!

Conclusion

By exploring the implementation of a straightforward example, we have observed that an Anaemic Domain Model better adheres to the SOLID principles than a Rich Domain Model. The benefits of adherence to the SOLID principles in the context of domain design were considered, in terms of both loose coupling and high cohesion and resulting increased flexibility of the architecture; evidence of this flexibility was that testability was improved by being able to trivially provide stubbed test implementations of dependencies. By considering a how the benefits of adherence to the SOLID principles might be gained in the RDM, the refactoring tended to result in an architecture resembling an ADM. If adherence to the SOLID principles is a property of well engineered Object-Oriented programs, and an ADM adheres better to these principles than an RDM, the ADM cannot be an anti-pattern, and should be considered a viable choice of architecture for domain modelling.