Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Right and wrong about services in DDD

Please correct me if I'm wrong (and add other things you think is correct):

Application services ...

  • the (public front-facing) api of a domain
  • responsible for loading and saving aggregates
  • can access repositories and other infrastructure services
  • is not part of a domains ubiquitous language
  • should/could be a very thin layer on top of the domain (that mostly handles load/save of aggregates and delegates the rest to the domain)
  • can contain pure read operations

Domain services ...

  • the "true" api of the domain
  • contains domain logic
  • works only with domain objects (not infrastructure services such as repos and email-sender-services)
  • usually contain code that orchestrates different aggregates
  • is part of a domains ubiquitous language
  • can depend on other domain services
  • contains only modifying operations
like image 602
Andreas Zita Avatar asked Sep 02 '25 15:09

Andreas Zita


2 Answers

Your definition of application services is correct. I see application services more as command handlers. Receive a command, load an aggregate, call the aggregate method and save the chance. One command is handled within a single transaction.

Domain services are used to do something that aggregates need but cannot do. Typical examples could be retrieving additional information from the outside world or doing some calculations. The app service doesn't necessarily know if the aggregate would need this or that information but it can resolve necessary dependencies and pass the domain service to the aggregate when it calls it.

In my practice, domain services are most often implemented as functions. Remember that domain services aren't exclusively used by aggregates. Complex value objects can perfectly use domain services for the same purpose.

In my book, I use a domain service to allow a value object to ensure that it will not be instantiated with text that contains profanity.

    public static DisplayName FromString(
        string displayName,
        CheckTextForProfanity hasProfanity)
    {
        if (displayName.IsEmpty())
            throw new ArgumentNullException(nameof(FullName));

        if (hasProfanity(displayName).GetAwaiter().GetResult())
            throw new DomainExceptions.ProfanityFound(displayName);

        return new DisplayName(displayName);
    }

Hence that the domain service contract (a named delegate in that case) is defined in the domain,

namespace Marketplace.Domain.Shared
{
    public delegate Task<bool> CheckTextForProfanity(string text);
}

but its implementation is the infrastructure concern and is being wired on the application side.

namespace Marketplace.Infrastructure
{
    /// <summary>
    /// PurgoMalum is a simple, free, RESTful web service for filtering and removing content of profanity, obscenity and other unwanted text.
    /// Check http://www.purgomalum.com
    /// </summary>
    public class PurgomalumClient
    {
        private readonly HttpClient _httpClient;

        public PurgomalumClient() : this(new HttpClient()) { }

        public PurgomalumClient(HttpClient httpClient) => _httpClient = httpClient;

        public async Task<bool> CheckForProfanity(string text)
        {
            var result = await _httpClient.GetAsync(
                QueryHelpers.AddQueryString("https://www.purgomalum.com/service/containsprofanity", "text", text));

            var value = await result.Content.ReadAsStringAsync();
            return bool.Parse(value);
        }
    }
}
like image 82
Alexey Zimarev Avatar answered Sep 07 '25 10:09

Alexey Zimarev


I believe your understanding of Domain Service is off the mark.

The right starting point is going to be chapter 5 of Domain Driven Design by Eric Evans, where he defines Value Object, Entity and Domain Service.

As best I can tell, Evans was basing his patterns on experiences gathered writing domain models using Java circa 2003. In Java, anything that isn't a domain agnostic primitive is "an object"; while you could implement static functions, there weren't any particularly good ways to pass them. You instead needed to wrap the function inside of an object.

So "Domain Services" are "stateless objects"; objects, because that was a constraint in passing them, and stateless because all of the mutation of the underlying data is the responsibility of the entity that manages that data.

In the text, I believe he uses the example of a tax table; an invoice needs to be able to calculate taxes correctly, but the tax code isn't owned or managed by an invoice instance; instead, that data is managed elsewhere, and a read-only copy is shared by all of the invoices in the model.

In the Cargo shipping example, Cargo need to be assigned to routes, but the Cargo entities don't manage their own copies of the shipping schedules. Instead, queries against those tables are supported by the "RoutingService".

Coordination of entities, what Robert Martin referred to as Use Cases, is an application concern, not something managed by the domain services (as described by Evans).

like image 37
VoiceOfUnreason Avatar answered Sep 07 '25 09:09

VoiceOfUnreason