Repositories and EF
This is a topic that a lot can be said about, but I want to keep it short here because I want to focus on better solutions.
The short answer is that the repository pattern doesn't mesh well with Entity Framework, because EF itself is already an implementation of the repository pattern. The
DbContext is a unit of work. The
DbSet<T> is a repository.
Trying to abstract the same abstraction leads to reinventing the wheel a second time, and if you want to use all of EF's features, required you to build that second wheel so that it can wrap and handle every EF feature, which is sheer madness.
To be fair, past versions of EF were lacking some features that meant you couldn't quite separate your database from your domain, but EF Core ticks all necessary boxes to provide a separation.
I strongly suggest listening to Jason Taylor's explanation, he expresses the underlying reasoning better than I am able to. The explanation takes just over 3 minutes, I've bookmarked the video at the right starting point.
If you prefer a written explanation, this article sums it up well.
Anecdotally, I have met an worked with several developers (who I think of as good developers) who are entrenched in their "EF must be abstracted" position. It's not hard to find blog posts that still strongly advocate for it. I think this is an outdated argument that applied to past versions of EF, but is no longer relevant.
My general take on it is that "it's called Entity Framework, not Entity Library", i.e. it's not worth the effort to fully abstract it. If you want to know more, I wrote a much longer answer on this in the past, specifically the "But Entity Framework is a framework!" part of the answer.
I want to use the rest of this answer to explore better ways of dealing with this data/domain segregation.
From experience, not everyone intuitively agrees on the direction an arrow should be drawn in, in my answer I use
A -> B to mean that B has a dependency on A.
Dependencies can be inverted
You point out several issues, all of which suggest that you are thinking of your dependency graph as a linear sequence:
Web <- Domain <- Data
<-------------------------- APPLICATION FLOW
Generally speaking, the order of the dependencies follows the flow of the application. The web controller speaks to the domain and the domain speaks to the DAL, so this is a linear sequence, right?
While that seems to be unavoidable based on which layer depends on which, the dependency between Domain and Data isn't as strictly defined as it would first seem. To cut a long story short, this is dependency inversion, but I'll use an example here to elaborate.
Forget the example and think of two classes:
MoreThanA. As the name suggests,
MoreThanA contains everything
A contains and then some more. This suggests the following dependency graph:
MoreThanA <- A
Whether this is a layer dependency or class dependency is irrelevant for the point I'm trying to make. The focus is on the direction of the arrow.
But wait... DAL entities are usually more complex than the domain objects they represent, because there may be persistence-specific properties that are being stored which the domain doesn't care about and doesn't work with, since the domain shouldn't be tightly coupled to the persistence implementation logic.
So that suggests the following class dependency graph:
MyDomainObject -> MyEntity
Which in turn suggests the following layer dependency graph:
Domain -> Data
Notice that the arrow is different from the initial example that lies at the base of your question. This is called an inverted dependency, because the arrow is reversed compared to the flow of the algorithm:
Web <- Domain -> Data
<-------------------------- APPLICATION FLOW
When you look at Clean Architecture diagrams, the DAL is often put on the same level as the top-level consumer (e.g. Web project) since both individually depend on the domain.
- In this answer, the domain and application layers have been lumped into one big "domain" blob because their distinction isn't the topic at hand.
- Persistence is the DAL. This is often just lumped into the infrastructure category in other diagrams.
- In other similar diagrams, "entities" are part of the domain, but beware that in those cases, "entities" means "domain objects".
Implementing an inverted dependency
So how do we implement this, on a technical level? There are some key points that are different to how you may have been implementing your original "linear sequence" dependency graph. When using an inverted dependency:
- The domain defines the interface (e.g.
- The domain's logic (e.g.
FooManager) has an injected
IFooRepository dependency which it operates on.
- The DAL implements the interface in e.g.
Data.FooRepository. It has to fulfill the contract that the Domain demands, which means that the domain forces the DAL to provide all features that the domain uses when interacting with its own
- The top-level project uses a DI container which registers
Those are the basic steps to starting with inverted dependencies. For more detailed information, you might want to look up a tutorial online.
Also note that while I use "repository" and "manager" classes, it's highly contested whether you should use the repository pattern in conjuction with EF. Especially when your dependency is inverted, there's actually little reason to do so. Me calling it a "repository" was only done to keep the explanation simple by using recognisable class names.
- This way you can leverage the EF ability to trace changes on entities to detect changes to domain objects. That would be quite challanging to implement by yourself.
Even when not using inverted dependencies, you can actually get around the loss of change tracking if you use interfaces instead of objects, and both the domain object and entity implement the same interface.
E.g. if I pass a
Domain.Foo : IFoo to my DAL (as an
IFoo) it gets converted to a
DAL.Foo : IFoo, but my DAL returns
DAL.Foo (as an IFoo) to the domain as the result of its queries.
If I then take something from the DAL and then hand it back to the DAL, it's actually still using the same tracked
This does come with some additional mapping logic and requires some extra effort, but it works.
- the fact that 'Course' class is both an entity and a domain object is something that you have to be aware of when you implement additional methods, let's say validation code or code related to the domain logic. This seems to me a violation of SRP, which practically means that if the aims of the "two classes merged into one" happen to diverge, you've got a problem.
Different layers can define their own validation rules.
If you wish to use the same class in both layers (both as the domain object and the entity), then that object inherently needs to pass both kinds of validations (each executed at an appropriate time in their own layer).
That's an inevitable consequence from using the same object in two different layers which both demand validation. The alternative is using a separate dto for each layer, which brings me to your next point:
- This approach saves from copying values from dao to domain object and vice versa
Tools like Automapper are a huge help here. You still need to create the two separate classes, but most properties can be automatically mapped using convention-based rules (e.g. properties of the same name and type get picked up automatically).