我的问题是你们在实践中将域名或基础设施服务注入你的DDD聚合中。我确信在DomainObjects中允许使用DependencyInjection是一个坏主意,因为根据我的经验,它鼓励rash开发人员使用它做一些令人讨厌的事情。但是,确实存在一些特殊情况,其中Domainobjects中的DependencyInjection可能有意义,特别是当它有利于可读性和简单性时。
就我而言,我正在尝试解决如何创建Ids
aggregate
root
的新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
现在,我想重构一个AddressIdFactory
创建来创建DomainService
ObjectId
,这个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());
...
}
AddressIdFactory
位于我的DomainModel中,由驻留在基础架构中的MongoDbRepository支持,以创建性能优化{{1来自MongoDb,我更喜欢长UUID。
可以通过将依赖项添加为形式参数来轻松解决这个问题:
@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);
}
}
然而,我真的不喜欢那个设计,因为它在第一眼看上去很明显地混淆了该方法的客户:“当我想要更改现有地址时,为什么我必须通过user
?”
客户端没有,并且不应该知道内部正在发生什么,并且有可能必须在某个星座中创建新地址。这也导致我的下一个论点,我们也可以重构你可以说的整个方法,但是这总是会导致一个设计,客户端负责传递一个新的AddressId或者传入一个xyz DomainService作为方法参数,我觉得这根本不是最优的。
因为我正在考虑做依赖注入,因为这是一个例外情况,能够保持“When”和“How”的逻辑在聚合中创建一个新地址,以简化聚合客户。
问题是如何。是的,我们正在使用Spring而不是我不能仅使用自动装配DomainService,因为整个聚合是一个持久化的Spring Data MongoDb对象,其中DomainObject在运行时从JSON反序列化。
{
"users": {
"123321": { <!-- is a random number which can be any number -->
"user": {
"id": "123321",
"name": "Bob"
...
}
},
"456654": {
"user": {
"id": "456654",
"name": "Foo"
}
}
...
}
当然,我可以通过调用setter()或其他东西在Repository中手动注入该依赖项,但是这也很难看,因为我的“beatiful”聚合上的公共setter只是为了技术问题。
另一个想法是使用反射将依赖项直接设置为私有字段,但是这也是丑陋的,我认为这是一个轻微的性能缺陷。
我想知道你的方法会是什么样子?
答案 0 :(得分:2)
因此解决方案就是&#34;控制倒置&#34;,这实际上只是一种自命不凡的方式来说&#34;采取争论&#34;。
我不会责怪你根本不喜欢这个设计;它确实感觉模型的实现细节已泄露给应用程序。
这个问题的部分答案是:您可以使用接口将应用程序与实现细节隔离开来。
interface UserAddresses {
AddressId changeAddress(AddressId addressId, AddressChangeDto dto);
}
因此当应用程序加载&#34;聚合&#34;从存储库中,它获得了实现此接口的 something 。
{
UserAddresses root = repository.get(...)
root.changeAddress(addressId, dto)
}
但并不是必须要求应用程序直接与&#34; root实体进行对话&#34;总计;应用程序可以与适配器通信。
Adapter::changeAddress(AddressId addressId, AddressChangeDto dto) {
this.target.changeAddress(addressId, dto, this.addressFactory);
}
同样&#34;&#34;&#34;应用程序正在与之交互的存储库是访问数据存储的存储库周围的适配器,以及将地址工厂注入适配器的其他管道。