我是 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);
}
选项是:
- 将存储库注入(inject)实体
- 将存储库注入(inject)工厂
- 在域服务上创建操作
- ???
每个选项更详细:
1 .将存储库注入(inject)实体
我可以在实体构造函数/属性中查询存储库。但我认为在实体中保留对存储库的引用是一种不好的气味。
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。将存储库注入(inject)工厂
我可以把这个验证逻辑放在 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
放在您的域包中,因此任何使用您的域包的人都可以访问它们。使用规范,您可以将您的业务规则与您的实体捆绑在一起,而不会创建难以阅读的实体以及对服务和存储库的不希望的依赖关系。如果需要,您可以将服务或存储库的依赖项注入(inject)到规范中。
根据上下文,您可以使用规范对象构建不同的 validator 。
实体的主要关注点应该是跟踪业务状态——这已经足够了,他们不应该关心验证。
例子
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);
}
}
还有一个 validator :
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));
}
// ...
}
为了完整性,接口(interface):
public interface IValidator<T>
{
bool IsValid(T entity);
IEnumerable<string> BrokenRules(T entity);
}
public interface ISpecification<T>
{
bool IsSatisfiedBy(T subject);
}
注意事项
我认为 Vijay Patel 之前的回答是朝着正确的方向,但我觉得有点不对劲。他建议用户实体取决于规范,我认为这应该是相反的方式。这样,您可以让规范依赖于服务、存储库和一般上下文,而无需通过规范依赖使您的实体依赖它们。
引用文献
一个很好回答的相关问题,例如:Validation in a Domain Driven Design .
Eric Evans 在 chapter 9, pp 145 中描述了使用规范模式进行验证、选择和对象构造。 .
这个 article on the specification pattern您可能会对 .Net 中的应用程序感兴趣。
关于c# - 在 DDD 中将全局规则验证放在哪里,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5818898/