What layer should contain Queries in DDD

允我心安 提交于 2019-12-11 05:23:57

问题


I have a simple DDD service, with Article Aggregate root. I use MediatR and CQRS for separation of commands and queries. In DDD domain should not have dependencies on application and infrastructure layers. I have a repository IArticleRepository for composing some data from articles database. I have a rest endpoint for getting articles by some kind of filters so that I create

ArticleQuery : IRequest<ArticleDto(or Article)>

And when this query object should be? I have a repository per aggregate, so in Domain layer I have IArticleRepository. And I need to specify the input parameter type. If I put query in Infrastructure or Application layer I get the dependency from domain pointing to infrastructure or application. If I put query in Domain it violates DDD, because the query has no relations to business. If I will not putting an object, and just fields as a parameter to the repository, there will be about 10-15 parameters - this is a code smell.

It needed because in Query handler also appear SearchEngine logic, so I decided to encapsulate SQL logic from search engine logic in infrastructure via the repository or something like that.


回答1:


I usually go for a query layer of sorts. Just as I would have an ICustomerRepository that handles my aggregates I would have an ICustomerQuery that interacts directly with my data store.

A repository really should be only for aggregates and, therefore, only for data modification. The only retrieval should be retrieving an entire aggregate in order to effect some form of change on that aggregate.

The query layer (more concern than layer, really) is infrastructure. I usually also namespace any read model in a Query namespace in order to distinguish between my domain Customer, say, and my Query.Customer.




回答2:


I don't understand your question entirely, but there seems to be some confusion on how to use repositories. Answering that may help you find the right way.

Let me answer your question in two parts: where do repositories fit in, and how to use queries represent domain concepts.

  1. Repositories are not part of the Domain layer. They belong outside in the Application layer.

    A typical transaction flow would be something like this:

    • UI sends a request to API
    • API Controller gathers request params and invokes the Application Service
    • Application Service gathers Repositories (Applications typically inject repositories at runtime based on configuration)
    • Application Service loads Aggregates (domain objects) based on the request params with the help of Repositories
    • Application Service invokes the methods on Aggregates to perform changes, if necessary
    • Application Service persists the aggregates with the help of Repositories
    • Application Service formats response and returns data to API Controller

    So, you see, Application Service deals with repositories and aggregates. Aggregates, being in the domain layer, do not ever have to deal with Repositories.

  2. A Query is best placed within the Repository because it is the responsibility of the Repository to interact with underlying data stores.

    However, you should ensure that each Query represents a concept in the domain. It is generally not recommended to use filter params directly, because you don't capture the importance of the Query from the domain's point of view.

    For example, if you are querying for, say, people who are adults (age > 21), then you should have a Query object called Adults which holds this filter within it. If you are querying for, say, people are who are senior citizens (age > 60), you should have a different Query object called Senior Citizen and so on.

    For this purpose, you could use the Specification pattern to expose one GET API, but translate it into a Domain Specification Object before passing it on to the Repository for querying. You typically do this transformation in your Controller, before invoking the Application Service.

    Martin Fowler and Eric Evans have published an excellent paper on using Specifications: https://martinfowler.com/apsupp/spec.pdf

    As the paper states, The central idea of Specification is to separate the statement of how to match a candidate, from the candidate object that it is matched against.

Note:

  • Use the specification pattern for the Query side, but avoid reusing it in different contexts. Unless the Query represents the same domain concept, you should be creating a different specification object for each need. Also, DO NOT use a specification object on both the query side and command side, if you are using CQRS. You will be creating a central dependency between two parts, that NEED to be kept separate.
  • One way to get the underlying domain concept is to evaluate your queries (getByAandB and getByAandC) and draw out the question you are asking to the domain (For ex., ask your domain expert to describe the data she is trying to fetch).

Repository Organization:

Apologies if this confuses you a bit, but the code is in Python. But it almost reads like pseudocode, so you should be able to understand easily.

Say, we have this code structure:

application
    main.py
infrastructure
    repositories
        user
            mongo_repository.py
            postgres_repository.py
        ...
    ...
domain
    model
        article
            aggregate.py
            domain_service.py
            repository.py
        user
        ...

The repository.py file under article will be an abstract repository, with important but completely empty methods. The methods represent domain concepts, but they need to implemented concretely (I think this is what you are referring to in your comments).

class ArticleRepository:
    def get_all_active_articles(...):
        raise NotImplementedError

    def get_articles_by_followers(...):
        raise NotImplementedError

    def get_article_by_slug(...):
        raise NotImplementedError

And in postgres_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)

And this is a possible concrete implementation of the Factory, in the same file:

# 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()

So in effect, the aggregate still does not know anything about the repository itself. Application services or configuration choose what kind of repository is to be used for a given environment dynamically (Maybe Postgres in Test and Mongo in Production, for example).



来源:https://stackoverflow.com/questions/57464478/what-layer-should-contain-queries-in-ddd

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!