Tuesday, December 18, 2007

Domain Modeling - What exactly is a Rich Model ?

It is possibly an understatement to emphasize the usefulness of making your domain model rich. It has been reiterated many times that a rich domain model is the cornerstone of building a scalable application. It places business rules in proper perspective to wherever they belong, instead of piling stuffs in the form of a fat service layer. But domain services are also part of the domain model - hence when we talk about the richness of a domain model, we need to ensure that the richness is distributed in a balanced way between all the artifacts of the domain model, like entities, value objects, services and factories. In course of designing a rich domain model, enough care should be taken to avoid the much dreaded bloat in your class design. Richer is not necessarily fatter and a class should only have the responsibilities that encapsulates its interaction in the domain with other related entities. By related, I mean, related by the Law of Demeter. I have seen instances of developers trying to overdo the richness thing and ultimately land up with bloated class structures in the domain entities. People tend to think of a domain entity as the sole embodiment of all the richness and often end up with an entity design that locks itself up in the context of its execution at the expense of reusability and unit testability. One very important perspective of architecting reusable domain models is to appreciate the philosophical difference in design forces amongst the various types of domain artifacts. Designing an entity is different from designing a domain service, you need to focus on reusability and a clean POJO based model while designing a domain entity. OTOH a domain service has to interact a lot with the context of execution - hence it is very likely that a domain service needs to have wiring with infrastructure services and other third party functionalities. A value object has different lifecycle considerations than entities and we need not worry about its identity. Hence when we talk of richness, it should always be dealt with the perspective of application. This post discusses some of the common pitfalls in entity design that developers face while trying to achieve rich domain models.

Entities are the most reusable artifacts of a domain model. Hence an entity should be extremely minimalistic in design and should encapsulate only the state that is required to support the persistence model in the Aggregate to which it belongs. Regarding the abstraction of the entity's behavior, it should contain only business logic and rules that model its own behavior and interaction with its collaborating entities.

Have a look at this simple domain entity ..


class Account {
    private String accountNo;
    private Customer customer;
    private Set<Address> addresses;
    private BigDecimal currentBalance;
    private Date date;

    //.. other attributes

    //.. constructors etc.

    public Account addAddress(final Address address) {
        addresses.add(address);
        return this;
    }

    public Collection<Address> getAddresses() {
        return Collections.unmodifiableSet(addresses);
    }

    public void debit(final BigDecimal amount) {
        //..
    }

    public void credit(final BigDecimal amount) {
        //..
    }

    //.. other methods
}



Looks ok ? It has a minimalistic behavior and encapsulates the business functionalities that it does in the domain.

Question : Suppose I want to do a transfer of funds from one account to another. Will transfer() be a behavior of Account ? Let's find out ..


class Account {
    //..
    //.. as above

    // transfer from this account to another
    public void transfer(Account to, BigDecimal amount) {
        this.debit(amount);
        to.credit(amount);
    }
    //.. other methods
}



Looks cool for now. We have supposedly made the domain entity richer by adding more behaviors. But at the same time we need to worry about transactional semantics for transfer() use case. Do we implant transactional behavior also within the entity model ? Hold on to that thought for a moment, while we have some fresh requirements from the domain expert.

In the meantime the domain expert tells us that every transfer needs an authorization and logging process through corporate authorization service. This is part of the statutory regulations and need to be enforced as part of the business rules. How does that impact our model ? Let us continue adding to the richness of the entity in the same spirit as above ..


class Account {
    //.. as above

    // dependency injected
    private AuthorizationService auth;
    //..

    public void transfer(Account to, BigDecimal amount) {
        auth.authorize(this, to, amount);
        this.debit(amount);
        to.credit(amount);
    }
    //.. other methods
}



Aha! .. so now we start loading up our entity with services that needs to be injected from outside. If we use third party dependency injection for this, we can make use of @Configurable of Spring and have DI in entities which are not instantiated by Spring.


import org.springframework.beans.factory.annotation.Configurable;
class Account {
    //.. as above

    // dependency injected
    @Configurable
    private AuthorizationService auth;

    //..
}



How rich is my entity now ?

Is the above Account model still a POJO ? There have already been lots of flamebaits over this, I am not going into this debate. But immediately certain issues crop up with the above injection :

  • The class Account becomes compile-time dependent on a third party jar. That import lying out there is a red herring.

  • The class loses some degree of unit-testability. Of course, you can inject mocks through Spring DI and do unit testing without going into wiring the hoops of an actual authorization service. But still, the moment you make your class depend on a third party framework, both reusability and unit testability get compromised.

  • Using @Configurable makes you introduce aspect weaving - load time or compile time. The former has performance implications, the latter is messy.



Does this really make my domain model richer ?

The first question you should ask yourself is whether you followed the minimalistic principle of class design. A class should contain *only* what it requires to encapsulate its own behavior and nothing else. Often it is said that making an abstraction design better depends on how much code you can remove from it, rather than how much code you add to it.

In the above case, transfer() is not an innate behavior of the Account entity per se, it is a use case which involves multiple accounts and maybe, usage of external services like authorization, logging and various operational semantics like transaction behavior. transfer() should not be part of the entity Account. It should be designed as a domain service that uses the relationship with the entity Account.


class AccountTransferService {
    // dependency injected
    private AuthorizationService auth;

    void transfer(Account from, Account to, BigDecimal amount) {
        auth.authorize(from, to, amount);
        from.debit(amount);
        to.credit(amount);
    }
    //..
}



Another important benefit that you get out of making transfer() a service is that you have a much cleaner transactional semantics. Now you can make the service method transactional by adding an annotation to it. There are enough reasons to justify that transactions should always be handled at the service layer, and not at the entity layer.

So, this takes some meat out of your entity Account but once again gives it back the POJO semantics. Taking out transfer() from Account, also makes Account decoupled from third party services and dependency injection issues.

What about Account.debit() and Account.credit() ?

In case debit() and credit() need to be designed as independent use cases under separate transaction cover, then it definitely makes sense to have service wrappers on these methods. Here they are ..


class AccountManagementService {
    // dependency injected
    private AuthorizationService auth;

    @Transactional
    public void debit(Account from, BigDecimal amount) {
        from.debit(amount);
    }
    @Transactional
    public void credit(Account to, BigDecimal amount) {
        to.credit(amount);
    }
    @Transactional
    public void transfer(Account from, Account to, BigDecimal amount) {
        //..
    }
    //..
}



Now the Account entity is minimalistic and just rich enough ..

Injection into Entities - Is it a good idea ?

I don't think there is a definite yes/no answer, just like there is no definite good or bad about a particular design. A design is a compromise of all the constraints in the best possible manner and the goodness of a design depends very much on the context in which it is used. However, with my experience of JPA based modeling of rich domain models, I prefer to consider this as my last resort. I try to approach modeling an entity with a clean POJO based approach, because this provides me the holy grail of complete unit-testability that I consider to be one of the most important trademarks of good design. In most of the cases where I initially considered using @Configurable, I could come up with alternate designs to make the entity decoupled from the gymnastics of third party weaving and wiring. In your specific design there may be cases where possibly you need to use @Configurable to make rich POJOs, but make a judgement call by considering other options as well before jumping on to the conclusion. Some of the other options to consider are :

  • using Hibernate interceptors that does not compromise with the POJO model

  • instead of injection, use the service as an argument to the entity method. This way you keep the entity still a pure POJO, yet open up an option to inject mocks during unit testing of the entity


Another point to consider is that @Configurable makes a constructor interception, which means that construction of every instance of that particular entity will be intercepted for injection. I do not have any figures, but that can be a performance overhead for entities which are created in huge numbers. A useful compromise in such cases may be to use a getter injection on the service, which means that the service will be injected only when it is accessed within the entity.

Having said all these, @Configurable has some advantages over Hibernate interceptors regarding handling of serialization and automatic reconstruction of the service object during de-serialization.

For more on domain modeling using JPA, have a look at the mini series which I wrote sometime back. And don't miss the comments too, there are some interesting feedbacks and suggestions ..

15 comments:

Danny said...

Good post, good example. However I would argue that the knowledge of how to transfer an amount should be inside the domain (I consider domain services to be outside the domain). After all, that is domain logic. So your AccountTransferService.transfer would call from.creditTo(to, amount). The service layer should contain a minimal amount of code, and certainly no domain code; that should be delegated onwards to your domain classes. If you need to transfer funds from within another domain service, your domain class already knows how to do that and you're not duplicating code.
(All IMO of course ;))

Jorge Báez said...

Great post.

It would be very interesting to see how you implement the logic of the services as well.

Anonymous said...

Oh excellent. I like domain driven design discussion!

Unknown said...

As someone who is only just getting into object oriented programming, I have to say that this post is a very clear and easy to understand explanation. Thanks for putting this up there. It is really helping to formulate my opinions on OOP.

Anonymous said...

You can't consider the scope of your domain model without a context. In other words: this example sucks.

Why should the transfer() method be in the Account class? And if including the transfer() method does impact your "entities", could it be that the reason to put it there in the first place bring about advantages that outweigh the disadvantages by a considerable amount?

Unknown said...

"You can't consider the scope of your domain model without a context."

Exactly! That is the point. And we should try to keep context out of reusable entities and into domain services. The entities should always be self-contained and free of any dependencies with the context services.

"Why should the transfer() method be in the Account class? "

It should not! And that is also the precise point of the post.

Anonymous said...

""You can't consider the scope of your domain model without a context."

Exactly! That is the point. And we should try to keep context out of reusable entities and into domain services. The entities should always be self-contained and free of any dependencies with the context services.
"

Wrong again.

You mean with context state. I mean with context the sum of all constraints and obligations.

If you try to cram your single silly table-to-class mapping everywhere in your application you will indeed suffer and pay the price.

If you however reconsider you state requirements and try to fit your data access requirements into specific situations you're free.

Anonymous said...

@Danny

I agree with you that it is not a service. I would probably have called it an AccountTransferCommand that is used by the service layer but is still a part of the domain.

@Anonymous

So you would inject the authorization service into the domain objects? And the Auditing service? And the ReceiptPrinterService? And whatever else needs to be done as part of the transfer?

Unknown said...

@Danny:
"However I would argue that the knowledge of how to transfer an amount should be inside the domain (I consider domain services to be outside the domain)"

Why do you consider domain services to be outside the domain ? Domain services, according to Eric Evans' DDD book are also very much part of the domain. Refer to Chapter 5 in his book. Entities are the most reusable, self contained elements of your architecture and *must* be designed as ones which are easily unit testable. When we talk about rich domain models, it is not only the entities but the domain services as well - the richness is all about making the *domain layer* rich without allowing domain logic to leak out of domain layer into others like presentation layers.

@Anonymous:

I usually use the terminologies of DDD as evangelized by Eric Evans. Hence I talked about transfer() being part of a service and NOT a command.

Anonymous said...

@Anonymous

So you would inject the authorization service into the domain objects? And the Auditing service? And the ReceiptPrinterService? And whatever else needs to be done as part of the transfer?


This example is so crappy that no sane person can figure out what's going one. Hence it's impossible to predict what one would or should do.

It's unfortunately common practice to try to explain DDD with crappy examples. I suggest to all self-proclaimed DDD educators to give us a break, work for a couple of months on a really complicated application and come back.

Anonymous said...

Debasish,

please don't confuse the concept of a 'Rich Domain Model' with a 'Distilled Domain Modell'.
A rich domain model isn't per se a concept from DDD. Instead it's the alternative draft to an anemic domain model, where entities are nothing more than dump data containers and business logic is centralized around that 'data structures' in a big service layer (often encountered with Entity Beans under 2.1). In contrast to that rather procedural style of organizing a domain model, there's a more object oriented style, where the domain is modeled by applying all offered features of OO (i.e. inheritance, polymorphic associations and objects that have state AND behaviour). This kind of domain model would be rich in respect to the mentioned features and true object oriented at it's core.
In your post you rather describe the process of distilling the core domain, finding the right boundaries, abstractions and concepts of the business that will lead to a deep or distilled domain model.

Greetings

Mario

Danny said...

@debasish:
"Why do you consider domain services to be outside the domain?"

Because they are -- literally -- not domain objects. They do not represent any tangible objects in the actual problem domain. Services are the way for the outside world to communicate with the domain; which is why they'll contain usecase-like methods. You're right though, that is not the implementation Evans describes. There are other DDD-gurus out there...

So I see domain services as a very thin layer that should delegate as much as possible to the actual domain classes. As domain classes have no knowledge of domain services, only a service can call another service (like the AuthorizationService in your example). Also, I think that putting too much (or any) domain logic into your domain services encourages a procedural programming style.

igodunov said...

I wouldn't introduce an additional method ("transfere") that is just a combination of the existing ones (credit & debit). Due to this argue, i'll also use a kind of additional (operational-like) API, if any. ) The example shows certain disadventage of such overloaded interface, but doesn't demonstrate something else.

Unknown said...

@Danny:
They do not represent any tangible objects in the actual problem domain

Why would everything have to be objects/nouns in the domain ? There are enough scopes for some verbs. Rich behavior that involve collaboration between multiple objects are often modeled better as services.

Anonymous said...

Note that the domain layer, not the application layer(service objects), is responsible for fundamental business rules—in this case, the rule is "Every credit has a matching debit."

- Domain Drive Design: Chapter 4