DTO Composition and Inheritance – Using Composition and Inheritance for DTOs

api-designdtopocorestweb-api

We have a ASP.NET Web API that provides a REST API for our Single Page Application. We use DTOs/POCOs to pass data through this API.

The problem is now, that these DTOs are getting bigger over time, so now we want to refactor the DTOs.

I am looking for "best practices" how to design a DTO: Currently we have small DTOs that consist only of value-type fields, e.g:

public class UserDto
{
    public int Id { get; set; }

    public string Name { get; set; }
}

Other DTOs use this UserDto by composition, e.g.:

public class TaskDto
{
    public int Id { get; set; }

    public UserDto AssignedTo { get; set; }
}

Also, there are some extended DTOs that are defined by inheriting from others, e.g.:

public class TaskDetailDto : TaskDto
{
    // some more fields
}

Since some DTOs have been used for several endpoints/methods (e.g. GET and PUT), they have been extended incrementally by some fields over time. And due to inheritance and composition other DTOs also got bigger.

My question is now are inheritance and composition not good practices? But when we do not reuse them, it feels like writing the same code multiple times.
Is it a bad practice to use a DTO for multiple endpoints/methods, or should there be different DTOs, which only differ in some nuances?

Best Answer

As a best practice, try and make your DTOs as concise as possible. Only return what you need to return. Only use what you need to use. If that means a few extra DTOs, so be it.

In your example, a task contains a user. One probably doesn't need a full user object there, maybe just the name of the user that is assigned the task. We don't need the rest of the user properties.

Let's say you want to reassign a task to a different user, one may be tempted to pass a full user object and a full task object during the re-assignment post. But, what is really needed is just the task Id and the user Id. Those are the only two pieces of information needed to re-assign a task, so model the DTO as such. This usually means there is a separate DTO for every rest call if your striving for a lean DTO model.

Also, sometimes one may need inheritance/composition. Let's say one has a job. A job has multiple tasks. In that case, getting a job may also return the list of tasks for the job as well. So, there is no rule against composition. It depends on what is being modeled.

Related Topic