Following the classic 3 layer architecture
domain Model (a list of domain models live there and has no dependencies)
DAL layer - My Repositories lives there with DBContext implementation (Ado.net) Dal return pure entities ( reference domain model)
Service Layer ( Business Layer ) expose methods of repositories ( reference DAL Layer and Domain Model)
UI Layer(WPF) > Service Layer(BLL) > Repositories
MY question is related to the following concepts
- Repository
- CQRS
Reading article upon article, state that Data Repositories should return only domain models.
e.g
public interface ICustomerRepository
{
IEnumerable<Customer> FindByText(string TextToFind);
Customer GetByID(string ID);
IEnumerable<Customer> GetAll();
void Update(Customer CustomerToAdd);
void Delete(params string[] Ids);
}
Then I ask my self.
- What if i need to show a list of customer with some other complex inner join, union query.
- end a lot of more other queries related to other view requirements.
(actually in our accounting application 90% of the screens are never shown data in their pure form(entities). always they are in form with other inner joins , sum amounts etc.. e.g customer : Name,AccountName(inner join from accounts), Balance (inner join from ledger), etc..
Some state that is ok to put this query in repository others say is not good because is not an entity
Where this logic goes? since repositories dose not allow me to return other than entity model. and also CustomerService in BLL Layer ( dose not allow me to return DTO in methods)
I read that CQRS comes to the stage to solve UI Queries
Ok, lets say that i follow cqrs for query side ( lets skip command as commands and updates go through repository)
Now I end up having:
- CustomerRepository (DAL Layer)
- CustomerService (BLL - service layer) which is just exposed repository methods and maybe some other related things
- CustomerQueries class (BLL - service layer) which contain any complex
query (DTO) related to customer and has direct connection to sql converting datatable to relative dto and give it to UI layer
My question is this proper way to follow?
which layer CQRS live? in (business layer which some call it service layer?) or in DAL Layer where my repositories live
Many times i found my self much easy way just to type CustomerServices. and the intellisense giving me all the related information I might want from customer, some functions return dto others return some amounts other times just some bool to check some rules which require complex sql queries)
The problem is that, since CustomerService as they say, should only be responsible to call relative CustomerRepository fetch or update only things related to repository,
where i put logic for complex queries and in which layer?
2 Answers 2
To cut a long story short, this is mostly a matter of self-imposing a design and then trying to make something fit. Something which is very much needed, but doesn't quite fit with the design we imposed on ourselves.
In the end, it's a matter of finding the compromise in your design that you're comfortable with.
Below, I've listed the common compromises I've encountered in companies/projects I've worked at.
A silly system
In the olden days, repositories were intended to be "entity boxes". You get the Person
from the PersonRepository
, you get the Car
from the CarRepository
, and so on. From a purely coding perspective, this is the cleanest way to organize your datastores.
But then database got significantly better at relational queries, and the "entity box" approach specifically breaks the relational nature of your entities when you're trying to receive multiple levels of entities in a single optimized query. That's a problem
And now we run into one of the silliest problems: self-imposed categorization. It's not that we can't write a query that fetches a list of people and the cars they own, it's that according to our restrictive "entity box" approach, we've actually made it impossible to properly categorize this.
This is no different from labelling all the shelves in your kitchen, only to realize that you now have another food that you didn't put on any label before, and now you refuse to put that food in your kitchen because it doesn't fit with any of your labels. It's downright ridiculous and essentially a jobsworth kind of discussion.
Sticking with repositories
Since this is purely an issue because of a self-imposed pattern, creating the query isn't difficult, and justifying how it fits in your design is a matter of argumentation. You could take the purist route:
- We should stick to the repository pattern as intended. We get the
Person
from thePersonRepository
, we get theCar
from theCarRepository
. No relational two-entity-types-in-one-query tricks.
Which is great for code organization, but you're going to run into significant performance issues for large data sets. If the performance becomes unbearable, this option becomes very hard to justify for what is essentially an arbitrary self-imposed rule.
Okay, so let's decide to create this query. Where do we put it? You could find a reason to justify why it belongs in one of the existing repositories:
- Since it returns
Person
with nestedCar
object, I consider this aPerson
-based query and therefore I put it in thePersonRepository
. - I consider the owner (i.e. the
Person
) as car data, so the query itself isCar
-based and therefore I put it inCarRepository
.
These justifications are all, well, justifiable, but you tend to run into issues where one developer thinks about the same query in a different light, and they disagree on which repository the query belongs in.
So to please these disagreeing developers, we could come up with a compromise:
- Since this query uses both
Person
andCar
entities, we should put this in its ownPersonCarRepository
.
This sounds great initially, but given many entity types and many queries which fetch several entity types, the amount of repositories is going to explode. Also, if you ever expand one query, it may need to move repository because it now includes another minor entity type. Is that really worth all the effort?
None of these solutions have really worked well, or at least it's very hard to get everyone to agree on a given approach. Maybe we should move away from repositories, at least partially?
Query objects
At a very basic level, query objects are repositories that only contain one query. Instead of naming them after the entity type (CarRepository
), you name them after the query itself (GetCarsForOwnerQuery
), but the implementation remains virtually the same.
This actually solves the categorization problem. We no longer deal with silly labels in which our custom query doesn't fit, and instead make a new custom label for our custom query.
It also plays very nicely with CQRS, which you mentioned in your question.
Note that you could decide to refactor all your repositories into query (and command) objects, but it's not necessary to do so. You already touched on this idea:
lets say that i follow cqrs for query side ( lets skip command as commands and updates go through repository)
I tend to take such a hybrid approach:
- Single-entity-type queries and commands remain in their entity repository. Personally, I use a generic base repository that provides all the CRUD basics to work with EF tables. It's quick and easy, but not really customization-friendly for very unique entities.
- Multi-entity-type queries and commands get their own query/command object.
When beginning development, you tend to rely heavily on your repositories as you're crudely making entities. But over time, as the application gets fleshed out, you tend to shift more towards query objects.
Whether you use the hybrid approach or move over to using query/command objects entirely is up to you. Using only query/command objects takes a bit more work to type it all out, but you may prefer to stick to one system.
Now I end up having:
- CustomerRepository (DAL Layer)
- CustomerService (BLL - service layer) which is just exposed repository methods and maybe some other related things
- CustomerQueries class (BLL - service layer)
That's not how I would do it. These query objects replace the repositories, so they should live with (or instead of) the repositories, i.e. in the DAL.
A more appropriate approach would be
CustomerService
(BLL)CustomerRepository
(DAL) - for all simple CRUD operations[myQueryName]Query
(DAL) - each of these classes contains a single multi-entity-type query
The CustomerService
(BLL) can connect to both the repositories or the query objects, depending on what it needs. The service can in principle mix and match to its liking, though usually you tend to create query objects that precisely match a specific CustomerService
method.
CQRS and query objects
which layer CQRS live? in (business layer which some call it service layer?) or in DAL Layer where my repositories live
Either, both, or neither.
The query objects in the DAL, which is a form of CQRS, are technically unrelated to any CQRS you may have going on in the Business or Domain layers. These are two separate design decisions. You can have:
- a monolith domain service and DAL query objects/commands
- domain queries/commands and monolith repositories
- a monolith domain service and and monolith repositories
- domain queries/commands and DAL query objects/commands.
These are two separate design decisions that do not depend on each other. The thought process behind either is very similar, but you're not forced to make the same decision twice.
-
Thank you for you time answering me. I feel more safe as you said to move the queries to DAL / Repositories.. But that means the DTO should leave from BLL as also [MyQueryName]Query (as currently now live in BLL) and move to domain Model? ( Domain model ) is accessible by all layers.(there Irepository Interfaces live and things that are common to all layers) And then, BLL will be the gateway to call DAL and call function that i will pass [myQueryName]Query . Did I understand correct?Stelios– Stelios01/22/2021 19:17:08Commented Jan 22, 2021 at 19:17
-
@user2160275: There are several ways to handle DTOs here. The cleanest approach is for every layer to have its own DTO - even if that mean that the business and dal DTOs are exactly the same. But there are other approaches where the only real DTO being used is a business DTO, where the domain itself just passes its domain objects around. That's a long discussion in and of itself. I'd suggest starting off with a DTO per layer by default, and only deviate from it when you find a proper reason to do so.Flater– Flater01/23/2021 17:05:14Commented Jan 23, 2021 at 17:05
-
@Flater Really good answer, in fact one of the best StackOverflow answers I have ever read. So many answers to questions like this miss the point. ThanksMSOACC– MSOACC08/01/2022 13:14:01Commented Aug 1, 2022 at 13:14
This confusion is not your fault. The literature sucks.
Some state that is ok to put this query in repository others say is not good because is not an entity
"Repository", in a DDD discussion, refers back to the REPOSITORY pattern described by Eric Evans in Chapter 6 of the original domain driven design book.
Evans, in that chapter, explicitly opens the possibility of Repositories returning values rather than entities.
... usually ENTITIES, sometimes VALUE OBJECTS with complex internal structure, sometimes enumerated VALUES.
The core motivation of the REPOSITORY pattern is to create a facade, so that the complexity of reconstituting the objects you need is delegated to the plumbing, allowing your application code to express its needs in the language of the model.
Therefore...
If you have a report, perhaps based on some complex inner join, it is a perfectly reasonable application of the pattern to create a REPOSITORY that represents a collection of those reports, with methods to allow the client to request a specific report (or subset of the collection).
These reports are, of course, values -- meaning that the implementation of the report doesn't include affordances that allow you to change the report. Similarly, the report collection (ie, the repository) does not include affordances for adding or updating reports. (In effect, the reports are a view into the information managed by the domain model; you can look, but if you want to change anything you need to send messages to the domain entities).
So basically DTO's should live not in Business Layer but in Domain Model Layer?
No, I don't think that follows. DTO are, in effect, an in memory representation of a message schema, where a schema is a representation of information that can be sent across time and space. That's a communication concern, not a domain modeling concern. That sort of thing normally makes sense out at the boundaries of our application, not in the core of our domain.
Message schema typically change much more slowly than our domain dynamics, and for different reasons.
If DTO and domain models change for different reasons, do you really want to design your solution so that these two different things are in the same pile of stuff?
Design is what we do to get more of what we want than we would get by just doing it.
So you need to think about which of the "what you want"s should have priority here.
-
Thank you ! I feel more comfortable now as is just matter of what is more convenient method to follow. So basically DTO's should live not in Business Layer but in Domain Model Layer? (a shared dll) if I understand wellStelios– Stelios01/22/2021 19:22:57Commented Jan 22, 2021 at 19:22