repository - 领域驱动设计中的规范模式

标签 repository domain-driven-design repository-pattern specifications

我一直在努力解决与 DDD 相关的规范问题,并且我已经阅读了很多关于 DDD 和规范以及存储库的内容。
但是,如果在不破坏领域驱动设计的情况下尝试将所有这 3 种结合起来,就会出现问题。归结为如何在考虑性能的情况下应用过滤器。
首先是几个明显的事实:

  • 获取数据访问/基础设施层的存储库
  • 领域模型代表业务逻辑,进入领域层
  • 数据访问模型代表持久层,进入持久层/基础设施/数据访问层
  • 业务逻辑进入领域层
  • 规范是业务逻辑,所以它们也属于领域层。
  • 在所有这些示例中,存储库中使用了 ORM 框架和 SQL Server
  • 持久性模型可能不会泄漏到领域层

  • 到目前为止,很容易。当/如果我们尝试将规范应用于存储库而不破坏 DDD 模式或出现性能问题时,就会出现问题。
    应用规范的可能方法:
    1)经典方式:在领域层使用领域模型的规范
    应用传统的规范模式,带有 IsSatisfiedBy方法,返回 bool和复合规范组合多个规范。
    这让我们可以在领域层中保留规范,但是......
  • 它必须与领域模型一起使用,而存储库使用表示持久层数据结构的持久模型。使用映射器(例如 AutoMapper)可以轻松解决此问题。 .
  • 然而,无法解决的问题是:所有的规范都必须在内存中执行。在大表/数据库中,这意味着如果您必须遍历所有实体仅过滤掉满足您规范的实体,这意味着巨大的影响

  • 2) 使用持久性模型的规范
    这类似于 1),但在规范中使用持久性模型。这允许直接使用规范作为我们 .Where 的一部分谓词将被转换为查询(即 TSQL),过滤将在持久性存储(即 SQL Server)上执行。
  • 虽然这给了我们很好的性能,但它显然违反了 DDD 模式。我们的持久化模型泄漏到域层,使域层依赖于持久层而不是相反。

  • 3) 与 2) 类似,但将规范作为持久层的一部分
  • 这不起作用,因为域层需要引用规范。它仍然取决于持久层。
  • 我们将在持久层中拥有业务逻辑。这也违反了 DDD 模式

  • 4) 与 3 类似,但将规范抽象为接口(interface)
    我们将在我们的领域层中有规范接口(interface),在持久层中有规范的具体实现。现在我们的领域层只会与接口(interface)交互,而不依赖于持久层。
  • 这仍然违反了 3) 中的 #2)。我们将在持久层中有业务逻辑,这很糟糕。

  • 5) 将表达式树从领域模型转化为持久性模型
    这当然解决了问题,但这是一项重要的任务,但它会将规范保留在我们的领域层中,同时仍然受益于 SQL 优化,因为规范成为存储库 Where 子句的一部分并转换为 TSQL
    我尝试采用这种方法,但存在几个问题(从实现方面来看):
  • 我们需要知道 Mapper 的配置(如果我们使用)或保留我们自己的映射系统。这可以部分完成(读取 Mapper 配置),即 AutoMapper,但存在更多问题
  • 模型 A 的一个属性映射到模型 B 的一个属性是可以接受的。 如果类型不同(即由于持久性类型,例如,枚举被保存为另一个表中的字符串或键/值对,则变得更加困难)我们需要在解析器内部进行转换。
  • 如果将多个字段映射到一个目标字段中,则会变得非常复杂。我相信这对于域模型 -> 持久性模型映射来说不是问题

  • 6) Query Builder like API
    最后一个是制作某种查询 API,该 API 被传递到规范中,并且 Repository/Persistence 层将从其中生成一个表达式树以传递给 .Where子句,它使用一个接口(interface)来声明所有可过滤的字段。
    我也朝这个方向做了一些尝试,但对结果不太满意。就像是
    public interface IQuery<T>
    {
        IQuery<T> Where(Expression<Func<T, T>> predicate);
    }
    public interface IQueryFilter<TFilter>
    {
        TFilter And(TFilter other);
        TFilter Or(TFilter other);
        TFilter Not(TFilter other);
    }
    
    public interface IQueryField<TSource, IQueryFilter>
    {
        IQueryFilter Equal(TSource other);
        IQueryFilter GreaterThan(TSource other);
        IQueryFilter Greater(TSource other);
        IQueryFilter LesserThan(TSource other);
        IQueryFilter Lesser(TSource other);
    }
    public interface IPersonQueryFilter : IQueryFilter<IPersonQueryFilter>
    {
        IQueryField<int, IPersonQueryFilter> ID { get; }
        IQueryField<string, IPersonQueryFilter> Name { get; }
        IQueryField<int, IPersonQueryFilter> Age { get; }
    }
    
    在规范中,我们会传递一个 IQuery<IPersonQueryFilter> query到规范构造函数,然后在使用或组合它时将规范应用到它。
    IQuery<IGridQueryFilter> query = null;
    
    query.Where(f => f.Name.Equal("Bob") );
    
    我不太喜欢这种方法,因为它使得处理复杂的规范有点困难(比如和或如果链接),而且我不喜欢 And/Or/Not 的工作方式,尤其是从这个“API”创建表达式树.
    我一直在找网上到处看了几十篇关于DDD和规范的文章,但他们总是只处理简单的情况,没有考虑性能,或者违反了DDD模式。
    在不进行内存过滤或将持久性泄漏到域层的情况下,您如何在实际应用程序中解决这个问题?
    是否有任何框架可以通过两种方式之一解决上述问题(类似于表达式树的语法的查询生成器或表达式树翻译器)?

    最佳答案

    我认为规范模式不是为查询条件设计的。实际上,DDD 的整个概念也不是。如果查询需求过多,请考虑 CQRS。

    规范模式有助于开发无处不在的语言,我认为它就像一种 DSL。它声明要做什么而不是如何做。例如,在订购环境中,如果已下订单但未在 30 分钟内付款,则订单被视为逾期。使用规范模式,您的团队可以使用一个简短但独特的术语:OverdueOrderSpecification。想象一下下面的讨论:

    情况1

    Business people: I want to find out all overdue orders and ...  
    Developer: I can do that, it is easy to find all satisfying orders with an overdue order specification and..
    

    案例-2
    Business people: I want to find out all orders which were placed before 30 minutes and still unpaid...  
    Developer: I can do that, it is easy to filter order from tbl_order where placed_at is less that 30minutes before sysdate....
    

    你更倾向哪个?

    通常,我们需要一个 DSL 处理程序来解析 dsl,在这种情况下,它可能在持久性适配器中,将规范转换为查询条件。这种依赖(infrastructure.persistence => domain)不违反架构原则。
    class OrderMonitorApplication {
        public void alarm() {
           // The specification pattern keeps the overdue order ubiquitous language in domain
           List<Order> overdueOrders = orderRepository.findBy(new OverdueSpecification());
           for (Order order: overdueOrders) {
               //notify admin
           }
        }
    }
    
    class HibernateOrderRepository implements orderRepository {
        public List<Order> findBy(OrderSpecification spec) {
            criteria.le("whenPlaced", spec.placedBefore())//returns sysdate - 30
            criteria.eq("status", spec.status());//returns WAIT_PAYMENT
            return ...
        }
    }
    

    关于repository - 领域驱动设计中的规范模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25953828/

    相关文章:

    混帐 https ://repository not found though it exists

    rest - 聚合根在 REST API (DDD) 中的作用

    c# - 如何通过域对象和服务中的验证与 UI 层中的验证保持 DRY

    domain-driven-design - 在 DDD 应用程序中持久保存应用程序特定数据

    c# - 这种关于访问数据的设计如何?

    java - 如何在 Oracle ATG 中使用 Java API 存储库添加 LIST 项?

    git - 从 git 存储库中 pull 所有未获取的更改

    java - 如何编写 JpaRepository 方法来搜索在 Set<String> 中存储电话号码的用户

    ASP.NET MVC,Ninject,每个请求的单个实例对多个构造函数

    design-patterns - 存储库设计模式