Repository DDD中哪一层应该包含查询

Repository DDD中哪一层应该包含查询,repository,domain-driven-design,cqrs,Repository,Domain Driven Design,Cqrs,我有一个简单的DDD服务,带有文章聚合根。我使用MediatR和CQR分离命令和查询。在DDD域中,不应依赖于应用程序层和基础结构层。我有一个存储库IArticleRepository,用于从文章数据库合成一些数据。我有一个rest端点,用于通过某种过滤器获取文章,以便创建 ArticleQuery : IRequest<ArticleDto(or Article)> ArticleQuery:IRequest 这个查询对象应该是什么时候?我每个聚合都有一个存储库,所以在域层我有

我有一个简单的DDD服务,带有文章聚合根。我使用MediatR和CQR分离命令和查询。在DDD域中,不应依赖于应用程序层和基础结构层。我有一个存储库IArticleRepository,用于从文章数据库合成一些数据。我有一个rest端点,用于通过某种过滤器获取文章,以便创建

ArticleQuery : IRequest<ArticleDto(or Article)>
ArticleQuery:IRequest
这个查询对象应该是什么时候?我每个聚合都有一个存储库,所以在域层我有一个IArticleRepository。我需要指定输入参数类型。如果我把查询放在基础设施或应用程序层,我会从指向基础设施或应用程序的域中获得依赖关系。如果我将查询放在域中,它将违反DDD,因为查询与业务没有关系。如果我不放置对象,只将字段作为存储库的参数,那么将有大约10-15个参数-这是一种代码味道


这是必需的,因为在查询处理程序中也会出现搜索引擎逻辑,所以我决定通过存储库或类似的东西将搜索引擎逻辑中的SQL逻辑封装到基础架构中。

我不完全理解您的问题,但在如何使用存储库方面似乎有些混乱。回答这个问题可能会帮助你找到正确的方法

让我分两部分回答您的问题:存储库适合于哪里,以及如何使用查询表示域概念

  • 存储库不是域层的一部分。它们属于应用层的外部

    典型的事务流如下所示:

    • UI向API发送请求
    • API控制器收集请求参数和 调用应用程序服务
    • 应用程序服务收集存储库(应用程序通常根据配置在运行时注入存储库)
    • 应用程序服务在存储库的帮助下根据请求参数加载聚合(域对象)
    • 如有必要,应用程序服务调用聚合上的方法来执行更改
    • 应用程序服务在存储库的帮助下持久化聚合
    • 应用程序服务格式化响应并将数据返回给API控制器
    所以,您可以看到,应用程序服务处理存储库和聚合。聚合位于域层,不必处理存储库

  • 查询最好放在存储库中,因为存储库负责与底层数据存储交互

    但是,您应该确保每个查询都代表域中的一个概念。通常不建议直接使用筛选器参数,因为您无法从域的角度捕获查询的重要性

    例如,如果您正在查询成人(
    age>21
    ),那么您应该有一个名为成人的查询对象,其中包含此筛选器。例如,如果您正在查询老年人(
    age>60
    ),则应该有一个名为老年人的不同查询对象,以此类推

    为此,您可以使用规范模式公开一个GET API,但在将其传递到存储库进行查询之前,将其转换为
    域规范对象
    。在调用
    应用程序服务之前,通常在
    控制器中执行此转换

    Martin Fowler和Eric Evans发表了一篇关于使用规范的优秀论文:

    正如本文所述,规范的中心思想是将如何匹配候选对象的语句与匹配的候选对象分开

  • 注:

    • 对查询端使用规范模式,但避免在不同上下文中重用它。除非查询表示相同的域概念,否则您应该为每个需求创建不同的规范对象。另外,如果您使用的是CQR,则不要在查询端和命令端同时使用规范对象。您将在需要分开的两个部分之间创建一个中心依赖项
    • 获取基本域概念的一种方法是评估您的查询(getByAandB和getByAandC),并引出您向域提出的问题(例如,请您的域专家描述她试图获取的数据)

    存储库组织:

    如果这让您有点困惑,我很抱歉,但是代码是用Python编写的。但它读起来几乎像伪代码,所以您应该能够轻松理解

    比如说,我们有这样的代码结构:

    application
        main.py
    infrastructure
        repositories
            user
                mongo_repository.py
                postgres_repository.py
            ...
        ...
    domain
        model
            article
                aggregate.py
                domain_service.py
                repository.py
            user
            ...
    
    article
    下的
    repository.py
    文件将是一个抽象存储库,其中包含重要但完全为空的方法。这些方法表示域概念,但它们需要具体实现(我认为这就是您在评论中提到的)

    postgres\u repository.py
    中:

    # import SQLAlchemy classes
    ...
    
    # This class is required by the ORM used for Postgres
    class Article(Base):
        __tablename__ = 'articles'
    
        id = Column(Integer, primary_key=True)
        title = Column(String)
    
    这是工厂可能的具体实施,在同一文件中:

    # This is the concrete repository implementation for Postgres
    class ArticlePostgresRepository(ArticleRepository):
        def __init__(self):
            # Initialize SQL Alchemy session
            self.session = Session()
    
        def get_all_active_articles(self, ...):
            return self.session.query(Article).all()
    
        def get_article_by_slug(self, slug, ...):
            return self.session.query(Article).filter(Article.slug == slug).all()
    
        def get_articles_by_followers(self, ...):
            return self.session.query(Article).filter(followee_id__in=...).all()
    
    因此,实际上,聚合仍然对存储库本身一无所知。应用程序服务或配置动态地选择要用于给定环境的存储库类型(例如,可能是Test中的Postgres和Production中的Mongo)。

    我通常选择各种查询层。正如我将拥有一个处理聚合的
    icCustomerRepository
    一样,我将拥有一个直接与数据存储交互的
    icCustomerQuery

    存储库实际上应该只用于聚合,因此只用于数据修改。唯一的检索应该是检索整个聚合,以便对该聚合进行某种形式的更改

    查询层(实际上比查询层更重要)是infrastru
    # This is the concrete repository implementation for Postgres
    class ArticlePostgresRepository(ArticleRepository):
        def __init__(self):
            # Initialize SQL Alchemy session
            self.session = Session()
    
        def get_all_active_articles(self, ...):
            return self.session.query(Article).all()
    
        def get_article_by_slug(self, slug, ...):
            return self.session.query(Article).filter(Article.slug == slug).all()
    
        def get_articles_by_followers(self, ...):
            return self.session.query(Article).filter(followee_id__in=...).all()