0

I know there is a lot of questions (And answers) about the difference between Domain Service and Application Service.

One of the most viewed answers regarding this is this one: Domain Driven Design: Domain Service, Application Service

But I'm still having trouble to draw the line between this two kind of services. So I brought here an example.

This is entity I have:

package com.transportifygame.core.domain.entities;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.transportifygame.core.domain.constants.Drivers;
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.time.ZonedDateTime;
import java.util.UUID;

@Getter
@Setter

@Entity
@Table(name = "drivers")
public class Driver
{
    @Id
    @GeneratedValue
    private UUID id;

    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "salary", nullable = false)
    private Double salary;

    @Column(name = "age")
    private Integer age;

    @Column(name = "hired_at")
    private ZonedDateTime hiredAt;

    @Column(name = "bonus")
    private Integer bonus;

    @Column(name = "experience_level", nullable = false)
    private Integer experienceLevel = Drivers.ExperienceLevel.BEGINNER.ordinal();

// And keep going...
}

And this is a Domain Service I have:

package com.transportifygame.core.domain.services;

import com.transportifygame.core.domain.entities.Company;
import com.transportifygame.core.domain.entities.Driver;
import com.transportifygame.core.domain.events.drivers.DriverFired;
import com.transportifygame.core.domain.exceptions.drivers.DriverInDeliveryException;
import com.transportifygame.core.domain.exceptions.drivers.DriverNotFoundException;
import com.transportifygame.core.application.repositories.DriverRepository;
import com.transportifygame.core.application.utils.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.UUID;

@Service
public class DriverService extends AbstractService
{
    private DriverRepository driverRepository;
    private DriverAvailableService driverAvailableService;

    @Autowired
    public DriverService(
        DriverRepository driverRepository,
        DriverAvailableService driverAvailableService
    )
    {
        this.driverRepository = driverRepository;
        this.driverAvailableService = driverAvailableService;
    }

    @Transactional
    public Driver hire(Company company, UUID driverAvailableId) throws DriverNotFoundException
    {
        // First load the driver
        var driver = this.driverAvailableService.getDriver(driverAvailableId);

        // copy the data from the driver available
        var newDriver = new Driver();
        newDriver.setName(driver.getName());
        newDriver.setAge(driver.getAge());
        newDriver.setBonus(driver.getBonus());
        newDriver.setHiredAt(DateTime.getCurrentDateTime(company.getUser().getTimezone()));
        newDriver.setSalary(driver.getSalary());
        newDriver.setCompany(company);

        // save it
        newDriver = this.driverRepository.save(newDriver);
        this.driverAvailableService.deleteDriver(driver);

        return newDriver;
    }

    public void fire(Company company, UUID driverId) throws DriverInDeliveryException, DriverNotFoundException
    {
        var driver = this.getDriverDetails(driverId);
        if (!driver.getCompany().getId().equals(company.getId())) {
            throw new DriverNotFoundException();
        }

        // First check if the driver it's in the middle of a delivery
        if (driver.getCurrentDelivery() != null) {
            throw new DriverInDeliveryException();
        }

        var driverFiredEvent = new DriverFired(this, company, driver.getName(), driver.getSalary());
        this.publishEvent(driverFiredEvent);

        // And delete the driver in the end
        this.driverRepository.delete(driver);
    }

    public Iterable<Driver> getAllCompanyDrivers(Company company)
    {
        return this.driverRepository.findAllByCompanyId(company.getId());
    }

    public Driver getDriverDetails(UUID id) throws DriverNotFoundException
    {
        var driver = this.driverRepository.findById(id);

        if (driver.isEmpty()) {
            throw new DriverNotFoundException();
        }

        return driver.get();
    }
}

Can this service be classified as Domain Service? If not what can it be sliced down to turn it in a ApplicationService?

Thanks!

Vitor Villar
  • 1,855
  • 18
  • 35

1 Answers1

3

The distinction, for me anyway, is that an application service is used in the integration layer/concern. Integration appears on the periphery of your solution where the "outside" (front-ends) access the "inside" (web-api / message processors).

As such they typically don't receive input in terms of domain objects but rather in primitives such as Ids and raw data. If the interaction is simple enough then the object performing the interaction (controller/message processor) could use a repository or query mechanism directly. The integration layer is where you perform transaction processing (begin/commit).

However, if your interaction requires orchestrating between two or more domain objects then you would typically opt for an application service and pass the primitive data to that. You could, again, perform the interaction yourself by coding all that in the object performing the interaction (controller/message processor). If you find you are duplicating code then an application service is certainly required.

Application services, therefore, would perform gathering of any additional data to be passed along to the domain.

A domain service, on the other hand, typically operates directly on domain objects. It does not do any additional data gathering. I like to pass everything the domain needs to the domain. The domain should not need to call out to obtain anything extra. I have also moved away from double dispatch and rather perform the relevant call outside the domain. If there is only a single object involved you may want to check whether the functionality cannot be moved to the domain object itself. For instance, you could go with driver.HiredBy(company); and the invariants could be applied in the HiredBy method. Same with Fired(). Also, I like returning domain events from the objects themselves: on the Driver class we could have DriverFirstEvent Fire(Company currentCompany);

These guidelines may vary depending on your requirements as nothing is cast in stone.

What you have as a sample I would categorise as an application service.

Eben Roux
  • 12,983
  • 2
  • 27
  • 48
  • Thanks for the explanation Eben! Sometimes I find it difficult to split the domain service and application service. So for example the code to fire the driver, could be moved out to an *application service* as it does more stuff not related to the driver, right? like check if they belong to the same company is one of the things. In other domain services I have this services talking to other domain services to act upon it, so I think it also can be moved to an application service. – Vitor Villar Jan 16 '20 at 11:08
  • I would actually have the `Fire(Company)` on the `Driver` class. It would then check whether the `Company` instance passed in is the one it is currently linked to; else `throw`. It could also check the current delivery. It could also return the relevant domain event. When all I do is load related aggregates I tend not to use an application service but that is going to be a design preference. The aim should be to keep as much code in the domain objects as possible. The alternative is a more anemic domain with "functions" (services) acting on the contained data. Rather keep that in the domain. – Eben Roux Jan 16 '20 at 12:31
  • I'm not sure, I don't like much the idea to have the fire function in the entity, cause then it would need to have a instance of the repository into it. I'm thinking into having the code to fire the driver in a *Domain Service* (and dispatching event from here), the additional checks in an *Application service*. Do you think is a good way to go? – Vitor Villar Jan 16 '20 at 12:41
  • This is where I pass into the entity what I need. One should *not* inject repositories, for instance. I also do not like *double dispatch*. If you need to *call out* from the entity to a repository/service you should identity the external entity/value object/data required and rather pass *that* in. That is why I suggest `driver.Fire(companyInstance)` and ***not*** `driver.Fire(companyId, repository)`. I hope that makes sense – Eben Roux Jan 16 '20 at 14:22
  • In my case, my domain entity is also an Hibernate entity, therefore it already has an instance of `Company`. So I would not need to pass the `companyInstance` to a possible `driver.fire()` method. Would that be a problem? – Vitor Villar Jan 16 '20 at 19:48
  • ORM objects typically do not make good domain objects. ORM objects are really part of infrastructure and in situations where I have to use an ORM I end up using the ORM object simply to provide the data for my domain objects in much the same way as I would when retrieving the data directly from the data store. Domain objects are typically also not queried (for instance: used for front-end lists) since they should be properly encapsulated and don't have deep graphs. That's another discussion. Using `driver.fire()` is fine for firing from the connected company, ORM or no. – Eben Roux Jan 17 '20 at 04:20
  • And is it OK if the domain object, receives an ORM object as parameter of a method, to change it's properties? – Vitor Villar Jan 17 '20 at 11:59
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/206142/discussion-between-vitor-villar-and-eben-roux). – Vitor Villar Jan 17 '20 at 12:27