DDD 聚合和实体中的 InversionOfControl(依赖注入)
InversionOfControl (Dependency Injection) in DDD Aggregates and Entities
我的问题是关于你们在实践中如何将 Domain- 或 InfrastructureServices 注入 DDD 聚合。我确信在 general 中允许在 DomainObjects 中进行 DependencyInjection 是一个坏主意,因为根据我的经验,它会鼓励轻率的开发人员使用它做一些令人讨厌的事情。但是可以肯定的是,在 Domainobjects 中的 DependencyInjection 可能有意义的例外情况总是存在的,尤其是当它有利于可读性和简单性时。
就我而言,我正在尝试解决如何创建 aggregate
的 root
.
的新 Ids
的问题
假设我们有一个 UserAddresses
聚合,其中包含 Address
个实体列表作为其聚合根。让我们进一步假设我们在该聚合中有一个方法 changeAddress(AddressId addressId, AddressChangeDto dto)
:
public AddressId changeAddress(AddressId addressId, AddressChangeDto dto) {
Address address = nullableAddress(addressId);
if (address == null) {
// Doesn't matter for this question
} else if (address.hasChanged(dto)) {
address = changeAddress(address, dto);
}
}
和私有 changeAddress(Address address, AddressChangeDto dto)
方法简化如下:
private Address changeAddress(Address address, AddressChangeDto dto) {
if (!addressCopyNeeded(address)) {
address.change(dto);
return address;
}
// Address is both Shipping- & BillingAddress, hence it must be copied to 2 separate
// entities.
Address copiedAddress = address.copy(new AddressId()); // <-- New AddressId created
...
}
现在我想重构 AddressId
的创建,它是从驻留在我的 DomainModel 中的 AddressIdFactory
DomainService
创建的,由驻留在基础设施中的 MongoDb 存储库支持,以从 MongoDb 创建性能优化的 ObjectId
,我更喜欢长 UUID。
可以通过将该依赖项添加为 形式参数 来轻松解决这个问题,如下所示:
public AddressId changeAddress(AddressId addressId, AddressChangeDto dto, AddressIdFactory idFactory) {
Address address = nullableAddress(addressId);
if (address == null) {
// Doesn't matter for this question
} else if (address.hasChanged(dto)) {
address = changeAddress(address, dto, idFactory);
}
}
private Address changeAddress(Address address, AddressChangeDto dto, AddressIdFactory idFactory) {
if (!addressCopyNeeded(address)) {
address.change(dto);
return address;
}
// Address is both Shipping- & BillingAddress, hence it must be copied to 2 separate
// entities.
Address copiedAddress = address.copy(idFactory.nextAddressId());
...
}
但是,我真的一点也不喜欢这种设计,因为它显然会让第一眼看到这种方法的客户感到困惑:"Why do I have to pass the AddressIdFactory
when I want to change an existing address?"
客户端不需要,并且 不必 知道内部发生了什么,并且有可能必须在某个星座中创建新地址。这也引出了我的下一个论点,我们也可以重构你所说的整个方法,但这总是会导致客户端负责传递新的 AddressId 或传递 xyz DomainService 作为方法参数的设计,这我认为这根本不是最佳选择。
正因为如此,我正在考虑将依赖注入作为一种例外情况,以便能够保持 "When" 和 "How" 的逻辑以在聚合中创建一个新地址为了聚合的客户的简单性。
问题是如何。是的,我们正在使用 Spring,不,我不能只使用自动装配 DomainService,因为整个聚合是一个持久的 Spring 数据 MongoDb 对象,其中 DomainObject 在 JSON 期间被反序列化运行。
@Aggregate
@Document(collection = "useraddresses)
public class UserAddresses extends Entity {
// When reading from MongoDb the object's creation lays in the responsibility of
// Spring Data MongoDb, not Spring, therefore Spring cannot inject the dependency
// at all.
@Autowired
private AddressIdFactory addressIdFactory;
@PersistenceConstructor
public UserAddresses(String id, Map<String, Address> userAddresses) {
setUserAddressesId(new UserAddressesId(id));
if (userAddresses != null) {
this.userAddresses.putAll(userAddresses);
}
}
当然,我可以通过调用 setter() 或其他方式在存储库中手动注入该依赖项,但这也很丑陋,因为我会有 public setter 在我的 "beatiful" 聚合上仅出于技术问题的目的。
另一个想法是使用反射将依赖项直接设置为私有字段,但是这也很丑陋,我猜这是一个轻微的性能缺陷。
我想知道你的方法会是什么样子?
So the solution to that is "Inversion of Control", which is really just a pretentious way of saying "taking an argument".
我不怪你根本不喜欢这个设计;确实感觉模型的实现细节正在泄露给应用程序。
谜语的部分答案是:您可以通过接口将应用程序与实现细节隔离开来。
interface UserAddresses {
AddressId changeAddress(AddressId addressId, AddressChangeDto dto);
}
因此,当应用程序从存储库加载 "the aggregate" 时,它会获得一个实现此接口的 东西。
{
UserAddresses root = repository.get(...)
root.changeAddress(addressId, dto)
}
但是 应用程序不必直接与聚合 "root entity" 对话;应用程序可能正在与适配器通信。
Adapter::changeAddress(AddressId addressId, AddressChangeDto dto) {
this.target.changeAddress(addressId, dto, this.addressFactory);
}
同样 "the" 应用程序正在与之通信的存储库是存储库周围的一个适配器,用于访问数据存储,并带有将地址工厂注入适配器的附加管道。
我的问题是关于你们在实践中如何将 Domain- 或 InfrastructureServices 注入 DDD 聚合。我确信在 general 中允许在 DomainObjects 中进行 DependencyInjection 是一个坏主意,因为根据我的经验,它会鼓励轻率的开发人员使用它做一些令人讨厌的事情。但是可以肯定的是,在 Domainobjects 中的 DependencyInjection 可能有意义的例外情况总是存在的,尤其是当它有利于可读性和简单性时。
就我而言,我正在尝试解决如何创建 aggregate
的 root
.
Ids
的问题
假设我们有一个 UserAddresses
聚合,其中包含 Address
个实体列表作为其聚合根。让我们进一步假设我们在该聚合中有一个方法 changeAddress(AddressId addressId, AddressChangeDto dto)
:
public AddressId changeAddress(AddressId addressId, AddressChangeDto dto) {
Address address = nullableAddress(addressId);
if (address == null) {
// Doesn't matter for this question
} else if (address.hasChanged(dto)) {
address = changeAddress(address, dto);
}
}
和私有 changeAddress(Address address, AddressChangeDto dto)
方法简化如下:
private Address changeAddress(Address address, AddressChangeDto dto) {
if (!addressCopyNeeded(address)) {
address.change(dto);
return address;
}
// Address is both Shipping- & BillingAddress, hence it must be copied to 2 separate
// entities.
Address copiedAddress = address.copy(new AddressId()); // <-- New AddressId created
...
}
现在我想重构 AddressId
的创建,它是从驻留在我的 DomainModel 中的 AddressIdFactory
DomainService
创建的,由驻留在基础设施中的 MongoDb 存储库支持,以从 MongoDb 创建性能优化的 ObjectId
,我更喜欢长 UUID。
可以通过将该依赖项添加为 形式参数 来轻松解决这个问题,如下所示:
public AddressId changeAddress(AddressId addressId, AddressChangeDto dto, AddressIdFactory idFactory) {
Address address = nullableAddress(addressId);
if (address == null) {
// Doesn't matter for this question
} else if (address.hasChanged(dto)) {
address = changeAddress(address, dto, idFactory);
}
}
private Address changeAddress(Address address, AddressChangeDto dto, AddressIdFactory idFactory) {
if (!addressCopyNeeded(address)) {
address.change(dto);
return address;
}
// Address is both Shipping- & BillingAddress, hence it must be copied to 2 separate
// entities.
Address copiedAddress = address.copy(idFactory.nextAddressId());
...
}
但是,我真的一点也不喜欢这种设计,因为它显然会让第一眼看到这种方法的客户感到困惑:"Why do I have to pass the AddressIdFactory
when I want to change an existing address?"
客户端不需要,并且 不必 知道内部发生了什么,并且有可能必须在某个星座中创建新地址。这也引出了我的下一个论点,我们也可以重构你所说的整个方法,但这总是会导致客户端负责传递新的 AddressId 或传递 xyz DomainService 作为方法参数的设计,这我认为这根本不是最佳选择。
正因为如此,我正在考虑将依赖注入作为一种例外情况,以便能够保持 "When" 和 "How" 的逻辑以在聚合中创建一个新地址为了聚合的客户的简单性。
问题是如何。是的,我们正在使用 Spring,不,我不能只使用自动装配 DomainService,因为整个聚合是一个持久的 Spring 数据 MongoDb 对象,其中 DomainObject 在 JSON 期间被反序列化运行。
@Aggregate
@Document(collection = "useraddresses)
public class UserAddresses extends Entity {
// When reading from MongoDb the object's creation lays in the responsibility of
// Spring Data MongoDb, not Spring, therefore Spring cannot inject the dependency
// at all.
@Autowired
private AddressIdFactory addressIdFactory;
@PersistenceConstructor
public UserAddresses(String id, Map<String, Address> userAddresses) {
setUserAddressesId(new UserAddressesId(id));
if (userAddresses != null) {
this.userAddresses.putAll(userAddresses);
}
}
当然,我可以通过调用 setter() 或其他方式在存储库中手动注入该依赖项,但这也很丑陋,因为我会有 public setter 在我的 "beatiful" 聚合上仅出于技术问题的目的。
另一个想法是使用反射将依赖项直接设置为私有字段,但是这也很丑陋,我猜这是一个轻微的性能缺陷。
我想知道你的方法会是什么样子?
So the solution to that is "Inversion of Control", which is really just a pretentious way of saying "taking an argument".
我不怪你根本不喜欢这个设计;确实感觉模型的实现细节正在泄露给应用程序。
谜语的部分答案是:您可以通过接口将应用程序与实现细节隔离开来。
interface UserAddresses {
AddressId changeAddress(AddressId addressId, AddressChangeDto dto);
}
因此,当应用程序从存储库加载 "the aggregate" 时,它会获得一个实现此接口的 东西。
{
UserAddresses root = repository.get(...)
root.changeAddress(addressId, dto)
}
但是 应用程序不必直接与聚合 "root entity" 对话;应用程序可能正在与适配器通信。
Adapter::changeAddress(AddressId addressId, AddressChangeDto dto) {
this.target.changeAddress(addressId, dto, this.addressFactory);
}
同样 "the" 应用程序正在与之通信的存储库是存储库周围的一个适配器,用于访问数据存储,并带有将地址工厂注入适配器的附加管道。