Question

I've worked on several applications that try to adhere to DDD principles, I noticed that we end up with situations where there is duplication between the Service Layer and the repositories that feels like a code smell.

For most of the operations in the Service layer, it seems like it is a direct mapping to CRUD operations, GetAll, GetById, Create, Delete etc.. the flow of the architecure is within these lines: I have a controller calling a Service layer that calls a Repository that calls an ORM which talks to the Backend ..

So for example GetAll would exist in both SL and Repository. Now, if we have a change/business requirement that GetAll should ignore certain items, how am I supposed to do it, should I ignore these in the repository, or that's business logic that should go in the Service Layer? Wouldn't life be easier if we just had a Service Layer calling the ORM directly?

To Sum up: I understand the Service Layer could abstract some businees logic but when - in most cases - it is dealing with simple CRUD operations, wouldn't it be easier to just get rid of the Repository? But, what if the SL also contains some methods with complicated business logic, should these go through a repository? From good design perspective, Should I favor consistency and always go through repository or just keep it simple and only use a repository when it is not a simple one-to-one mapping to a CRUD operation.

PS: I realize there are seemingly similar questions but didn't find any satisfying answer

Was it helpful?

Solution

I noticed that we end up with situations where there is duplication between the Service Layer and the repositories that feels like a code smell.

It isn't a code smell since they do different things.

You should keep in mind that Domain or Application Services reside in a different layer than Repository implementations. Layers are there for a reason - objects in different layers don't have the same responsibilities and don't talk to the same neighbors. Repository implementations are tightly coupled to the means of persistence of your objects. They might generate SQL statements and talk to a relational database, they might talk to your ORM... the important thing is that they know about the way your objects are persisted, which is not true of Application Services.

If your Services layer was to call the ORM directly, it would really do 2 big things, breaking the Single Responsibility Principle. It would also be more difficult to change your ORM for another one or for a different means of persistence.

So for example GetAll would exist in both SL and Repository. Now, if we have a change/business requirement that GetAll should ignore certain items, how am I supposed to do it, should I ignore these in the repository, or that's business logic that should go in the Service Layer?

If GetAll() ignores certain items, I strongly suggest renaming it both in the Service and the Repository to reflect that, eg : GetAllAllowedToUser(), GetAllBut...(). Thus the contract of the method will be clear and you'll avoid misunderstandings about what it's supposed to return. Plus you'll be able to keep the original genuine GetAll() method which could still be of some use.

OTHER TIPS

in most cases - it is dealing with simple CRUD operations, wouldn't it be easier to just get rid of the Repository

IMHO, I wouldn't say to get rid of Repository. I would say that if you are doing CRUD you don't need DDD (at all). If you read Fowler's enterprise patterns or Evans they both say that DDD is only of use when you have domain logic that is significantly complex. CRUD is not complex and therefore no DDD needed.

What you describe is a code smell. But I don't think it is a smell with DDD. You are just seeing an over-engineered piece of code.

+1 for Dtryon, also:

Now, if we have a change/business requirement that GetAll should ignore certain item

Not directly related and I know you just used it as an example, but I've seen this exact thing. Please don't end up with methods called GetAll that do not get all. Keep GetAll, have it GetAll, then have GetAllLive, or GetAllAvailable or something like that as well which does what it says it does

Maybe the "Finder pattern"(dont know if this is the right term) can solve your problem. According to CQS(Command-Query-Separation) principle, (IMO,) query operations are not "business logic" at all. We can write some specific "Finder"s in the Infrastructure Layer to perform various queries and let all non-query operations(business logic) stay in the Service Layer, then on the client side we treat the finders the same as services.
Sorry for my language :-(.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top