3

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.

Andriy
  • 362
  • 3
  • 16

2 Answers2

12

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.

Eben Roux
  • 12,983
  • 2
  • 27
  • 48
1

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).

Subhash
  • 3,121
  • 1
  • 19
  • 25
  • I so appreciate you for answering. Iknow about the SpecificationPattern. The question was where to store specifications(Domain, Infrastructure or Application). But now, I have another question: 1. how I can translate specification on c# (Expresion<>, or Func<>) to SQL query for dapper 2. I know specification pattern as a generic class like ISpecification. But I have a filter DTO, that accept on QueryHandler, and this DTO is not Entity. So it shouldn't fit in DOMAIN. On the other hand, repository abstraction should accept this filterDTO for querying needed data – Andriy Aug 13 '19 at 08:06
  • you said: " Query is best placed within the Repository ". Okay, I can realize that, but DDD concept ensures, that repository abstraction should be captured by the aggregate in Domain layer. And in IRepository interface, it will be a method like `GetAll(QueryInInfrastructureLayer query);`, where the repository is fitted. So there is dependency from domain pointing to infrastructure and as consequence - violation DDD. Or I misunderstand you. – Andriy Aug 13 '19 at 09:51
  • 1. In the past, I have written custom classes to accept, say, a dictionary of params and convert them into a query as required by the datastore. I cannot give an example from the .Net world, but in the python world, this is what Django ORM (https://docs.djangoproject.com/en/2.2/topics/db/queries/#retrieving-specific-objects-with-filters) and Ruby on Rails ActiveRecord (https://guides.rubyonrails.org/active_record_querying.html) interface do. There is a good chance that the ORM you use will have such support, and you can pass params to it and get an SQL query back. – Subhash Aug 14 '19 at 17:54
  • 2. The repository is not part of the domain layer, strictly speaking. It sits in the middle of Application services and domain layer and helps in persistence. By that logic, if it accepts a filter DTO, it is completely fine. The filter DTO would typically be created either by the Application Service, or the controller that invokes the Application Service. – Subhash Aug 14 '19 at 17:56
  • Aggregates should not handle repositories, or receive access to them. Application Service should use repositories to load already existing aggregates from the database or persist new aggregates. Either way, the construction and validation of aggregates happen in the domain layer, and repositories receive fully-formed aggregate objects to act on. – Subhash Aug 14 '19 at 17:59
  • 1. Okay, and where these classes need to be placed? And do you understand the full complexity of parsing Expression(it is an expression tree used to implement Specification in .net)? We need to traverse all that tree, and it so hard logic, and when I swap Orm to EF, it will be stale, cause EF is full ORM, and there is no need raw SQL. Why not use filter objects(without method `IsSatisfied()`), with all filter parameters (time, order, and other different parameters). Or it is a violation of DDD? – Andriy Aug 15 '19 at 07:42
  • 2. Sure, a repository is not a part of the domain, but repository abstraction is (see Robert C. Martin Dependency rule, and Microsofts book about microservices, where they implemented IRepository in the domain). And the problem arises when you pass DTO (application layer) in IRepository - that make dependency from the Domain to Application, and this is a violation of DDD. – Andriy Aug 15 '19 at 07:45
  • 1. Sorry, I don't work with .Net, so I can't point you to the right place. But an example in my Python world is SQLAlchemy (an ORM), and it's `filter` method (https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.filter). You can pass keyword arguments or dictionary to the filter method, and it creates the correctly formatted SQL query. Or, you can provide the SQL query directly to the conn: https://docs.sqlalchemy.org/en/13/core/connections.html. You can invoke these ORM methods from repository methods, which would in-turn be used by Application Services. – Subhash Aug 15 '19 at 19:40
  • 2. That's an approach outlined by Clean Architecture principles, that is true. But because you tagged your question with domain-driven design, I was referring to different (and far superior, IMHO) approach that DDD outlines. In Clean Arch, repositories are injected for use by Entities. In DDD, repositories stay outside and are used by App Services to load and persist aggregates. Aggregates themselves remain clean. – Subhash Aug 15 '19 at 19:44
  • sorry, but I'm not sure, that this isn't outlined by DDD, because Microsofts book tells to me about IRepository abstraction: "`A pattern related to this practice (placing the repository interfaces in the domain model layer) is the Separated Interface pattern. As explained by Martin Fowler, “Use Separated Interface to define an interface in one package but implement it in another. This way a client that needs the dependency to the interface can be completely unaware of the implementation.”`" – Andriy Aug 16 '19 at 06:20
  • On 2, I think we both are on the same page, so let me explain a little differently. I have added a new section to my answer, to try an address this. Let me know if that clarifies. – Subhash Aug 16 '19 at 23:06
  • Maybe a better way of saying it is repositories ARE part of the domain, their implementations are not. – Devon Burriss Aug 29 '19 at 08:50
  • @DevonBurriss Spot on! – Subhash Sep 08 '19 at 15:57