Reputation: 1925
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!
Upvotes: 0
Views: 4100
Reputation: 1
This part:
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();
}
can be moved to domain object:
if (!driver.isWorkingInGivenCompany(company)) {
throw new DriverNotFoundException();
}
// First check if the driver it's in the middle of a delivery
if (driver.isInTheMiddleOfADelivery()) {
throw new DriverInDeliveryException();
}
This also can be moved to domain object:
// First load the driver
AvalibleDriver avalibleDriver = this.driverAvailableService.getDriver(driverAvailableId);
// copy the data from the driver available
var newDriver = Driver.of(avalibleDriver);
Then you can add validations inside of
method like, for example, name cannot be null, etc.
The idea is to keep logic that involves only domain object and do not need external information in your domain object, so it will be reusable and easy to find in the code.
Upvotes: 0
Reputation: 13256
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.
Upvotes: 4