这似乎是一个基本问题,而且我对 DDD 的研究不多。这个问题的背景是关于使用 ABP 框架、它的层和它生成的代码。
我应该强制相关聚合根之间的引用完整性吗?如果是的话,在哪里?
在使用 ABP Suite 生成我的初始实体(其中许多是聚合根(AR))之后,我开始实现它们之间的导航属性。我最初的方法是修改每个实体/AR 的构造函数以包含依赖实体/AR 的 Guid ID。
原始方法
public Address(Guid id, string name, AddressTypes addressType,
string line1, string line2, string line3,
string city, string postalCode,
Guid stateId, Guid countryId, // <-- dependent AR IDs
bool primary = false, bool active = true)
{
//other properties set
StateId = stateId;
CountryId = countryId;
//etc
}
我让我的域数据种子逻辑与我的数据种子贡献者一起工作,然后我继续处理测试数据。
很快我意识到我必须创建每个依赖实体/AR 并将它们的 ID 传递给每个被测试实体的构造函数。
这促使我在文档中搜索示例或最佳实践。我遇到的一件事是来自 Entity Best Practices & Conventions 的声明页面:
Do always reference to other aggregate roots by Id. Never add navigation properties to other aggregate roots.
好的。因此,这似乎表明我应该在每个从属/子 AR 的主体/父 AR 中具有可为空的 ID 属性。 AR 下的完全托管实体可能可以自由地拥有导航属性,但 AR 则不然。
因为我想在更高级别管理 AR 关系,所以我似乎应该从构造函数中删除依赖的 AR,或者至少使它们可以为空。
修订方法 1
public Address(Guid id, string name, AddressTypes addressType,
string line1, string line2, string line3,
string city, string postalCode,
Guid? stateId = null, Guid? countryId = null, // <-- nullable dependent AR IDs
bool primary = false, bool active = true)
{
//other properties set
StateId = stateId;
CountryId = countryId;
//etc
}
修改后的方法 2
public Address(Guid id, string name, AddressTypes addressType,
string line1, string line2, string line3,
string city, string postalCode,
bool primary = false, bool active = true)
{
//other properties set, not setting dependent IDs
}
显然修改后的方法 2 需要应用程序服务在构造对象后设置依赖 ID 属性:
Address address = null;
address = await _addressRepository.InsertAsync(new Address
(
id: _guidGenerator.Create(),
name: "DEMO Contact Home Address",
addressType: AddressTypes.Home,
line1: "123 Main St",
line2: "",
line3: "",
city: "Anytown",
postalCode: "00000",
primary: true,
active: true
), autoSave: true);
address.StateId = alState.Id; // set here
address.CountryId = usId; // and here
到目前为止我是否步入正轨?
因此,如果我想强制引用完整性,我不应该通过 Entity Framework (或选择的 ORM)来做到这一点。我应该在应用程序服务层强制引用完整性。
[Authorize(MyProductPermissions.Addresses.Create)]
public virtual async Task<AddressDto> CreateAsync(AddressCreateDto input)
{
if (input.StateId == default)
{
throw new UserFriendlyException(L["The {0} field is required.", L["State"]]);
}
if (input.CountryId == default)
{
throw new UserFriendlyException(L["The {0} field is required.", L["Country"]]);
}
var address = ObjectMapper.Map<AddressCreateDto, Address>(input);
address.TenantId = CurrentTenant.Id;
// BEGIN Referential Integrity Logic
// Assumes that the dependent IDs will be set when the DTO is mapped to the entity
if (address.StateId == null)
{
// log and throw 500 error
}
if (address.CountryId == null)
{
// log and throw 500 error
}
// END Referential Integrity Logic
address = await _addressRepository.InsertAsync(address, autoSave: true);
return ObjectMapper.Map<Address, AddressDto>(address);
}
这样的理解正确吗?
如果我的依赖 ID 可为空,我可以继续使用 ABP Suite 生成的测试代码。
await _addressRepository.InsertAsync(new Address
(
Guid.Parse("ca846f1a-8bbd-4e2c-afbd-8e40a03ae18f"),
"7d7b348e410d48ee89e1807beb2f2ac0bd66af4ea82943ec8eee3a52962577b1",
default,
"de5ec0226aba4c1a837c9716b21af6551d10436756724d4fa507028eaaddcdadec779bea0ef04922992f9d2432068b180e6fe95f425f47c68559c1dbd4360fdb",
"53bc12edeb4544158147f3b835b0c4ce5e581844f5c248d69647d80d398706f5ee1c769e4ee14bd0a1e776a369a96ea3c0582b659ce342bdbdf40e6668f3b9f9",
"117880188dfd4a6f96892fea3e62a16f057748ebe76b4dd0a4402918e2fee9055272ff81c53d4c28825cc20d01918386864efd54e1aa458bb449a1d12b349d40",
"866a81007219411a971be2133bf4b5882d4ef612722a45ac91420e0b30d774ed",
"93bba338449444f5",
true,
true
));
如果不是,我将必须增强测试代码,为与我的 AR 关联的每个依赖实体类型创建至少一个依赖实体。
// Create dependent State entity
var alState = //...
// Create dependent Country entity
var country = //...
Address address = null;
address = await _addressRepository.InsertAsync(new Address
(
Guid.Parse("ca846f1a-8bbd-4e2c-afbd-8e40a03ae18f"),
"7d7b348e410d48ee89e1807beb2f2ac0bd66af4ea82943ec8eee3a52962577b1",
default,
"de5ec0226aba4c1a837c9716b21af6551d10436756724d4fa507028eaaddcdadec779bea0ef04922992f9d2432068b180e6fe95f425f47c68559c1dbd4360fdb",
"53bc12edeb4544158147f3b835b0c4ce5e581844f5c248d69647d80d398706f5ee1c769e4ee14bd0a1e776a369a96ea3c0582b659ce342bdbdf40e6668f3b9f9",
"117880188dfd4a6f96892fea3e62a16f057748ebe76b4dd0a4402918e2fee9055272ff81c53d4c28825cc20d01918386864efd54e1aa458bb449a1d12b349d40",
"866a81007219411a971be2133bf4b5882d4ef612722a45ac91420e0b30d774ed",
"93bba338449444f5",
true,
true
));
address.StateId = state.Id;
address.CountryId = country.Id;
这可能会成为我的层次结构中的许多对象,该层次结构目前约有 30 个实体/AR。多级依赖关系加剧了这种情况。
请帮助我了解 DDD 世界中的最佳实践。在开始实现 30 个奇怪的构造函数和应用程序服务之前,我需要先解决这个问题。
最佳答案
如果您的 Address
实体必须使用指定的 StateId
和 CountryId
创建,您需要使用原始方法并强制设置它们的值,同时对象创建。因为,聚合根有责任维护其自身的完整性。 (参见相关
documentation了解更多信息)
- 我猜您还会问如果您的数据库中不存在
StateId
并且它只是一个简单的 GUID,会发生什么情况。在这种情况下,如果您将 StateId 设置为外键,它将不会添加到您的数据库中。但是,如果您想以任何方式查询它并在不存在时抛出异常,您可以创建一个域服务并检查是否存在具有给定 stateId 的状态,如果存在则将其传递给 Address 构造函数(如果不存在)抛出异常)并在数据库中创建新的地址记录。
public class AddressManager : DomainService
{
private readonly IAddressRepository _addressRepository;
private readonly IStateRepository _stateRepository;
private readonly ICountryRepository _countryRepository;
public AddressManager(IAddressRepository addressRepository, IStateRepository stateRepository, ICountryRepository countryRepository)
{
_addressRepository = addressRepository;
_stateRepository = stateRepository;
_countryRepository = countryRepository;
}
public async Task CreateAsync(string name, AddressTypes addressType,
string line1, string line2, string line3,
string city, string postalCode,
Guid stateId, Guid countryId)
{
if(await _stateRepository.FindAsync(stateId))
{
//throw exception
return;
}
if(await _countryRepository.FindAsync(stateId))
{
//throw exception
return;
}
var address = new Address(GuidGenerator.Create(), AddressTypes.Typee, "line1", "line2", "line3", "city", "postalCode", stateId, countryId);
await _addressRepository.InsertAsync(address);
}
}
创建新地址时,请在应用服务中调用 AddressManager 的 CreateAsync 方法。 (您可能希望将 Address 实体构造函数设置为内部而不是公共(public),以防止在应用程序层错误地创建 Address 对象。)
关于entity-framework-core - 我应该在相关聚合根之间强制执行引用完整性吗?如果是的话,在哪里?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69980421/