DDD聚合根Person的地址更新与初始化问题咨询
针对DDD项目中Person聚合的两个问题解决方案
问题1:更新Person地址时的业务规则验证
Addresses对外是只读集合,外部直接修改集合内的Address对象违反聚合根封装原则——聚合根必须掌控所有内部状态的修改逻辑,确保业务规则全程被遵守。
正确做法是在Person聚合根内暴露修改地址的专属方法,所有地址修改操作都通过该方法完成,同时内嵌业务规则校验:
代码调整示例:
public class Person // AggregateRoot { public Guid Id { get; set; } public Guid StatusId { get; set; } private List<Address> _addresses = new List<Address>(); public IReadOnlyCollection<Address> Addresses => _addresses.AsReadOnly(); // 修改地址的方法,外部只能通过此方法修改地址 public void UpdateAddress(Guid addressId, string newAddress1, Guid newAddressTypeId) { var targetAddress = _addresses.FirstOrDefault(a => a.Id == addressId); if (targetAddress == null) { throw new InvalidOperationException("指定地址不存在"); } // 执行修改 targetAddress.Address1 = newAddress1; targetAddress.AddressTypeId = newAddressTypeId; // 校验业务规则 ValidateAddressBusinessRules(); } // 原有AddAddress方法同样需要校验规则 public void AddAddress(Address address) { _addresses.Add(address); ValidateAddressBusinessRules(); } // 封装所有地址相关的业务规则校验 private void ValidateAddressBusinessRules() { // 示例规则1:必须至少有1个指定类型的地址(比如居住地址) var requiredTypeId = Guid.Parse("你的指定地址类型ID"); var requiredAddressCount = _addresses.Count(a => a.AddressTypeId == requiredTypeId); if (requiredAddressCount < 1) { throw new BusinessRuleViolationException("Person必须至少拥有一个居住地址"); } // 示例规则2:地址总数不能超过5个(可根据实际需求扩展) if (_addresses.Count > 5) { throw new BusinessRuleViolationException("Person的地址数量不能超过5个"); } } } // 自定义领域异常,用于抛出业务规则违反错误 public class BusinessRuleViolationException : Exception { public BusinessRuleViolationException(string message) : base(message) { } }
核心逻辑:
- 聚合根垄断内部状态修改权限,外部无法直接操作
Address对象,确保所有修改都经过规则校验。 - 将业务规则集中封装在私有方法中,所有可能改变地址集合的操作(新增/修改/删除)都调用该方法,避免重复代码。
- 一旦违反规则,立即抛出领域异常,确保聚合根始终处于有效状态。
问题2:创建Person时至少包含2个地址的初始化方式
DDD要求聚合根必须始终处于有效状态,因此绝对不能允许创建地址数量不足的Person对象。最佳实现方式是通过构造函数强制校验或工厂方法确保初始化合规:
方案1:构造函数强制校验(推荐)
public class Person // AggregateRoot { public Guid Id { get; } public Guid StatusId { get; } private List<Address> _addresses = new List<Address>(); public IReadOnlyCollection<Address> Addresses => _addresses.AsReadOnly(); // 私有构造函数,禁止外部直接实例化 private Person(Guid id, Guid statusId, List<Address> addresses) { Id = id ?? throw new ArgumentNullException(nameof(id)); StatusId = statusId; _addresses = addresses ?? throw new ArgumentNullException(nameof(addresses)); // 初始化时立即校验规则 ValidateMinimumAddressCount(); ValidateAddressBusinessRules(); } // 静态工厂方法,对外提供唯一创建入口 public static Person Create(Guid id, Guid statusId, List<Address> addresses) { return new Person(id, statusId, addresses); } private void ValidateMinimumAddressCount() { if (_addresses.Count < 2) { throw new BusinessRuleViolationException("创建Person时必须至少包含2个地址"); } } // 原有方法省略... }
方案2:构建器模式(适用于需要分步初始化的场景)
如果需要分步添加地址再完成创建,可使用构建器模式,最终构建时强制校验规则:
public class PersonBuilder { private Guid _id; private Guid _statusId; private List<Address> _addresses = new List<Address>(); public PersonBuilder WithId(Guid id) { _id = id; return this; } public PersonBuilder WithStatusId(Guid statusId) { _statusId = statusId; return this; } public PersonBuilder AddAddress(Address address) { _addresses.Add(address); return this; } public Person Build() { if (_addresses.Count < 2) { throw new BusinessRuleViolationException("创建Person时必须至少包含2个地址"); } return Person.Create(_id, _statusId, _addresses); } } // 使用方式 var person = new PersonBuilder() .WithId(Guid.NewGuid()) .WithStatusId(activeStatusId) .AddAddress(homeAddress) .AddAddress(workAddress) .Build();
核心逻辑:
- 禁止外部通过无参构造函数直接创建Person,确保所有创建路径都经过规则校验。
- 无论是构造函数还是构建器,都在Person实例化完成前验证地址数量规则,从根源避免无效对象流入系统。
内容的提问来源于stack exchange,提问作者Dylan
相关产品推荐
相关产品推荐

