dotnet-architecture / eShopOnWeb

Sample ASP.NET Core 8.0 reference application, powered by Microsoft, demonstrating a layered application architecture with monolithic deployment model. Download the eBook PDF from docs folder.
https://docs.microsoft.com/dotnet/standard/modern-web-apps-azure-architecture/
MIT License
10.07k stars 5.42k forks source link

Questions on general architecture (Generic repository, multiple dbContexts, service layer) #134

Closed fabercs closed 5 years ago

fabercs commented 5 years ago

This is a batch question for guidance at my an ongoing project. Well, I am trying to follow clean architecture structure.

1- First of all, is there any enforcement to not to call generic repository IRepository<User> from Web api controllers? Should I always create a Service Layer for at least a simple query anyway? Or should I build a specific repository that inherits generic repository than use it in controllers as the sample does? From my understanding I can call IRepository from controllers, which has not services.

2- In my project to use multiple EF Core dbContexts, I have changed my IRepository<TEntity> contract to IRepository<TContext, TEnitity>, to call it from a controller or service. So inside a service ( Project.Core ) or a controller ( Project.Web ) I always have a reference to ( Project.Infrastructure ) thanks to context dependency. I have dependency to these context objects inside at Startup class as well as it is discussed earlier in some issues posted here and eshoponweb repo's. Also, when I need to implement an UnitOfWork pattern I still dependent to context object. So is this a real red line not to get accross which has the only [this argument ] ?(https://github.com/ardalis/CleanArchitecture/issues/18#issuecomment-399950805) Or is it better to try make dbContext object stick to an interface such as IDbContext?

3- E.g a user with posts and comments will be deleted completely from application. For that I have an api endpoint like in my controller

/api/User/DeleteUserEntirely/?id

So inside my controller, I have an IUserService dependency which handles this process. Where this IUserService's implementation detail should live? Is this a domain service or application service?

Clean architecture puts the business logic and application model at the center of the application. Instead of having business logic depend on data access or other infrastructure concerns, this dependency is inverted: infrastructure and implementation details depend on the Application Core. This is achieved by defining abstractions, or interfaces, in the Application Core, which are then implemented by types defined in the Infrastructure layer. A common way of visualizing this architecture is to use a series of concentric circles, similar to an onion

According to this passage from Clean Architecture book, it should reside in Infrastructure layer. However inside eshoponWeb project services reside in Core project?

Thanks in advance.

ardalis commented 5 years ago
  1. No, there is no enforcement. You certainly can call repositories directly from controllers, and many applications do so. If your model is such that your entities have a lot of behavior embedded in them, then many controller actions may simply consist of:
// get entity via repository
// work with the entity via its methods and properties
// save entity via repository

However, some teams do establish standards that, in order to keep controllers lean, they only call a service layer from the controllers or they only raise events (e.g. MediatR) from controller actions, which are handled elsewhere. So, that's also something to keep in mind.

  1. Personally I wouldn't expose DbContext through your interfaces. That will mess up your use of the Dependency Inversion Principle and will force everything in your project to depend on EF Core/infrastructure. If you have separate DbContexts because they correspond to different domains, then I would use domain-specific repository interfaces, rather than generic ones that depend on DbContext type. That should also result in more intention-revealing code and in code that won't require the client code to know so much about the internal implementation details of your abstractions.

The whole point of a repository abstraction is to encapsulate the details of how data access is performed. If you have a repository abstraction that includes in its definition details of one particular implementation, it's not an effective abstraction. Hence, you don't want repository interfaces that return SqlDataReader, for instance, because it's going to be very hard to later implement that against an XML file or a web API. The same is true if the interface itself exposes a DbContext.

  1. I try to push logic into Core whenever possible. The only thing that forces me to put it into Infrastructure is if it has dependencies on infrastructure, or into Web if it has UI-level dependencies (like ViewModels or MVC types). So, I would put this service in Core, and I would construct it such that it took in a repository or two to perform its work (and perhaps a Unit of Work). And the implementations of those repositories would be in Infrastructure.

The book doesn't suggest that all services go into Infrastructure, just the low level implementation details. The calls to EF to perform the delete are low level. The orchestration of deleting multiple things in an object hierarchy, error handling, perhaps unit of work implementation, follow-on work like notifications, etc. is all higher level logic that can go in a Core service (which operates against interfaces). Let me know if that makes sense.

fabercs commented 5 years ago

@ardalis Thank you very much for your response, it all does make sense, just a few points,

For exposing DbContextin IRepository all the things got engaged (generic repositories with multiple contexts and transactions etc), so did it unwittingly, thanks for your reviews, I will revert it.

For a concrete example of domain-specific repository interface, let's say I have 2 DbContexts AppDbContext LogDbContext and I have IRepository<TEntity> for AppDbContext, do you suggest me something like this ILogRepository : IRepository<Log> and use LogDbContext inside it?

And, no matter what, as long as any repository or uow implementation would be inside Infra, I will always have reference from Web to Infra for now(until built-in container features appears, not willing to stick to external IoC dlls ), am I right?

ardalis commented 5 years ago

Yes, something like ILogRepository would work, and you could have your concrete implementation in the Infrastructure project request a LogDbContext. Assuming this is for logging, I probably wouldn't call it a repository, since typically you don't do updates or deletes or even many reads of log entries. If that's the case, a more intention-revealing name might simply be an ILogger. But I don't know your model or application so I could be way off here.

And yes it's normal to have a reference from Web to Infrastructure, but the only place you should be using that reference is in Startup ConfigureServices, typically. It is possible to wire things up without the reference but it requires some work and can make it a little harder to run the application or get set up if you're a new developer unfamiliar with the project structure. So, unless you're having a very hard time keeping your team from using Infrastructure types in Web (outside Startup), it's not something I see many teams doing.

fabercs commented 5 years ago

It was just a bad choice for naming the context. Now all is ok, thank you for your efforts.