Architectural Layers and Modeling Domain Logic

As I was discussing the PoEAA patterns used to model domain logic (i.e. transaction script, table module, domain model), I noticed that people get the impression (albeit wrong impression) that the domain model pattern is best. So, they set out to apply it on everything.

Not Worthy of Domain Model Pattern

Let's get real. The majority of sub-systems are CRUD-based. Only a certain portion of the system requires the domain model implementation pattern. Or, put it in another way, there are parts of the application that just needs forms over data, and some validation logic (e.g. required/mandatory fields, min/max values on numbers, min/max length on text). For these, the domain model is not worth the effort.

For these, perhaps an anemic domain model would fit nicely.

Anemic Domain Model Isn't As Bad As It Sounds

The anemic domain model isn't as bad as it sounds. There, I said it (at least here in my blog post).

In this case, having the Person class exposed to the presentation layer is perfectly all right. The presentation layer can use it directly, since it has a public zero-arguments constructor, getters and setters, which are most likely needed by the view.

And there you have it. A simple CRUD-based application.

Do you still need a service layer? No. Do you still need DTO (data transfer objects)? No. In this simple case of CRUD, you don't need additional services or DTOs.

Yes, the Person looks like a domain entity. But it does not contain logic, and is simply used to transfer data. So, it's really just a DTO. But this is all right since it does the job of holding the data stored-to and retrieved-from persistence.

Now, if the business logic starts to get more complicated, some entities in the initially anemic domain model can become richer with behavior. And if so, those entities can merit a domain model pattern.

Alternative to Anemic Domain Model

As an alternative to the anemic domain model (discussed above), the classes can be moved out of the domain logic layer and in to the presentation layer. Instead of naming it PersonRepository, it is now named PersonDao.

Keep the repository in the domain.model package. Place the repository implementations in another package (e.g. infrastructure.persistence). But why?

The domain.model package is where the repository is defined. The elements in the domain model dictate what methods are needed in the repository interface definition. Thus, the repository definition is placed in the domain.model package. The repository implementations need to follow what new methods are added (or remove unused ones). This packaging follows the dependency inversion principle. The infrastructure.persistence package depends on the domain.model package, and not the other way around.

Application Services for Transactions

So, when would application services be appropriate? The application services are responsible for driving workflow and coordinating transaction management (e.g. by use of the declarative transaction management support in Spring).

If you find the simple CRUD application needing to start transactions in the presentation-layer controller, then it might be a good sign to move them into an application service. This usually happens when the controller needs to update more than one entity that does not have a single root. The usual example here is transferring amounts between bank accounts. A transaction is needed to ensure that debit and credit both succeed, or both fail.

Domain Model Pattern (only) for Complex Logic

I'll use the double-entry accounting as an example. But I'm sure there are more complex logic that's better suited.

Let's say we model journal entries and accounts as domain entities. The account contains a balance (a monetary amount). But this amount is not something that one would simply set. A journal entry needs to be created. When the journal entry is posted, it will affect the specified accounts. The account will then update its balance.

Now, in this case, a naive implementation would have a presentation-layer controller create a journal entry object, and use a repository to save it. And at some point in time (or if auto-posting is used), the corresponding account transactions are created, with account balances updated. All this needs to be rolled into a transaction (i.e. all-or-nothing).

If there's a need to allow the user to browse through journal entries and account transactions, the presentation-layer controller can directly use the corresponding repositories. If the domain entities are not suitable for the view technology (e.g. it doesn't follow JavaBean naming conventions), then the presentation-layer can define DTOs that are suitable for the view. Be careful! Don't change the domain entity just to suit the needs of the presentation-layer.

2 comments:

Upon reading Patterns, Principles, and Practices of Domain-Driven Design (Scott Millett and Nick Tune), I agree with what they wrote on p. 64 (Chapter 5: Domain Model Implementation Patterns):

"The majority of sub systems are CRUD based, with only the core domain requiring the domain model implementation pattern to ensure clarity or to manage complex logic. What you should not do is try to apply the domain model pattern for everything. Some parts of your application will simply be forms over data and will require just basic validation instead of rich business logic. Trying to model everything and apply object‐oriented practices would be a waste of effort that would be better spent on your core domain. Software development is all about making things simpler, so if you have complex logic, apply the domain model pattern; otherwise, look for a pattern that fits the problem you have, like the anemic domain model or the table module pattern."

Also on p. 86 (Chapter 6: Maintaining the Integrity of Domain Models with Bounded Contexts)

"Not all bounded contexts need to share the same architectural pattern. If a bounded context contains a supporting or generic domain with a low logic complexity, you might want to favor a more create, read, update, and delete (CRUD) style of development. If, however, the domain logic is sufficiently complex, it’s best to create a rich object-oriented model of the domain. Once bounded contexts are separated you can go a step further and apply different architectural patterns."