Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/285.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/350.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在DDD中的何处放置全局规则验证_C#_Java_Domain Driven Design - Fatal编程技术网

C# 在DDD中的何处放置全局规则验证

C# 在DDD中的何处放置全局规则验证,c#,java,domain-driven-design,C#,Java,Domain Driven Design,我是DDD新手,我正在尝试将其应用到现实生活中。对于这样的验证逻辑,如空检查、空字符串检查等,并没有任何问题,它们直接进入实体构造函数/属性。但是,将一些全局规则(如“唯一用户名”)的验证放在哪里呢 所以,我们有实体用户 public class User : IAggregateRoot { private string _name; public string Name { get { return _name; } set { _name = v

我是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);
}
更新:我们可以使用DI通过规范对象隐藏用户和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

实体的全局验证逻辑放在哪里?

谢谢

创建域服务

或者我可以为创建域服务 创建和编辑用户。但是 有人可以直接编辑用户名 不打那个电话

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);
    }
}

如果您正确设计了实体,这应该不是问题。

编辑:从其他答案判断,此类“域服务”的正确名称是规范。我更新了我的答案以反映这一点,包括一个更详细的代码示例

我会选择选项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.
    }
}
var userRepository = IoC.Resolve<IUserRepository>();
var specification = new UniqueUserNameSpecification(userRepository);

user.SetName("John", specification);
公共类用户
{
公共字符串名称{get;private set;}
public void SetName(字符串名称,isspecification规范)
{
//在此处插入基本空验证。
如果(!specification.Issatifiedby(此,名称))
{
//抛出一些验证异常。
}
this.Name=Name;
}
}
公共接口规范
{
布尔值由(t类型obj,t值)确定;
}
公共类UniqueUserNameSpecification:isSpecification
{
私有IUSER存储库;
公共UniqueUserNameSpecification(IUserRepository存储库)
{
this.repository=存储库;
}
public bool Issatifiedby(用户对象,字符串值)
{
if(值==obj.Name)
{
返回true;
}
//使用this.repository进一步验证名称。
}
}
您的呼叫代码如下所示:

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.
    }
}
var userRepository = IoC.Resolve<IUserRepository>();
var specification = new UniqueUserNameSpecification(userRepository);

user.SetName("John", specification);
var userRepository=IoC.Resolve();
var规范=新的UniqueUserNameSpecification(userRepository);
user.SetName(“John”,规范);

当然,您可以在单元测试中模拟
isspecification
,以便于测试。

我不建议禁止更改实体中的属性,如果它是用户输入。 例如,如果验证未通过,您仍然可以使用实例在用户界面中显示验证结果,从而允许用户更正错误

Jimmy Nilsson在他的“应用领域驱动的设计和模式”中建议对特定操作进行验证,而不仅仅是持久化。虽然一个实体可以成功地持久化,但真正的验证发生在实体即将更改其状态时,例如“已订购”状态更改为“已购买”

创建时,实例必须有效才能保存,这涉及检查唯一性。它与订购时的有效性不同,订购时不仅必须检查唯一性,还必须检查客户的可信性和商店的可用性


因此,验证逻辑不应在属性分配上调用,而应在聚合级操作上调用,无论它们是否持久。

我不是DDD方面的专家,但我问过自己同样的问题,这就是我想到的: 验证逻辑通常应进入构造函数/工厂和设置器。这样可以保证始终具有有效的域对象。但是,如果验证涉及影响性能的数据库查询,那么高效的实现需要不同的设计

(1)注入实体:注入实体可能在技术上很困难,而且由于数据库逻辑的碎片化,使得管理应用程序性能非常困难。看似简单的操作现在可能会产生意想不到的性能影响。它还使您无法优化域对象,以便对同类实体的组执行操作,您不再可以编写单个组查询,而是始终对每个实体执行单独的查询

(2)注入存储库:您不应该在存储库中放置任何业务逻辑。保持存储库简单且集中。它们应该像集合一样工作,只包含用于添加、删除和查找对象的逻辑(有些甚至将find方法派生到其他对象)

(3)域服务这似乎是处理需要数据库查询的验证的最合理的地方。一个好的实现将使涉及包的构造函数/工厂和设置器私有,因此只能使用域服务创建/修改实体。

我将使用规范来封装规则。然后,您可以在更新UserName属性时(或从可能需要它的任何其他地方)调用:

如果其他开发人员试图直接修改
User.Name
,这无关紧要,因为rul
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);
}
if (cmdService.ValidateCommand(myCommand) == ValidationResult.OK)
{
    // Now we can assume there will be no business reason to reject
    // the command
    cmdService.ExecuteCommand(myCommand); // Async
}
public ValidationResult ValidateCommand(MakeCustomerGold command)
{
    var result = new ValidationResult();
    if (Customer.CanMakeGold(command.CustomerId))
    {
        // "OK" logic here
    } else {
        // "Not OK" logic here
    }
}
public interface IUser
{
    string Name { get; }
    bool IsNew { get; }
}

public class User : IUser
{
    public string Name { get; private set; }
    public bool IsNew { get; private set; }
}

public class UserService : IUserService
{
    public void ValidateUser(IUser user)
    {
        var repository = RepositoryFactory.GetUserRepository(); // use IoC if needed

        if (user.IsNew && repository.UserExists(user.Name))
            throw new ValidationException("Username already exists");
    }
}