Question

I am working on a WebAPI application which follows the layered approach like Controller > Service Layer > Repository Layer > Entity Framework Core (SQL / Cosmos) The view is in Angular.

In many of our API, we have some transformation required on the Request object (DTO - request send by UX) to a domain entity that my repository understands. This transformation usually is handled in the Service layer. This is the standard approach I think.

Now I have an Request object (DTO - request sent by UX, shown below) which is a simple class and I don't need any transformation from DTO to Domain entity. Infact I have a DbSet matching exactly this and the database table has exactly these 3 columns only. In this case, I end up doing the transformation from DTO to Domain entity unnecessarily.

    public class BookDTO //Received in the API request
    {
        public string Name { get; set; }
        public string Author { get; set; }
        public decimal price { get; set; }
    }

To avoid this pointless mapping / transformation I can use the same DTO across all the layers (Controller to Service to Repository), But I feel this is not the right way to do. (Do let me know if there is nothing wrong with this approach)

Essentially either I will be doing a transformation of DTO to domain model when they have absolutely same attributes or I will end up referencing the DTO in all layers including the repository as well.

I am not sure if these are the only two options for me or there is a gap in my understanding.

Was it helpful?

Solution

It might be helpful to understand why the Repository and Service Layers exist.

The purpose of your Repository layer is to provide CRUD operations.

The Service Layer is there so that you can provide endpoints that return aggregate business objects instead of making multiple (expensive) CRUD calls to your Web API and assembling a complete object on the client.

An example of a CRUD object would be things like Products, Customers, and Addresses.

An example of an aggregate business object would be an Invoice. An Invoice object contains a Customer object, two Address objects (billing address, shipping address), and multiple Product objects.

The Service Layer assembles an Invoice object by making multiple calls to the database and sending the entire object to the client in one go. This improves efficiency by reducing the overhead of multiple HTTP calls and simplifying the API.


So what happens if you really do just need a CRUD object?

You have three choices:

  1. Retrieve the CRUD object by calling the Repository directly, or
  2. Pass the CRUD object through the Service Layer directly, or
  3. Map the CRUD object to a new object, and return that.

The reason choice 3 might be desirable is because it provides isolation. The Service Layer object can be modified independently of the CRUD DTO, and there are no surprises later on when someone finds out (in options 1 and 2) that changes to a DTO object unexpectedly broke something else in the system.

Further Reading
Too many Use Case Classes

Licensed under: CC-BY-SA with attribution
scroll top