entity-frameworkasp.net-web-apiunit-of-work

ASP.NET Web API - Service Between Controller and Repository?


I've been following this example in setting up my data access logic for my web API project: https://codewithmukesh.com/blog/repository-pattern-in-aspnet-core/. It discusses a repository wrapper / unit of work pattern.

About halfway down the page, the author writes this:

"Ideally you would want to have a service layer between the Repository and Controllers. But, to keep things fairly simple, we will avoid the service layer now."

The trouble is, every single example I find on the Internet wants to "keep things fairly simple" and doesn't mention any implementation of a service layer that sits between the controller and the repositories.

I think this may be the solution to my problem (Unit of Work Pattern with Foreign Keys - ASP.NET Web API), but I'm drawing a blank on how I would organize it.

Basically I have my controller project dealing only with DTOs /Model objects. They currently request DTOs from the repository layer, and send DTOs back for updates. The repository layer knows how to convert between entities and DTOs and back again, but the controller layer (my Web API project) doesn't have any idea what an entity is.

It works great, but now I have a requirement to do all updates in single transaction, including writing of newly inserted foreign keys. The Repository / Unit of Work pattern says: make all your updates, to whatever repositories need updating, but call DbContext.SaveChanges() only once, at the end. The wrapper has a SaveChanges() method, and that's what you call.

This is very easy if the repositories return entities instead of DTOs. The caller of the repository methods then need to coordinate the updates of the entities, then call SaveChanges. No problem! I can just deal with entities in my Controllers instead of DTOs!

Ah, but the mess. I want my Controllers clean. So a service layer in between sounds great! I'll do all the messy work of converting between entities and DTOs there, and the Controller gets to live a nice peaceful life, delegating its dirty work to the underclasses.

So my question is simply this: where to put this service layer. How to structure the dependency injection around it. Should it also play a role in simple Read operations? Should it live in its own project, or part of the Repository project?

I would love to hear from someone who actually did it this way, preferably on a larger project.

Thanks so much! Happy first day of Spring!


Solution

  • A service layer is extremely important when programming larger projects. It creates code that is more loosely coupled, prevents duplicate code, and ultimately your API has to do much less work.

    The service layer can be seen as an intermediate layer between the controllers and the repositories. Its role is to provide high-level operations that the controllers can use to interact with the repositories in a more abstract way. The service layer should be responsible for managing the business logic and any validation checks before calling the repositories to perform any database operations.

    Here is a link to a project I started that is still very bare bones, which gives a solid understanding of the topics you spoke about in your question.

    Here, you will see an ApplicationCore project, this project holds all of the Core components of the project. The entities, models, and more.

    You will also see a Infrastructure project. And yep, you guessed it, this holds all of the infrastructure to the project. Here you will find the service layer you mentioned, the data folder with the DbContext, migrations, the repositories, and more. This is where you can create service methods based on the models, enums, etc from the ApplicationCore, which is eventually called in your API endpoints.

    The infrastructure project is dependent on the ApplicationCore project, and the API project is dependent on Infrastructure. You can create separate Interfaces that are implemented in separate services to create loosely coupled code that is also reusable in other places. For instance, in the above example you will see that I am able to call two different repositories which hold different methods within the same service layer, which allows me to use a service method I create in one file in another to incorporate it into what I want to do with the new method.

    Basic example, I create a method called GetAllBooks(). Great. Now I want a method that retrieves specific information about all of my books. Instead of creating a larger method, I can simply call GetAllBooks() in my new method and tweak it in the body of my method.

    I hope this answers your question, and if it doesn't please comment and I can try to be more specific.