18

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; }
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Util
  • 191
  • 1
  • 5
  • 3
    What about [inheritance](https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/inheritance) and/or [interface segregation principle](https://en.wikipedia.org/wiki/Interface_segregation_principle) in SOLID? – Salah Akbari May 28 '18 at 06:14
  • Do both the addresses end up in same database table? – danish May 28 '18 at 06:18
  • 1
    Agree with @S.Akbari think of data integrity as well. Though I am not sure if this is even possible or not. Whatever the reason could be, but looks risky (if possible). – PM. May 28 '18 at 06:21
  • @S.Akbari, You are right. But I'll end up with multiple classes for same entity. Is that a good practice? Should I make 3-4 classes for a single entity? – Util May 28 '18 at 06:32
  • @danish, yes, it'll end up in same database table. – Util May 28 '18 at 06:32
  • 1
    Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise. – Reza Aghaei May 28 '18 at 06:40
  • 1
    @RezaAghaei: I disagree here, especially to your remark that it can't be answered based on specific expertise. The implementation of SOLID and DRY principles is something that is hugely commonplace in OOP, and is in no way uncharted territory. If we take your comment to heart, then every mention of preventing SQL injection is similarly off topic due to its usage being a matter of "opinion" and implementing it not being irrefutably technically superior as opposed to building a string. – Flater May 28 '18 at 07:33

1 Answers1

32

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.

Flater
  • 12,908
  • 4
  • 39
  • 62