Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple Dtos for same entity

Is it a good practice to use multiple DTO's for same entity in different API endpoints. For example: I have a api endpoint which accpets the following Dto:

public class AddressDto
{
    public string City { get; set; }
    public string Country { get; set; }
    public string Contact { get; set; }
    public string Street1 { get; set; }
    public string Street2 { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
}

And now there is second Api which accepts the same dto but in that api call I'm using only Streer1, Street2, Contact all other are ignored.

Should I make another DTO for second api endpoint like:

public class AddressDtoForSecondAPI
{
    public string Contact { get; set; }
    public string Street1 { get; set; }
    public string Street2 { get; set; }
}
like image 919
Util Avatar asked May 28 '18 06:05

Util


People also ask

Should I have different DTOs for create and update?

You do not need another DTO for create operation. You just set the default value(id=0) for creating a new object. This will help you for figuring out if the object is yet to be created in database in case you have to.

Are entities DTOs?

Short answer: Entities may be part of a business domain. Thus, they can implement behavior and be applied to different use cases within the domain. DTOs are used only to transfer data from one process or context to another.

Can DTO extend entity?

Usually, a DTO will be a subset of the entity data or also contain data from other associations in sub-DTOs or directly embedded in that DTO. If the DTO extends the entity, a user of a DTO object will have the possibility to invoke a getter to access all that state.

Should DTOs use inheritance?

More fundamentally, we should not, in principle, use inheritance among DTOs, or for that matter any types, solely to share implementations between them. In other words, unless there is a logical, "is a" relationship between types they should not inherent from one another.


1 Answers

In short, yes it is acceptable.


However, as you can see in the comments and the other answer, not everyone agrees here. So let me explain my answer.

Argument 1 - Misleading the consumer

And now there is second Api which accepts the same dto but in that api call I'm using only Streer1, Street2, Contact all other are ignored.

The issue here is one of making your intentions clear. If you allow a consumer to send you a fully fleshed AddressDTO, but then only use a subset of properties, then you're misleading your consumer. You've made them think that the other properties are relevant.

This is effectively the same as:

public int AddNumbersTogether(int a, int b, int c, int d)
{
    return a + c + d; //we ignore b
}

There is no reason for b to exist. Anyone who uses this method is going to be scratching their head when AddNumbersTogether(1,2,3,4) returns a value of 8. The syntax contradicts the behavior.

Yes, it's easier to omit an unused method parameter than it is to develop a second DTO. But you need to be consistent here and stick to the same principle: not misleading the consumer.

Argument 2 - A DTO is not an entity

Your consumer's interaction with your API(s) needs to happen without the consumer knowing anything about the structure of your database records.

This is why you're using a DTO and not your entity class to begin with! You're providing a logical separation between taking an action and storing the data of that action.

The consumer doesn't care where the data is stored. Regardless of whether you store the street in the same table as the address, or a diferent table (or database) altogether, does not matter in scope of the consumer calling an API method.

Argument 3 - Countering S.Akbari

What about inheritance and/or interface segregation principle in SOLID? – S.Akbari

These are not valid arguments for this particular case.

Inheritance is a flawed approach. Yes, you can technically get away with doing something like AddressDto : AddressDtoForSecondAPI in the posted example code, but this is a massive code smell.
What happens when a third DTO is needed, e.g. one where only zip codes and city names are used? You can't have AddressDto inherit from multiple sources, and there is no logical overlap between AddressDtoForSecondAPI and the newly created AddressDtoForThirdAPI.

Interfaces are not the solution here. Yes, you could technically created an IAddressDtoForSecondAPI and IAddressDtoForThirdAPI interface with the appropriate fields, and then do something like AddressDto : IAddressDtoForSecondAPI, IAddressDtoForThirdAPI. However, this is the same massive code smell again.

What happens if the second and third variation have a few shared properties, and a few individual ones? If you apply interface segregation, then the overlapping properties need to be abstracted in an interface by themselves.
If then a fourth variation presents itself, which has some properties in common with the second variation, some with the third variation, some with both the second AND third variation, and some individual properties, then you're going to need to create even more interfaces!

Given enough variations of the same entity and repeatedly applying the interface segregation principle; you're going to end up with an interface for every property of the entity; which requires a ridiculous amount of boilerplating. You'll end up with something like:

public class AddressDto : IAddressCity, IAddressCountry, IAddressContact, IAddressStreet1, IAddressStreet2, IAddressState, IAddressZip
{
    public string City { get; set; }
    public string Country { get; set; }
    public string Contact { get; set; }
    public string Street1 { get; set; }
    public string Street2 { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
}

Imagine having to do this for all classes; since the same principle would apply to every DTO that is being used by the API.

Argument 4 - DRY does not apply here

I sort of get why you're apprehensive of creating two classes. Most likely, there's a DRY/WET error flag being raised in your mind.

Avoiding WET is a good reflex to have; but you can't always listen to it. Because if you were to really avoid duplication, then you should effectively also not create separate entity and DTO classes, as they are usually copy/pastes of each other.

DRY is not an absolute. Taking the entity/DTO example, there is a balance of considerations here:

  • Do you want avoid repetition at all costs? (= DRY)
  • Do you want to separate your DAL from your API logic? (= separation of concerns)

In this case, the latter generally wins out.

The same argument applies in your case. The argument against following DRY (which is the arguments I just listed) far outweighs the benefits of following DRY in this scenario.

like image 113
Flater Avatar answered Nov 02 '22 19:11

Flater