Domain Services in DDD: Navigating Through Validation and Dependency Management
Tags: ddd, architecture, dotnet
Published: 2023-10-03 18:59:50

Models abstract and simplify reality to make it understandable and computable. By doing so, certain nuances and details are inevitably omitted. Domain models are no exception. The last time we ended up with the following design of the `UserProfile` class:

```csharp
public class UserContact
{
    public EmailAddress Email { get; protected set; } // protected for EF
    
    public Phone Phone { get; protected set; } // protected for EF

    protected UserContact(){} // protected for EF

    public static UserContact Create(EmailAddress email) => 
        new() { Email = email }; 
    
    public static UserContact Create(Phone phone) =>
        new() { Phone = phone };
    
    public static UserContact Create(
        EmailAddress email, Phone phone) => 
        new() { Email = email, Phone = phone };
}
```

What if we needed to change the email of the user?

```csharp
public class UserContact
{
    // ...
    public void ChangeEmail(EmailAddress newEmail)
    {
        Email = newEmail;
    }
}
```

Validation typically takes two forms:
1. **Contextless**: Ensures the self-consistency of an object, for example `EmailAddress` constructor guards implemented [one way](https://dev.to/maxarshinov/type-based-design-386l) or [another](https://dev.to/maxarshinov/robust-design-through-value-objects-in-c-5aic).
2.  **Contextual**: Verifies an object concerning external factors, like ensuring user email uniqueness across a system.

```csharp
// UserController
public Task<IActionResult> ChangeEmail(int userId, EmailAddress newEmail)
{
    // Contextual validation
    User existingUser = await _userRepository.GetByEmailAsync(newEmail);
    if (existingUser != null && existingUser.Id != userId)
    {
        return BadRequest();
    }
    
    User user = await _userRepository.GetByIdAsync(userId);

    user.ChangeEmail(newEmail);
    await _userRepository.SaveAsync(user);

    return Ok();
}
```

### DDD Trilemma: A Crucial Tradeoff in Design

In his thought-provoking analysis, Vladimir Khorikov introduces the concept of the [DDD Trilemma](https://enterprisecraftsmanship.com/posts/domain-model-purity-completeness/), highlighting a pivotal choice developers must make:

1. Preserve domain model completeness and purity at a potential cost to performance.
2. Maintain performance and model completeness while sacrificing purity.
3. Uphold performance and model purity but compromise on completeness.

![](https://enterprisecraftsmanship.com/images/2020/2020-08-06-trilemma.png)

### Making Decisions Based on Queries

Coincidentally, Scott Wlaschin in his book “[Domain Modeling Made Functional](https://www.amazon.es/Domain-Modeling-Made-Functional-Domain-Driven/dp/1680502549)” (which I strongly recommend even for those who are not familiar with F#) recommends keeping the pure functions intact, but placing them between impure I/O functions if pushing I/O towards application boundaries is not an option because of performance reasons.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jxtzqaoeeuwtq61qo9ta.png)

### Simplifying Decisions: Two Robust Rules

In C#, I found it useful to domain Domain Services should the need arise:

1. **Maintain Purity**: use constructors, `required` keyword [accompanied by Value Objects](https://dev.to/maxarshinov/robust-design-through-value-objects-in-c-5aic) for the “pure” (IO-less) parts of your model.
2. **Domain logic fragmentation is a lesser evil**: use domain services for I/O operations, preserving the absence of IO in your entity classes. Introduce a domain service anytime you need the `async/await`. Don't create `async` methods in your entity classes. Don't use synchronous IO because it obscures the impurity.

These rules make it so it's easy to choose between an entity method and a service. However, there is one notable exception of the infamous *Aggregate* pattern, which I'd like to discuss [next time](https://dev.to/maxarshinov/harnessing-ddd-aggregates-with-graphql-a-deep-dive-56ck).