Util
Util

Reputation: 321

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; }
}

Upvotes: 21

Views: 10888

Answers (1)

Flater
Flater

Reputation: 13773

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.

Upvotes: 38

Related Questions