我知道关于领域服务和应用程序服务之间的区别有很多问题(和答案)。
有关此问题浏览次数最多的答案之一是:领域驱动设计:领域服务、应用程序服务
但是我仍然很难区分这两种服务。所以我在这里举了一个例子。
这是我拥有的实体:
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...
}
这是一个域服务我有:
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();
}
}
此服务可以归类为域服务吗?如果不是,可以将其切成什么部分以将其放入ApplicationService?
谢谢!
无论如何,对我来说,区别在于应用程序服务用于集成层/关注点。集成出现在解决方案的外围,其中“外部”(前端)访问“内部”(Web-API /消息处理器)。
因此,它们通常不接收域对象的输入,而是接收 Id 和原始数据等原语的输入。如果交互足够简单,那么执行交互的对象(控制器/消息处理器)可以直接使用存储库或查询机制。集成层是您执行事务处理(开始/提交)的地方。
但是,如果您的交互需要在两个或多个域对象之间进行编排,那么您通常会选择应用程序服务并将原始数据传递给that。您可以再次通过在执行交互的对象(控制器/消息处理器)中编码所有内容来自己执行交互。如果您发现自己在重复代码,那么肯定需要应用程序服务。 因此,应用程序服务将收集要传递到域的任何附加数据。
另一方面,领域服务通常直接在领域对象上运行。它不进行任何额外的数据收集。我喜欢将域需要的所有内容传递给域。该域不需要
调用 来获取任何额外的东西。我也放弃了doubledispatch,而是在域外执行相关调用。如果只涉及一个对象,您可能需要检查功能是否无法移动到域对象本身。例如,您可以使用 driver.HiredBy(company);
,并且可以在 HiredBy
方法中应用不变量。与
Fired()
相同。另外,我喜欢从对象本身返回域事件:在 Driver
类上,我们可以有 DriverFirstEvent Fire(Company currentCompany);
这些准则可能会根据您的要求而有所不同,因为没有什么是一成不变的。您所拥有的样本,我将其归类为 应用程序服务。
这部分:
可以移动到域对象:
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();
}
这也可以移动到domainObject:
// First load the driver
AvalibleDriver avalibleDriver = this.driverAvailableService.getDriver(driverAvailableId);
// copy the data from the driver available
var newDriver = Driver.of(avalibleDriver);
然后您可以在“of”方法中添加验证,例如名称不能为空等。
这个想法是在 doimanObject 中保留仅涉及域对象并且不需要外部信息的逻辑。因此它将是可重用的并且很容易在代码中找到。