Domain services vs Application services



In this post, we’ll take a look at domain services: what differs them from application services and when it is preferable to use one in addition to an application service.

Domain services vs Application services

It is often said that domain services carry domain knowledge that doesn’t naturally fit entities and value objects. There’s another reason, however, why you may want to introduce a domain service. That reason is related to domain model isolation. More on that in just a minute.

So, what differs domain services from application services? Both these concepts assume stateless classes which can work on top of domain entities and value objects, but that’s pretty much as far as their similarities go. The main difference between them is that domain services hold domain logic whereas application services don’t.

As we discussed in a previous post, domain logic is everything that is related to business decisions. Domain services, therefore, participate in the decision-making process the same way entities and value objects do. And application services orchestrate those decisions the same way they orchestrate decisions made by entities and value objects.

Let’s take this example:

public void WithdrawMoney(decimal amount)

{

    _atm.DispenseMoney(amount);

    decimal amountWithCommission = _atm.CalculateAmountWithCommission(amount);

 

    _paymentGateway.ChargePayment(amountWithCommission);

    _repository.Save(_atm);

}

The WithdrawMoney method is part of an application service and comprises a customer-facing API. It tells an ATM entity to dispense some amount of money first, then asks it to calculate the amount of money with a commission, uses the calculation result to charge the payment through a payment gateway and finally saves the entity to the database.

Does this application service look good or should we extract some code out of it into a domain service? Let’s see.

In the first two lines, the method uses the Atm domain entity to make some business decisions. With the last two lines, it transforms those decisions into visible side effects. It calls the payment gateway and modifies the state of the database.

The first two lines here are about delegating the decision-making process to the domain model. But isn’t the sole act of knowing which two lines of code to use a domain knowledge in itself? Shouldn’t we extract it into a domain service? For example, like this:

public void WithdrawMoney(decimal amount)

{

    decimal amountWithCommission = _atmService.DispenseAndCalculateCommission(

        _atm, amount);

 

    _paymentGateway.ChargePayment(amountWithCommission);

    _repository.Save(_atm);

}

public sealed class AtmService // Domain service

{

    public decimal DispenseAndCalculateCommission(Atm atm, decimal amount)

    {

        atm.DispenseMoney(amount);

        return atm.CalculateAmountWithCommission(amount);

    }

}

Not really. The mere fact of using more than one line of code that is somehow related to the domain model doesn’t constitute a domain knowledge. What matters is whether or not those lines of code are responsible for making business decisions.

In the sample above, the DispenseAndCalculateCommission method has a cyclomatic complexity of 1. There are no branches (“if” statements) that signalize the code makes any decisions whatsoever, it just asks the domain entity to do two separate things.

The order in which these two methods are called doesn’t matter either. If we were to re-arrange them and put the commission calculation query above the dispense money command, nothing would change in terms of the Atm’s invariants, it would still remain valid. It’s a strong sign there are no leaking implementation details either.

Now, let’s change the code sample a little bit and say that there’s also a validation involved:

public void WithdrawMoney(decimal amount)

{

    if (!_atm.CanDispenseMoney(amount))

        return;

 

    _atm.DispenseMoney(amount);

    decimal amountWithCommission = _atm.CalculateAmountWithCommission(amount);

 

    _paymentGateway.ChargePayment(amountWithCommission);

    _repository.Save(_atm);

}

The cyclomatic complexity in this example is higher than one because we now have an “if” statement. Doesn’t it mean the application service now contains domain knowledge?

Also, no. The actual decision-making process still resides in Atm. The entity and entity alone decides whether it can dispense any money. The application service just orchestrates that decision and either continues the execution or not.

As long as the DispenseMoney method in Atm has a pre-condition stating that CanDispenseMoney must hold true prior to dispensing cash, all invariants stay protected. The precondition itself can be implemented as simple as this:

public void DispenseMoney(decimal amount)

{

    if (!CanDispenseMoney(amount))

        throw new InvalidOperationException();

 

    /* … */

}

So, even if the application service ignores the decision made by CanDispenseMoney, the Atm entity will not enter an inconsistent state. It will throw an exception thus adhering to the fail fast principle.

When to extract a domain service?

The application service in the sample above doesn’t make any business decisions, it delegates those decision to the domain model. Note that the domain model is isolated: the Atm entity doesn’t save itself to the database and doesn’t directly charge payments through the payment gateway. We have a nice separation of concerns here: business logic is attributed to the domain model, while interactions with the external world – to the application service.

You can notice a pattern in most code bases that adhere to such a guideline. Their execution flow goes as follows:

  • Prepare all information needed for a business operation: load participating entities from the database and retrieve any required data from other external sources.
  • Execute the operation. The operation consists of one or more business decisions made by the domain model. Those decisions result in either changing the model’s state, generating some artifacts (amountWithCommission value in the sample above), or both.
  • Apply the results of the operation to the outside world.

Only the 1st and the 3rd steps involve the work with external dependencies. The 2nd step is closed under the data retrieved in the first step. The arguments it accepts and the output it generates consist of entities, value objects, and primitive types only.

Note that in simple CRUD applications, there’s no 2nd step because there are no decisions to make. In this case, all operations can be performed solely by application services, no need to delegate them to a domain model. In fact, there can be no rich domain model whatsoever. An anemic domain model would work just fine in such situations.

Now, let’s modify our code sample to a more realistic scenario. Let’s assume that the payment charge can fail due to insufficient balance and that we shouldn’t dispense any cash if that happens.

This is where the nice separation of concerns I described above starts to break apart. The decision process now depends on information unavailable by the time we start that decision process. Here’s the code:

public void WithdrawMoney(decimal amount)

{

    if (!_atm.CanDispenseMoney(amount))

        return;

 

    decimal amountWithCommission = _atm.CalculateAmountWithCommission(amount);

    Result result = _paymentGateway.ChargePayment(amountWithCommission);

 

    if (result.IsFailure)

        return;

 

    _atm.DispenseMoney(amount);

    _repository.Save(_atm);

}

In this version, the second “if” statement we introduced does represent domain logic. It decides whether we will be dispensing cash to the user. However, unlike in the first conditional operator, it’s not the Atm entity who makes that decision. It’s the application service itself.

It is possible now to take cash from the Atm even if the payment has failed prior to it. The domain entity doesn’t hold this invariant for us. And it’s impossible to introduce such an invariant without violating the entity’s isolation because in order to check this precondition it will have to call the 3rd party service.

So, what to do in such situation? This is where domain services can be helpful. You can attribute to them any business decisions which require additional information from the external world and which cannot be made by entities and value objects because of that:

public void WithdrawMoney(decimal amount)

{

    Atm atm = _repository.Get();

    _atmService.WithdrawMoney(atm, amount);

    _repository.Save(_atm);

}

public sealed class AtmService // Domain service

{

    public void WithdrawMoney(Atm atm, decimal amount)

    {

        if (!atm.CanDispenseMoney(amount))

            return;

 

        decimal amountWithCommission = atm.CalculateAmountWithCommission(amount);

        Result result = _paymentGateway.ChargePayment(amountWithCommission);

 

        if (result.IsFailure)

            return;

 

        atm.DispenseMoney(amount);

    }

}

The domain service here is a middle ground between impurity and the amount of complexity / business logic held. On one hand, we can’t make this service completely isolated because it has to work with the payment gateway in order to do its work. On the other, we don’t attribute too much domain logic to it, only the knowledge regarding how to exchange cash for credit.

We still attribute as much logic as possible to the entity. For example, the act of dispensing cash is still a responsibility of Atm. And we still try to isolate the domain service as much as possible. For example, we don’t make it work with the repository as it is not required to make the business decision. The impurity and the domain logic introduced to the service are the bare minimum here. Just enough for it to work properly.

This extraction may look questionable. Isn’t it just a shift of responsibilities with no practical benefits? There are some benefits, however. The code in the domain service is more testable than in the application service. It has fewer external dependencies and therefore we need to use fewer test doubles in order to unit test it. This service is of course not as testable as the entity but still. The second benefit is that this way we prevent domain knowledge leakage and keep all domain logic within the domain model boundary which may be helpful for readability.

I wouldn’t say that in this particular example these two benefits play a significant part. It’s mostly fine to keep a little bit of logic that doesn’t fit an entity in an application service itself and not introduce a separate domain service every single time. Make sure, however, that this logic is not duplicated and that it is not too complex. If you see the DRY principle is violated or the application service becomes too complex, you need to definitely introduce a domain service.

Injecting a domain service into an entity

A question I sometimes hear is: can a domain service be injected into an entity?

I personally distinct two types of domain services: pure (isolated) and impure (non-isolated). The former is closed under entities and value objects and doesn’t depend on the external world. AtmService is an example of an impure domain service.

Injection of an impure domain service into entities breaks the isolation, so I’d recommend against it. On the other hand, a pure domain service doesn’t do any harm, so it’s totally fine to refer to them from your entities and value objects.

Summary

  • Domain services carry domain knowledge; application services don’t (ideally).
  • Domain services hold domain logic that doesn’t naturally fit entities and value objects.
  • Introduce domain services when you see that some logic cannot be attributed to an entity/value object because that would break their isolation.

Related articles

Share




  • Yuriy

    Thank you for the article! A small note, I think you’ve meant “if (!_atm.CanDispenseMoney(amount))” in your examples instead of non-negating version of the if predicate. I know it’s just a small implementation detail, but it was a tiny bit distracting 🙂

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Ah, indeed! Fixed that, thanks for pointing out.

  • Naeem Sarfraz

    I think there’s a small typo in this sentence: “The arguments it accepts and the output in generates consist of entities, value objects, and primitive types only”

    Should it be? “…the output it generates…”

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Fixed that, thanks!

  • Naeem Sarfraz

    Thanks for sharing these thoughts.

    I’m seeing more and more Commands replacing Application Services especially when you reduce the AS down to 1) loadnew up something 2) perform an action & 3) save.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Yep, and that’s exactly how the Command part in many CQRS applications looks like. Very clean and easy to follow.

  • dave barnett

    Thanks, I’ve found this and the other blogs this month really useful.

    I was just wondering with your final example; how would you report success or failure back to the client? I ask this because the only way I can think of doing it would be to query the atm object somehow to see if it had actually dispensed the money.

    I noticed you called it AtmService and wondered what you would do if you needed the service for something other than withdrawal. In this case its perhaps unlikely but in your experience does it ever happen that domain services can end up having more than one responsibility? And if this happens should we look to refactor so that we have several services each with one responsibility?

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      How would you report success or failure back to the client?

      I would return a Result from the service’s WithdrawMoney method. Something like this:

      public Result WithdrawMoney(Atm atm, decimal amount)
      {
      if (!atm.CanDispenseMoney(amount))
      return Result.Fail(“Cannot dispense money”);

      /* … */

      return Result.Ok();
      }

      And if there are several possible reasons of failure, I’d make CanDispenseMoney return Result too and then check that result in the domain service and return it back to the client code if isn’t a failure.

      In this case its perhaps unlikely but in your experience does it ever happen that domain services can end up having more than one responsibility? And if this happens should we look to refactor so that we have several services each with one responsibility?

      Depends on how large those responsibilities are. If they are small, I would hesitate to introduce separate services for them. Otherwise yes, I would definitely recommend coming up with separate ones. Something like WithdrawalService, ReportingService, etc. But again, only if that logic doesn’t naturally fit the Atm entity itself or requires to query external world.

      • dave barnett

        Just what I needed. Thanks!

  • dave barnett

    Just wanted to ask where logging fits into this. I’ve just read your blog on Singleton v dependency injection http://enterprisecraftsmanship.com/2016/05/03/singleton-vs-dependency-injection/. I’ve tried to apply your advice to logging by writing the following method

    public static class AppLogManager
    {
    private static ILogManager _logManager;

    public static void Init(ILogManager logManager)
    {
    _logManager = logManager;
    }

    public static ILogger GetLogger()
    {
    return _logManager.GetLogger(typeof(T));
    }
    }

    so that I can get access to a logManager via the Singleton Pattern. ILogger and ILogManger are defined as follows

    public interface ILogManager
    {
    ILogger GetLogger(Type type);
    }

    public interface ILogger
    {
    void Info(string message);
    //other methods
    }

    Using the following class

    public static class GenericLoggingExtensions
    {
    public static ILogger Log(this T thing)
    {
    var log = AppLogManager.GetLogger();

    return log;
    }

    }

    means I can write code such as this.Log().Info(“log message”);

    Am I correct in saying that the logger here is impure and therefore code like this should not reside in my domain entities? If logging is needed in this context it should be done inside a handler for a domain event?

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Am I correct in saying that the logger here is impure and therefore code like this (*) should not reside in my domain entities?

      Yes. Generally, logging routine is not a responsibility of domain entities because it has nothing to do with the domain knowledge, so try to put it into app services instead (such as domain event handlers).

    • J7mbo

      Logging isn’t a domain responsibility. An alternative to ‘domain event handlers’ would be utilising some aspects *ahem* of AOP.

  • Samuel Francisco

    The domain services hinder my understanding of aggregations. In the described scenario, for example, I would not think of using a domain service to validate the e-mail availability. I would use an aggregation to register e-mails in the system. In this aggregation would be a list of registered e-mails and so the aggregation would be ready to implement the validation logic. In this case, then I’m in doubt about what would be the right approach.