Ser*_*kiy 62 c# java domain-driven-design
我是DDD的新手,我正试图在现实生活中应用它.没有关于这种验证逻辑的问题,如空检查,空字符串检查等 - 直接进入实体构造函数/属性.但是在哪里验证一些全局规则,如"唯一用户名"?
所以,我们有实体用户
public class User : IAggregateRoot
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
// other data and behavior
}
Run Code Online (Sandbox Code Playgroud)
和用户存储库
public interface IUserRepository : IRepository<User>
{
User FindByName(string name);
}
Run Code Online (Sandbox Code Playgroud)
选项包括:
每个选项更详细:
1.将存储库注入实体
我可以在实体构造函数/属性中查询存储库.但我认为在实体中保持对存储库的引用是一种难闻的气味.
public User(IUserRepository repository)
{
_repository = repository;
}
public string Name
{
get { return _name; }
set
{
if (_repository.FindByName(value) != null)
throw new UserAlreadyExistsException();
_name = value;
}
}
Run Code Online (Sandbox Code Playgroud)
更新:我们可以使用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);
}
}
Run Code Online (Sandbox Code Playgroud)
4. ???
您在哪里为实体设置全局验证逻辑?
谢谢!
Mar*_*ijn 56
大多数情况下,最好将这些规则放在Specification
对象中.您可以将这些Specification
s放在域包中,这样任何使用域包的人都可以访问它们.使用规范,您可以将业务规则与实体捆绑在一起,而不会创建对服务和存储库具有不良依赖性的难以读取的实体.如果需要,您可以将服务或存储库的依赖项注入到规范中.
根据上下文,您可以使用规范对象构建不同的验证器.
实体的主要关注点应该是跟踪业务状态 - 这是一种责任,他们不应该关注验证.
例
public class User
{
public string Id { get; set; }
public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
两个规格:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
还有一个验证器:
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));
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
为了完整性,接口:
public interface IValidator<T>
{
bool IsValid(T entity);
IEnumerable<string> BrokenRules(T entity);
}
public interface ISpecification<T>
{
bool IsSatisfiedBy(T subject);
}
Run Code Online (Sandbox Code Playgroud)
笔记
我认为Vijay Patel先前的答案是正确的方向,但我觉得它有点偏.他建议用户实体依赖于规范,我相信这应该是另一种方式.这样,您可以让规范依赖于服务,存储库和上下文,而不会让您的实体通过规范依赖性依赖它们.
参考
一个相关的问题,例如一个很好的答案:域驱动设计中的验证.
Eric Evans在第9章第145页中描述了规范模式在验证,选择和对象构建中的使用.
关于 .Net中的应用程序的规范模式的这篇文章可能会引起您的兴趣.
Geo*_*voy 11
如果它是用户输入,我不建议不允许更改实体中的属性.例如,如果验证没有通过,您仍然可以使用实例在用户界面中显示验证结果,允许用户更正错误.
Jimmy Nilsson在他的"应用领域驱动的设计和模式"中建议验证特定操作,而不仅仅是为了持久化.虽然可以成功保持实体,但实体验证会在实体即将更改其状态时发生,例如"已订购"状态更改为"已购买".
在创建时,实例必须是有效的保存,这涉及检查唯一性.它与有效订购不同,不仅要检查唯一性,还要检查客户的可信度和商店的可用性.
因此,不应在属性赋值上调用验证逻辑,应在聚合级别操作时调用验证逻辑,无论它们是否持久.
编辑:从其他答案判断,这种"域名服务"的正确名称是规范.我已经更新了我的答案以反映这一点,包括更详细的代码示例.
我选择3; 创建一个域服务规范,它封装了执行验证的实际逻辑.例如,规范最初调用存储库,但您可以在稍后阶段将其替换为Web服务调用.拥有抽象规范背后的所有逻辑将使整体设计更加灵活.
为了防止某人编辑名称而不验证它,请使规范成为编辑名称的必要方面.您可以通过将实体的API更改为以下内容来实现此目的:
public class User
{
public string Name { get; private set; }
public void SetName(string name, ISpecification<User, string> specification)
{
// Insert basic null validation here.
if (!specification.IsSatisfiedBy(this, name))
{
// Throw some validation exception.
}
this.Name = name;
}
}
public interface ISpecification<TType, TValue>
{
bool IsSatisfiedBy(TType obj, TValue value);
}
public class UniqueUserNameSpecification : ISpecification<User, string>
{
private IUserRepository repository;
public UniqueUserNameSpecification(IUserRepository repository)
{
this.repository = repository;
}
public bool IsSatisfiedBy(User obj, string value)
{
if (value == obj.Name)
{
return true;
}
// Use this.repository for further validation of the name.
}
}
Run Code Online (Sandbox Code Playgroud)
你的调用代码看起来像这样:
var userRepository = IoC.Resolve<IUserRepository>();
var specification = new UniqueUserNameSpecification(userRepository);
user.SetName("John", specification);
Run Code Online (Sandbox Code Playgroud)
当然,您可以ISpecification
在单元测试中进行模拟以便于测试.