Paweł Lisy
Paweł Lisy

Reputation: 11

Rich Domain Model entity being big because of too many methods

I'm making an app, trying to stick to DDD concepts and practices. I have a rich domain model, with entities, value objects, business rules encapsulated within them, etc.

Entities don't really have too many fields and are relatively small when it comes to contained data, but the problem is, some of them got really big (as in: classes representing them) because of lots of possible interactions with those entities.

You know,

void DoThis();
int GetThat();
bool CheckForThis();

every interaction with some piece of my domain is represented by a public or internal method, each of those methods containing some business rules.

I tried to extract that logic into internal classes I call policies, so instead of

public int CalculateAmountOfCoolThingy()
{
    int result = 0;
    /*
    Super duper complicated calculations here with 100 lines of code.
    */
    return result;
}

in my entity, I have:

internal sealed class CoolThingyCalculationPolicy
{
    public int CalculateAmountOfCoolThingy(MyEntity entity)
    {
        int result = 0;
        /*
        Super duper complicated calculations here with 100 lines of code.
        */
        return result;
    }
}

and in my entity:

public int CalculateAmountOfCoolThingy()
{
    CoolThingyCalculationPolicy policy = new CoolThingyCalculationPolicy();
    int result = policy.CalculateAmountOfCoolThingy(this);
    return result;
}

Is this the way? Is this a good practice? Because it doesn't feel entirely right. Maybe there's some better approach?

Upvotes: 0

Views: 724

Answers (2)

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57214

I tried to extract that logic into internal classes I call policies.... Is this the way? Is this a good practice?

Probably not; the samples you provide suggest that you are designing an Anemic Domain Model. You might also want to review Tell, Don't Ask.

My view: big vs small is really an evaluation of the size of the data model controlled by the entity, rather than by the number of different methods you have that act upon that data model.

For example, if you have a single integer field, and 100 methods that need to lock that field (typically: while changing it), then you are going to end up with a single entity with 100 methods.

On the other hand, if you have two fields, and 100 methods that need to lock either field A or field B, but no method needs to lock both fields, then your entity is "too big", and you should consider using two entities instead.

The analysis metaphor that I use is that of a connected graph - draw an edge between each pair of values that need to be locked at the same time, then separate the graphs so that you can lock each without locking any of the others.

It's somewhat common to analyze a concept like "Customer" and discover that what you have are many disconnected graphs that all share the same lock. Those are entities that you should consider breaking up.

Upvotes: 1

R.Abbasi
R.Abbasi

Reputation: 711

Yes, you can encapsulate those rules in another class named domain services. But remember that you must inject the service into your domain model.

There are three attributes of a design that you should consider. The domain purity, completeness, and performance. You can't have it all at the same time.

  • Purity means that your model doesn't need any other services (such as repositories) to do its thing.
  • Completeness means that your model will have all its logic. In other words, there is no separation of logic (all validation goes through your domain model).
  • Performance is another attribute of design, which refers to situations when completeness and purity can be achieved while you sacrifice the performance for them.

In this article, Vladimir Khorikov talks about these three and I suggest you read it thoroughly. In the conclusion, he suggests that domain purity is more important than completeness (you can read about the reasons in the article). So I suggest that:

  • if your logic is an out-of-process service, you should put the logic in other layers. Out-of-process means that your logic needs external resources like a repository to complete the process.
  • if your logic is a handy service that doesn't need any external data, you can put it on your domain service and inject it into your domain. (it doesn't cost you much because you won't need to mock it for testing and you won't have infrastructure issues like opening a connection etc.)

Another thing to consider is that moving logic to domain services is not recommended unless you have a good reason for it. Fat domain services mean thin domain models which results in anemic domain models. To manage your domain models better, consider separating them into smaller building blocks like entities and value objects. Using domain services to make smaller models feels like cheating. Because the responsibility should be on the domain model and you force it into another object. It's not a good reason to create domain services unless you want to reuse some logic or calculations.

Upvotes: 0

Related Questions