Index
While I was writing the code for the API post, I realized that I never explained a minimal but crucial difference when developing APIs.
We need to understand the difference between DTO and Entity
1- What is a DTO
As its name suggests, a DTO
is a Data Transfer Object and it is the object we return from our API to other services. It should only contain data, and no business logic.
This object needs to be serializable.
Note: ViewModel is NOT the same as DTO. Even though they can overlap in many scenarios, a ViewModel can contain business logic and is used in web applications. Meanwhile, the DTO is for APIs. A common practice is for a ViewModel to be composed of multiple Dto, among other properties.
2 - What is an Entity
An entity is made up of two points:
- An object that represents data from the database; for instance, an object that represents each row in the database.
- An entity that encapsulates critical rules of our application related to this object, and may contain some business logic.
Keep in mind that an Entity should NEVER have more properties than what exist in the database.
3 - Why do we need to differentiate DTO and Entity?
The easiest way to see why we need this separation is through an example.
Let’s base our example on a table from the database that we’ll use for this project. As we remember from the previous post, it is used to build a web version of our resume.
For this example, I’ll use the education section, where we have an object containing: id, start date, end date, degree name, school or university, and finally the mark. Each corresponds to a row in the database:
public class Educacion
{
public int Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public string CourseName { get; set; }
public string UniversityName { get; set; }
public decimal Mark { get; set; }
}
With this in mind, let’s imagine we are returning this information in our API. Other applications or our own Blazor frontend take this info and display it.
So far so good, but what if we no longer want to save the mark in the database? Maybe based on our experience, it is now completely irrelevant. The mark only matters for the first job.
As we said, the entity contains only the fields from the database so we should remove the mark from the entity itself.
At this point, we have two options:
- Instead of getting the mark from the database, we fetch it from another source. Let’s say we get it directly from the government. In this case, we’re also removing the mark from the entity. But we still send the mark. In this scenario, we only need to update where the mark is obtained from. The rest of the applications or services will NOT be affected in any way.
- In our particular case, we need to remove the mark property from the DTO. But the result is the same; our DTO won’t include the property. In this scenario, the best thing is to version the API or the object itself. Other services using our API may fail because there is now one less field returned.
4 - Versioning API in C#
First, and this is important, if you are the only clients or consumers of your API, do NOT version it. Instead, try to update the applications that reference it. In the end, it’s less work to update two applications using a service than to maintain different versions of the API, whenever possible.
Another option is to keep two separate repositories, where version 1 returns one result and version 2 returns another. When moving from v1 to v2, v1 is only modified for bug fixes, not new features. This scenario is very common in legacy codebases.
But, in my opinion, the best practice is to version the API in the URL.
For the client, this is very simple to understand: if they call `v1
`, it’s version 1 of the API, and if they call `v2
`, it’s version 2.
The downside is that clients must update the URL to target the correct version.
To use versioning in .NET, you should use the Microsoft.AspNetCore.Mvc.Versioning
library.
With this, you can specify which API versions are accepted by using the [ApiVersion]
attribute. But first, add versioning to your middleware in the startup.cs file.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddApiVersioning();
}
Once you have this in place, you need to change your controller route to include version checking.
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1")]
[ApiController]
public class PerfilPersonalController : ControllerBase
{
[HttpGet("{id}")]
public string Get(int id)
{
///Code
}
//Other methods
}
Now, our API will only accept calls to the URL https://www.dominio.com/api/v1/perfilpersonal/{id}
But if we want to have multiple DTOs at the same time, since version 2 removes the mark field, our DTO library should have both versions but in different namespaces.
To do this, we create a folder for version 1 and a folder for version 2 in the DTO project.
And of course, we must have different DTOs for different responses:
namespace WebPersonal.Shared.Dto.V1
{
public class EducationDto
{
public int Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public string CourseName { get; set; }
public string UniversityName { get; set; }
public decimal Mark { get; set; }
}
}
namespace WebPersonal.Shared.Dto.V2
{
public class EducationDto
{
public int Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public string CourseName { get; set; }
public string UniversityName { get; set; }
}
}
So now, we have two Dto
s with the same name but different namespaces. What we want is to call https://www.dominio.com/api/v2/perfilpersonal/{id}
and get the new Dto
.
To achieve this, we need to follow the same logic with the controller as with the DTO, meaning multiple namespaces.
As you can see, by having two controllers, we can return a different version of the DTO from each one. You must specify this in the "using" statement. And of course, in the controller, you need to specify [ApiVersion("2")]
.
using System;
using Microsoft.AspNetCore.Mvc;
using WebPersonal.BackEnd.Model.Entity;
using WebPersonal.BackEnd.Model.Mappers.v2;
using WebPersonal.Shared.Dto.V2;
namespace WebPersonal.BackEnd.API.Controllers.v2
{
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("2")]
public class EducationController : ControllerBase
{
[HttpGet("{id}")]
public EducationDto Get(int id)
{
var entity = EducationEntity.Create(id, DateTime.UtcNow, null, "ejemplo", "UniversityEjemplo");
return entity.Map();
}
}
}
Note: In my case, I have created a mapper to map from Entity to DTO, so I also have to version this DTO.
With this, you can now make calls to multiple versions of the API:
If we call version 1:
Url: https://localhost:44363/api/v1/Education/1
Response:
{
"id": 1,
"startDate": "2020-06-14T14:11:50.3187551Z",
"endDate": null,
"courseName": "ejemplo",
"universityName": "UniversityEjemplo",
"mark": 5
}
If we call version 2:
Url: https://localhost:44363/api/v2/Education/1
Response:
{
"id": 1,
"startDate": "2020-06-14T14:12:15.0414859Z",
"endDate": null,
"courseName": "ejemplo",
"universityName": "UniversityEjemplo"
}
As you can see, for the client, the only change needed to use the new API is to change the version, and the result comes without the "mark" field.
Conclusion
- Using DTOs and Entities is considered a best practice, so you won’t always see the benefits right when you start developing or working with them. But you will probably notice them if you ever need to change the application in the future or integrate it with other services.
- Separating both concepts gives us code independence, so some changes do NOT affect each other. This is one of the main reasons why old applications are so hard to maintain: because everything is mixed together. You don't know where changes might have a quick impact and that slows down progress. By separating concepts, maintenance is much easier.
- Both classes should NOT share information. For example, if both the DTO and the Entity use the “course” object, we should NOT use it as a property in both. Instead, you have one DTO for one case and one Entity for the other. In the same way that if two classes have a repeated property, we don’t make them implement a class with that common property.
If there is any problem you can add a comment bellow or contact me in the website's contact form