Domain Model and Service Layer patterns in P of EAA
Asked Answered
I

2

15

In Patterns of Enterprise Application Architecture, Martin Fowler talks about two patterns for organizing Domain Logic: Domain Model and Service Layer. The Domain Model pattern is the "pure OOP" approach, where models (those objects that are probably being looked up from the database using an ORM) contain business logic (albeit, probably only delegating to the logic in another class).

A sample Domain Model

The Service Layer pattern is like the Domain Model pattern, but with a thin layer in front of it containing the business operations that can be performed. In MVC, the controller would mostly interact with the Service Layer. I believe that most well-designed MVC web applications use this pattern.

A sample Service Layer

Now, to my question. Martin suggests that the Domain Model approach is the more object-oriented approach, and is therefore better. In my experience, it is very difficult (see: impossible) to implement in practice.

Take the example given in the first diagram above. There are two "entities" Contract and Product. These are persisted to the database with a mapper. In the example, there is a RecognitionStrategy. Martin puts the methods for delegating to this strategy, which contains the actual business logic, in the entities themselves; the client performs this calculation with contract.calculateRecognitions or contract.recognizedRevenue(someDate). When implementing similar designs, I usually write the client interface as strategy.calculateRecognitions(contract) and strategy.recognizedRevenue(contract, someDate). This makes the service layer necessary for coordinating the strategy and contract. The concrete strategy used is injected into the service.

Martin's approach is definitely more appealing from a design perspective, but the work around the setup is much more difficult:

  1. Passing in the strategy when instantiating a Product is a pain. You need to create Products via a factory curried with the concrete service to use, which will in turn pass it into the entity when creating it.
  2. Less fine-grained control over database access. Depending on the ORM settings, the Contract delegating to Product may perform a query per Product. Greedily loading Products in the mapper (or ORM) may be overzealous when we load a Contract but don't intend to call contract.calculateRecognitions(). My approach gives us finer-grained control, because the service has knowledge of the database abstraction layer, where as the entities shouldn't.

I'm sure there are more pain points in practice that I haven't enumerated here.

What concrete advantages are there in Martin's approach that might convince me to use a pure Data Model pattern?

Intine answered 2/3, 2014 at 19:50 Comment(0)
S
5

Regarding your first point, you should use dependency injection when instantiating the Product object. Object graph construction is a full flagged responsibility and should not be mixed with your business logic (single responsibility principle).

Regarding the second point, your vendor particularities should be hidden behind you data access layer and your DAO or Repository should return the objects according to your needs.

An alternative for your concern over greedily loading Product s (on a situation the relationship is one to many) is to have the Product DAO injected into the Contract object. With that approach you could get the Product s related to the contract when required (probably on a getter that could also be used internally).

Of course a perfect solution does not exist and there will always be trade offs. Your job as an architect to evaluate the approach that better fits you application.

On my personal experience I noticed that relying too much on service classes tends to generate gigantic classes that don't have a well defined responsibility and are usually too difficult to test.

So benefits of using the Domain Model approach are clear separation of concerns and increased testability.

Finally you don't need to use a "pure" Domain Model approach. The domain model and service layer are expected to be used together. The domain model entities cover behaviors that fall within their boundaries and the service layer cover logic doesn't belong in any domain entity.

Some additional reference you may find interesting

Domain Driven Design and Development In Practice - An interesting article on DDD

Dependency Injection, Design patterns using Spring and Guice - Great book on dependency injection

Regards,

Emanuel Luiz Lariguet Beltrame

Sumo answered 23/4, 2015 at 0:11 Comment(0)
M
2

Domain model represent an object as well as it's behavior better than anemic one. Because the behavior attached to it. Basic example is, a dog can bark, breathe and eat. In Service layer, the model are enhanced with BarkHandler and BreatheHandler.

Domain model approach are natively supported by UML design pattern. My previous answer here. For anemic domain model approach (service layer), it is hard to make a UML diagram (class diagram), and even if you had able to create one, it is not officially accepted so people will have different interpretation there.

In terms of design perspective, service layer is too "independent" or separated. By looking at the anemic domain model class, you cannot find the behavior (save, for example) related to the domain model. You need to search for entire project to find the specific behavior for the domain model. While in rich domain model, you know the traces of behavior inside the domain model itself.

Rich domain model have better access modifier (public, private, protected) for their properties. As well as property visibility. For example if you want to change status after submit, you can make the property get access to public, but the set access to protected. In service layer, you need to make the set access to public, or trick it with internal protected and make the submitter to directly change the property via internal access. But it is an added complexity.

But rich domain model does not have the flexibility as anemic domain model have. You cannot add behavior to the model without changing the domain model's class, unless you use inheritance. While in anemic domain model, you can even swap it at runtime level.

Max answered 3/3, 2014 at 1:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.