我是DDD的新手,并且正在尝试将其应用于现实生活中。毫无疑问,此类验证逻辑(如空检查,空字符串检查等)直接进入实体构造函数/属性。但是,如何验证某些全球规则(例如“唯一用户名”)呢?
因此,我们有实体用户
public class User : IAggregateRoot { private string _name; public string Name { get { return _name; } set { _name = value; } } // other data and behavior }
和用户存储库
public interface IUserRepository : IRepository<User> { User FindByName(string name); }
选项有:
并且每个选项都更加详细:
1。将存储库注入实体
我可以查询实体构造函数/属性中的存储库。但是我认为在实体中引用存储库是一种难闻的气味。
public User(IUserRepository repository) { _repository = repository; } public string Name { get { return _name; } set { if (_repository.FindByName(value) != null) throw new UserAlreadyExistsException(); _name = value; } }
更新:我们可以使用DI通过Specification对象隐藏User和IUserRepository之间的依赖关系。
2.将存储库注入工厂
我可以将此验证逻辑放在UserFactory中。但是,如果我们想更改现有用户的名称怎么办?
3.在域服务上创建操作
我可以创建用于创建和编辑用户的域服务。但是有人可以直接编辑用户名而无需调用该服务…
public class AdministrationService { private IUserRepository _userRepository; public AdministrationService(IUserRepository userRepository) { _userRepository = userRepository; } public void RenameUser(string oldName, string newName) { if (_userRepository.FindByName(newName) != null) throw new UserAlreadyExistException(); User user = _userRepository.FindByName(oldName); user.Name = newName; _userRepository.Save(user); } }
4。???
您将实体的全局验证逻辑放在哪里?
谢谢!
在大多数情况下,最好将此类规则放在Specification对象中。您可以将这些Specification放入您的域包中,因此任何使用您的域包的人都可以访问它们。使用规范,您可以将业务规则与实体捆绑在一起,而无需创建对服务和存储库具有不期望依赖性的难以阅读的实体。如果需要,可以将对服务或存储库的依赖项注入到规范中。
Specification
根据上下文,您可以使用规范对象构建不同的验证器。
实体的主要关注点应该是跟踪业务状态-这足以承担责任,并且他们不应该关注验证。
例
public class User { public string Id { get; set; } public string Name { get; set; } }
两种规格:
public class IdNotEmptySpecification : ISpecification<User> { public bool IsSatisfiedBy(User subject) { return !string.IsNullOrEmpty(subject.Id); } } public class NameNotTakenSpecification : ISpecification<User> { // omitted code to set service; better use DI private Service.IUserNameService UserNameService { get; set; } public bool IsSatisfiedBy(User subject) { return UserNameService.NameIsAvailable(subject.Name); } }
和验证器:
public class UserPersistenceValidator : IValidator<User> { private readonly IList<ISpecification<User>> Rules = new List<ISpecification<User>> { new IdNotEmptySpecification(), new NameNotEmptySpecification(), new NameNotTakenSpecification() // and more ... better use DI to fill this list }; public bool IsValid(User entity) { return BrokenRules(entity).Count() > 0; } public IEnumerable<string> BrokenRules(User entity) { return Rules.Where(rule => !rule.IsSatisfiedBy(entity)) .Select(rule => GetMessageForBrokenRule(rule)); } // ... }
为了完整起见,这些接口:
public interface IValidator<T> { bool IsValid(T entity); IEnumerable<string> BrokenRules(T entity); } public interface ISpecification<T> { bool IsSatisfiedBy(T subject); }
笔记
我认为Vijay Patel的较早答案是朝正确的方向发展,但我觉得有些偏离。他建议用户实体取决于规范,我认为应该与规范相反。这样,您可以使规范通常依赖于服务,存储库和上下文,而无需通过规范依赖关系使您的实体依赖于它们。
参考文献
埃里克·埃文斯(Eric Evans)在第9章145页中介绍了使用规范模式进行验证,选择和对象构建。
该文章的规格模式:在.NET应用程序可能是你的兴趣。