The XYZ of the Clean Modules (2): the Domain Layer

Continuing with the Clean Modules discussion, let’s take a look on the Domain Layer package.

Domain Layer
Domain Layer Example

The domain layer package is the container of the business rules implementers. What does that really mean? Contrary to the traditional object-oriented opinion, the domain objects aren’t a mirrored programmatic version of the world. Object-orientation doesn’t intrinsically mean world-vision orientation or domain-orientation. The traditional OOAD manuals tried to explain the object-orientation by proposing the «method» to translate the application’s real world into software pieces. You must know: the classic example of Airplane real craft converted into an Airplane class, with operations like TakeOff() or Landing().

The most recent manifest of these trends is the Domain-Driven Development (DDD) method.[1] Those were good guidelines for the novices to the object-orientation thinking. But now, we know better. There are other more concrete solutions that avoid the anemic domain models syndrome.[2]

Here, I will follow the other way to interpret OOAD as a dance of modules or objects with well-defined roles, distributed in a definite architecture clean model based on the four discussed packages. That’s key of object-orientation: the system vision as a set of objects with cohesive tasks collaborating themselves to obtain a shared goal. The relevance of the domain is in the data the system moves around and, more important, the business rules implementation.

For the following lines the domain layer package core comprises of the following modules (no other types of modules excluded):

  • Domain Rulers or Business Objects Interfaces and Implementations

Modules that represents the business rules implementations. Most of the time they are objects with a limited state; the state it requires to apply the cohesive set of rules.

  • Collections of Domain Rulers and DTOs

Besides the single domain object, there are rules that must be applied to a collection of objects. The collections are a set of business rulers or simple DTO objects.

  • Factories

As we see in the use-case layer, the communication between layers requires the instantiation service of the concrete instances. The creation of those objects is the responsibility of factories.

  • DTOs Interfaces and Implementations

Although the main object type in this layer is the domain ruler, the DTOs are the technique to share the state of domain objects between the rulers themselves. Those objects are DTOs where the rulers apply the business rules.

Domain rulers’ interfaces and implementations

Domain Ruler
Domain Ruler Example

I will call Domain Ruler to any domain object that resembles a real-world concept or object (policy, profile…) and manifests in its operations the business rules associated with the concept. Those objects have little state data, injected by its constructors or by the operation arguments themselves.

It’s important to distinguish the categories of those operations to achieve highly cohesive modules and to comply with the SRP (Single-responsibility principle).

Let’s review such operation categories:

  • Calculators

One of the most the typical operations is the kind of calculators where we encapsulate formulas (not only arithmetic operations) that are rules of the business. Besides the Sum() or CalculatePrime() operations, there are many other concrete computations that deserves their own function. Although those operations are queries most of the time (returning the result of the calculation) it’s possible to find calculators as command operations, changing the internal state of the object (or a parament it receives) with the product of the operation.

  • Commands

Those operations are the simple DoX() function, which can receive a parameter as the target object to manipulate. The command changes the state of the domain ruler or the object it receives as a parameter.

  • Factory methods

The domain rulers also are the Creators (GRASP) of other child or contained domain objects. From the use-case layer, an interactor might use the domain ruler to gather another associated ruler which construction details or specifications are known by its parent or container object (it is its Information Expert). Only the root domain objects are created by factories.

  • Queries

Like calculators, the query operations are those of the type of GetStartDate() or GetBalance() functions. They don’t depend on any formula or calculation. They are single requests to the Information Expert (GRASP) that the domain ruler is, normally responded by a primitive value (dates, numbers, strings). In modern languages (C# or Java) the property methods or accessors of the domain object are concrete examples of those functions.

  • Validators

The validators are the operations that return a Boolean value after the assessment of the internal state of the domain ruler or a parameter it receives. They are the operations of IsX() or HasY() or simply ValidateZ().

The most important design and quality factor for a domain ruler is that every business rule of the domain should be traced to an operation of the domain ruler object that represents the domain concept.

Collections of Domain Rulers and DTOs

Collection Implementation Examples
Collection Implementation Examples

Many business rules must be applied to a collection or a set of the same objects. The domain-collection modules contain a list of the domain rulers or DTOs of the domain concepts. Internally, the implementation is frequently a hashtable or an equivalent data structure where all the rules are applied or where a filtered set are touched by the rule (GetHigherCreditScoreClients(), for example).

In the first example above, the collection is implemented as a generic or template class. This implementation is the easiest to use. Along with the mandatory operations to add or remove items to the collection, there are other operations that apply to the whole set (GetAll()).

In the second example, the collection is implemented as a Composite (GoF design pattern). This is a special case, because the concrete class BorrowerCollection might be used in three distinguished versions: as a single IBorrower, as a composite instance of IBorrower or as a CompositeBorrower. That last version is properly the collection object. Notice again, that the operations GetHigherCreditScoreClients() and ApplyDiscount() are applied globally to all qualified items.

Factories

Domain Layer Factories
Domain Layer Factory Examples

To instantiate a domain ruler, the interactor should use some building mechanism to avoid the dependency to implementations or concrete classes. Although some domain rulers are factories of other dependent rulers or DTOs, the root domain rulers require a factory to be instantiated. At least we should expect a factory for the domain rulers and their collections. When there are DTOs as part of the implementation of the domain layer, a factory mechanism for those objects should be implemented. The domain rulers concerned with the related data might implement also the factory methods for other domain rulers. Or, when the DTO is shared with multiple domain rulers, a dedicated factory might be used. In any case those factory mechanisms should hidden to the world outside this layer.

As in the example, the factory modules are concrete classes with static operations or queries that returns the data structure or the domain ruler. If the factory class doesn’t have any other than static methods is better to declare the factory as static too. Only when the concrete factory itself depends on the context, it’s valuable to define several constructors and oblige the callers to instantiate the factory class passing the context arguments. When this is the case, the factory hides the strategy to create the domain rulers. Although this should work, a better way is to implement the factory as a Strategy or use an Abstract Factory as we saw in the instantiation of the technical services.

DTOs interfaces and implementations

Data-Transfer-Object
Data-Transfer-Object Example

The use of DTOs under the domain layer has two common intentions:

  1. As a data communication mean between domain rulers.
  2. As a shared data store for several domain rulers.

The first case is a similar use of the DTOs in the use case layer, when the UI layer implements the DTO interface declared in the former layer. But in this occasion is the domain layer who implement the DTO itself, passing the DTO concrete instances as a parameter between domain objects.

This DTO use should exclude the outside accessibility of this module by the interactors to read the domain state data and, much less, to change its state independently of the domain ruler. The use case interactors will reach the domain layer only by its rulers (and, of course, by the factory to create them) calling them to apply the business rules over the primitive data from the UI or the data store the interactor pass as argument. This restriction is a big difference between the use of the domain objects as a DTOs and the domain objects as rulers of the business.

In the other use, the DTOs interfaces and implementations are defined in the domain layer itself and are used by the domain ruler objects as an external state module, upon the rule’s operations must be applied.

In the above example, both domain rulers, the IBorrower and the IMortgage, share the IBorrowerDTO as a single state. Also the IBorrower declares an interface to make the DTO with MakeBorrowerDTO() operation. This solution is common and an easy way to split the domain rulers cohesively by operations, but at the same time operating over the same domain data, avoiding unnecessary duplication. The context should decide if the DTO instance is shared between the two concrete instances of the rulers or if each one will have its own instance.

The next post will be dedicated to the User Interface (UI) layer package.


[1] Although the concept has already 15 years since the presentation of the book Domain-Driven Development by Eric Evans in 2004.

[2] It refers of the domain layers, full of DTOs and almost empty of operations or business rulers implementations.

Deja un comentario