A filtering logic should be in a repository or in a service

domain-driven-designfilteringsearch

I'm wondering the following: suppose we are building a system where there needs to be some filtering functionality to search for some entity. For example, one might want to apply the filtering to a table listing the entities to find something, or use it to generate a report on a filtered set, whatever.

The point is: we need to have a filtering logic somewhere. One bad way of doing this would be to replicate the filtering logic where needed. I've done that once and it is one terrible idea.

On the other hand, I belive there should be a method like Filter(FilteringOptions filteringOptions) designed to perform the filtering operation and return the filtered list of entities.

Now, IMHO, the filtering logic is a kind business logic. The business experts are the ones who knows how the filtering takes place, what things are filtered and how. Because of that, I believe the filtering logic should be located in the domain layer.

I've found two options for doing this: embeding the filtering method in the corresponding repository for that particular entity, or else, creating a domain service like EntityNameSearchService which would consume the repository to perform the filtering.

I'm still confused which one would be the better way. So, if I'm trying to use DDD properly, where this filtering logic should be? On the repository or in a separate service?

Best Answer

I'm wondering the following: suppose we are building a system where there needs to be some filtering functionality to search for some entity. For example, one might want to apply the filtering to a table listing the entities to find something, or use it to generate a report on a filtered set, whatever.

You should observe that this point that your use cases for filtering center around reads, rather than writes. This is the normal pattern -- a write is typically addressed to a specific aggregate root in your domain.

If you are performing a read, then you don't actually care about aggregates - you don't care about the enforcement of the business invariant because you aren't actually trying to change anything. You just care about state.

Which may mean that it makes sense to bypass the domain model completely.

Just something to think about.

Now, IMHO, the filtering logic is a kind business logic. The business experts are the ones who knows how the filtering takes place, what things are filtered and how. Because of that, I believe the filtering logic should be located in the domain layer.

Yes, and no. You are drawing upon the ubiquitous language to describe the filter. So you are definitely using the domain vocabulary.

But you don't have any "behavior", in so far as you aren't actually modifying the book of record, so you don't have the invariant to worry about.

On the other hand, I belive there should be a method like Filter(FilteringOptions filteringOptions) designed to perform the filtering operation and return the filtered list of entities.

You are very close to the idea of "Specification". It's basically a predicate than a repository can use to identify which artifacts match some arbitrary criteria.

There are some traps to be aware of. Greg Young touched on them some time back, but I'll summarize here.

First, the abstraction of running a predicate against a collection is O(N). You'll probably want something nicer, especially if your persistence store is smart about indexing. Your persistence component will likely want to be able to transform it to a implementation specific constraint (example: taking a specification and turning it into a where clause on a prepared statement).

Second, an interface is a means of documenting the contract served by the persistence component. "Make the implicit explicit" -- if you describe what it is that you really need, then the interface tells you something about what characteristics are important for your data store, which gives you a single place to look when trying to evaluate whether an alternative store is suitable.

(Of course, the implementation of that interface might just be an adapter that creates the specification from the method arguments, and forwards that along. That's OK, you've captured the actual requirement).