Java 域服务与应用程序服务(示例)

Java 域服务与应用程序服务(示例),java,domain-driven-design,Java,Domain Driven Design,我知道关于域服务和应用程序服务之间的区别有很多问题和答案 关于这一点,最受欢迎的答案之一是: 但我仍然难以在这两种服务之间划清界限。所以我在这里举了一个例子 这是我拥有的实体: package com.transportifygame.core.domain.entities; import com.fasterxml.jackson.annotation.JsonIgnore; import com.transportifygame.core.domain.constants.Drivers

我知道关于域服务和应用程序服务之间的区别有很多问题和答案

关于这一点,最受欢迎的答案之一是:

但我仍然难以在这两种服务之间划清界限。所以我在这里举了一个例子

这是我拥有的实体:

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...
}

这是我拥有的一项域服务:

此服务是否可以归类为域服务?如果没有,可以将其切片以在应用程序服务中打开它


谢谢

无论如何,对我来说,区别在于集成层/关注点中使用了应用程序服务。集成出现在解决方案的外围,外部前端访问内部web api/消息处理器

因此,它们通常不接收域对象方面的输入,而是接收ID和原始数据等原语方面的输入。如果交互足够简单,那么执行交互控制器/消息处理器的对象可以直接使用存储库或查询机制。集成层是执行事务处理开始/提交的地方

但是,如果您的交互需要在两个或多个域对象之间进行编排,那么您通常会选择应用程序服务并将原始数据传递给该应用程序服务。同样,您可以通过对执行交互控制器/消息处理器的对象中的所有内容进行编码来自己执行交互。如果您发现您正在复制代码,那么肯定需要应用程序服务

因此,应用程序服务将执行要传递给域的任何附加数据的收集

另一方面,域服务通常直接对域对象进行操作。它不做任何额外的数据收集。我喜欢将域所需的一切传递给域。域不需要调用来获取任何额外信息。我也不再双重分派,而是在域外执行相关调用。如果只涉及一个对象,您可能需要检查功能是否无法移动到域对象本身。例如,你可以选择driver.HiredBycompany;这些不变量可以应用于HiredBy方法。被解雇也一样。另外,我喜欢从对象本身返回域事件:在驱动程序类上,我们可以有DriverFirstEvent FireCompany currentCompany

这些指导原则可能会因您的要求而有所不同,因为任何东西都不是一成不变的


您所拥有的作为示例,我将其归类为应用程序服务。

谢谢您的解释,Eben!有时我发现很难将域服务和应用程序服务分开。例如,启动驱动程序的代码可以被转移到应用程序服务中,因为它做了更多与驱动程序无关的事情,对吗?比如检查他们是否属于同一家公司就是其中之一。在其他域服务中,我将此服务与其他域服务进行对话,以便对其进行操作,因此我认为它也可以移动到应用程序服务中。实际上,我将在驱动程序类中使用FireCompany。然后它将检查传入的公司实例是否是当前链接的公司实例;否则就扔。它还可以检查当前的交付情况。它还可以返回相关的域事件。当我所做的只是与负载相关的聚合时,我倾向于不使用应用程序服务,但这将是一种设计偏好。目标应该是在域对象中保留尽可能多的代码。另一种选择是一个更贫乏的领域,其功能服务作用于包含的数据。我不确定,我不太喜欢在实体中使用fire函数,因为它需要在实体中包含存储库的实例。我正在考虑使用代码在域服务中启动驱动程序,并从此处调度事件,在应用程序服务中进行附加检查。你认为这是一个好方法吗?这是我进入实体的地方,我需要什么。例如,不应该注入存储库。我也不喜欢双重派遣。如果需要从实体调出到存储库/服务,则应该标识所需的外部实体/值对象/数据,而不是将其传入。这就是为什么我建议使用driver.firecompanystance,而不是driver.FirecompanyId,repository。我希望在我的案例中,我的域实体也是一个Hibernate实体,因此它已经有了一个公司的实例。因此,我不需要将公司的立场传递给可能的driver.fire方法。那会是个问题吗?
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();
    }
}