Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/spring-mvc/2.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
Architecture 存储库是否应该抛出域错误_Architecture_Clean Architecture_Ddd Repositories - Fatal编程技术网

Architecture 存储库是否应该抛出域错误

Architecture 存储库是否应该抛出域错误,architecture,clean-architecture,ddd-repositories,Architecture,Clean Architecture,Ddd Repositories,我正在构建一个尝试遵守干净体系结构的应用程序。我理解存储库是为了抽象掉持久层并返回域语言方面的实体。但是,这是否意味着如果出现问题,它也应该检查并抛出域错误。让我们考虑一个情况,我想通过用户存储库添加一个用户。我可以做到以下几点: //在用户repo中 常量添加=(用户:用户):void=>{ 试一试{ //做一些数据库工作 }捕获(){ 抛出新的EmailAlreadyInUse(user.email); } } 但这种实施是否可取?我们现在依赖于数据库已正确设置,并使用正确的唯一密钥模式来

我正在构建一个尝试遵守干净体系结构的应用程序。我理解存储库是为了抽象掉持久层并返回域语言方面的实体。但是,这是否意味着如果出现问题,它也应该检查并抛出域错误。让我们考虑一个情况,我想通过用户存储库添加一个用户。我可以做到以下几点:

//在用户repo中
常量添加=(用户:用户):void=>{
试一试{
//做一些数据库工作
}捕获(){
抛出新的EmailAlreadyInUse(user.email);
}
}
但这种实施是否可取?我们现在依赖于数据库已正确设置,并使用正确的唯一密钥模式来实施域规则(没有两个用户可以使用同一电子邮件注册)。在我看来,我们可能会将域规则泄漏到perisitence层

从用例层抛出这个异常是否更有意义

const AddNewUserUseCase = (userRepository, email) => {
  const user = userRepository.findByEmail(email);
  if(user) {
    throw new EmailAlreadyInUseError(email)
  }
  else {
    const user = new User(email);
    userRepository.add(user);
  }
}

这将起作用并消除持久层中的任何溢出。但我必须在每个我想添加用户的地方都这样做。你会选择什么样的推荐模式?你有没有其他鼓励的方法?您将在何处执行这些检查以抛出错误。

完全依赖数据库功能来强制执行业务规则是一种糟糕的做法

也就是说,考虑到引发域异常需要进行一些业务验证检查,因此不应该从表示数据库(存储库)的类内部引发域异常

顾名思义,域异常应该在域(或应用程序)层内使用

因此,您的重复电子邮件验证应该放在用例中,然后是存储库操作(添加用户)。至于代码重复,解决方案很简单:使用包含这两个阶段逻辑(验证然后操作)的方法创建一个域服务,并在您喜欢的任何地方使用该服务


清洁架构的一个关键原则是形成一个稳定的域层,同时让基础设施细节可以交换。但是,当你把一个业务规则放在一个存储库(基础设施)中时,考虑一下如果你决定创建一个替代的存储库会发生什么:你必须记住将你的业务规则复制到新的存储库中。

知识库通常在用例层中声明,因为它们是用例需要什么的定义。因此,这些接口应该是面向领域的。由于它们必须在外层中实现,这意味着如果定义了域异常,外层必须引发域异常

但这种实施是否可取?我们现在依靠数据库已正确设置,并使用正确的唯一密钥模式来实施域规则(没有两个用户可以使用同一电子邮件注册)

从用例的角度来看,它不依赖于数据库特性。您所描述的是存储库接口的一种实现。我的意思是,如果实现是关系数据库的网关,您可能希望使用db contstraint来满足存储库接口的定义。但您可能会使用另一个数据库,甚至是内存中的数据库

主要的一点是,存储库接口以面向域的方式描述了它想要什么,而不是如何实现。通常,接口的本质是描述客户想要什么,而不是如何描述。因此,域约束仍然在接口处定义,例如

public interface UserRepository {

    /**
     *
     * throws an UserAlreadyExistsException if a user with the given email already exists.
     * returns the User created with the given arguments or throws an UserAlreadyExistsException. 
     *         Never returns null.
     */
    public User createUser(String email, ....) throws UserAlreadyExistsException;

}
请记住,接口不仅仅是一个方法签名。它有前置条件和后置条件,通常以非正式的方式描述

备选方案

例如,在Java中,如果希望实现遵循已定义的路径,也可以使用抽象类。因为我不知道您使用哪种语言,所以我将给您这个Java示例

public abstract class UserRepository {
   
     public User createUser(String email, ...) throws UserAlreadyExistsException {
        User user = findByEmail(email);

        if(user) {
            throw new UserAlreadyExistsException(email)
        } else {
            User user = new User(email);
            add(user);
        }
     }

     protected abstract findByEmail(String email);
     protected abstract add(User user);
}
但是当您使用抽象类时,您已经定义了实现的一部分。实现不像接口示例中那样自由。您的实现必须扩展抽象类。这可能是个问题,例如在Java中,因为Java不允许多重继承。因此,这取决于你使用的语言

结论

我将使用第一个示例,只定义一个抛出域异常的接口,并让实现选择如何执行

当然,这意味着我通常必须用集成测试来测试实现,不能使用非常快的单元测试。但是用例仍然可以通过单元测试进行快速测试


我认为干净的体系结构可以帮助您将大部分代码作为纯粹和快速的单元测试进行测试。我们应该尽量用一种简单的单元测试的方式来构造我们的代码。如果我们需要集成测试,我们应该尽可能快地进行测试,并且应该尽可能少地使用。

我正在考虑类似的问题,但是这个策略会产生一些问题。1.这是否意味着您现在正在存储库层中捕获域规则(不允许重复电子邮件)?2.如果发生基础结构错误而不是域错误(例如,无法访问数据库),您会怎么做?如果你犯了一个基础设施的错误,这不意味着合同已经破裂了吗?你说得很有道理。非常感谢您的投入。那么,这是否意味着您认为存储库在发生基础结构错误时应该抛出这些错误?您会在不同的存储库实现中标准化这些错误吗?或者,根据定义,存储库会引发由它们封装的远程资源引发的基础设施异常吗。您可能会发现,在某些情况下,让存储库方法捕获远程资源引发的异常非常有用,但无论如何,应用程序层不应该直接执行