Índice
Mientras estaba haciendo el código para trabajar con el post de la API cuando me he dado cuenta que nunca he llegado a explicar una diferencia mínima, pero crucial a la hora de desarrollar APIs.
Necesitamos entender la diferencia entre DTO y Entity
1- Qué es un DTO
Como su nombre indica un DTO
es un “Data Transfer Object” y es el objeto que vamos a devolver desde nuestras API hacia otros servicios. Y únicamente debe contener datos, nada de lógica de negocio.
Este objeto debe ser serializable.
Nota: ViewModel NO es lo mismo que DTO, pese a que en muchos escenarios se pueden solapar, un viewModel puede contener lógica de negocio, y los utilizamos en aplicaciones web, mientras que el DTO es para las API. Una práctica común es que un ViewModel esté compuesto por múltiples Dto, entre otras muchas propiedades.
2 - Qué es una Entity
Una entidad se compone de dos puntos
- Un objeto que representa datos de la base de datos; Como podría ser un objeto que represente cada fila de la base de datos.
- Entidad que encapsula reglas críticas de nuestra aplicación que están relacionadas con este objeto. Y que pueden contener cierta lógica de negocio.
Hay que tener en cuenta que NUNCA debe contener más propiedades que las que contiene en la base de datos.
3 - Por qué necesitamos diferenciar DTO y Entity?
La forma más sencilla de ver porque necesitamos esta separación es viendo un ejemplo.
vamos a basar el ejemplo en una tabla de la base de datos que vamos a utilizar para este proyecto, como recordamos del post anterior es pasar a una web, nuestro cv.
Para este ejemplo voy a utilizar la parte de educación, donde disponemos de un objeto que contiene: id, fecha inicio, fecha fin, nombre del grado, el centro educativo o universidad y finalmente la nota. El cual corresponde con cada fila en la base de datos:
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; }
}
Con esto en mente imaginemos que estamos devolviendo esta información en nuestra API. Y tenemos otras aplicaciones o nuestro propio front end que haremos en blazor que coge esta información y la imprime.
Por ahora bien, pero, qué pasa, si ya no queremos guardar la información de la nota en la base de datos, porque debido a nuestra experiencia es completamente irrelevante; La nota solo es relevante para el primer trabajo.
Como hemos dicho la entity únicamente contiene los campos de la base de datos por lo que deberemos quitar la nota de la propia entidad.
Llegados a este punto tenemos dos opciones:
- La nota, en vez de obtenerla de la base de datos la obtenemos de otra fuente. Imaginémonos que obtenemos la nota directamente del gobierno, en este caso también estamos quitando la nota de la entidad. Pero, Seguimos enviando la nota. En este escenario únicamente debemos actualizar de donde la nota es obtenida, el resto de aplicaciones o servicios NO se verán afectados por este asunto.
- En nuestro caso particular, la debemos quitar la propiedad nota del DTO, pero claro, el resultado final será el mismo, nuestro DTO no contendrá la propiedad. En este escenario, lo mejor es versional la API o el objeto en sí. Ya que otros servicios que usan nuestra API pueden fallar debido a que tenemos un campo menos.
4 - Versionar API en C#
Primero de todo y antes de comenzar, esto es muy importante, si estáis utilizando una API y los únicos clientes o consumidores de esa API sois vosotros mismos NO realices versiones, sino intentad actualizar las aplicaciones que hacen referencia. Al final tiene menos trabajo actualizar dos aplicaciones que utilizan un servicio que mantener diferentes versiones de una API. Siempre y cuando lo podamos evitar claro.
Otra opción es mantener dos repositorios separados, directamente la versión 1 devuelve un resultado y la versión 2 devuelve otro. cuando se pasa de la version1 a la 2, la 1 solo se modifica para arreglar bugs, no se le añaden nuevas funcionalidades; este escenario es muy común en códigos antiguos.
Pero la que a mi me parece más recomendable es versionar la API en la URL
Ya que para el cliente es muy sencillo de entender, si llaman a `v1
` es la versión 1 de la api mientras que si llaman a `v2
` es la versión 2.
Como contra diremos que son los clientes los que deben actualizar la URL para llamar a la versión correcta.
Para utilizar versiones en .NET debemos usar la libreria Microsoft.AspNetCore.Mvc.Versioning
Y con esto ya podemos especificar qué versiones de la API vamos a aceptar utilizando el atributo de la clase [ApiVersion]
. Pero antes debemos añadir la versión a nuestros service middleware en el fichero startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddApiVersioning();
}
Una vez tenemos esta información debemos cambiar la ruta de nuestro controlador para añadir que se compruebe la versión.
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1")]
[ApiController]
public class PerfilPersonalController : ControllerBase
{
[HttpGet("{id}")]
public string Get(int id)
{
///Codigo
}
//Resto de métodos
}
Ahora mismo nuestra api solo aceptará llamadas a la url https://www.dominio.com/api/v1/perfilpersonal/{id}
Pero claro, buscamos tener múltiples Dto al mismo tiempo, ya que la versión 2 incluye quitar la nota de nuestro objeto, para ello en nuestra librería con el Dto debemos tener ambas versiones, pero en diferentes namespaces.
Para ello creamos una carpeta en el proyecto del Dto para la versión 1 y para la versión 2.
Y por supuesto debemos tener diferentes Dto a la hora de responder:
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; }
}
}
Bien, llegados a este punto tenemos dos Dto
con el mismo nombre, pero distinto namespace, pero lo que queremos es llamar a https://www.dominio.com/api/v2/perfilpersonal/{id}
y que nos devuelva el nuevo Dto
.
Para realizar dicha acción deberemos seguir la misma lógica con el controller que con el Dto que teniamos, que es, realizar múltiples namespaces.
Como podemos observar al tener dos controllers podemos devolver de cada uno de los controladores una versión diferente del DTO. Para ello debemos especificarlo en el using. Y por supuesto en el controlador, debemos especificar [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();
}
}
}
Nota: En mi caso he creado un mapper para mapear desde la entity al Dto, con lo que también tengo que versionar este Dto.
Y con esta acción ya podemos realizar llamadas a múltiples versiones de la API:
Si llamamos a la versión 1:
Url: https://localhost:44363/api/v1/Education/1
Respuesta:
{
"id": 1,
"startDate": "2020-06-14T14:11:50.3187551Z",
"endDate": null,
"courseName": "ejemplo",
"universityName": "UniversityEjemplo",
"mark": 5
}
Si llamamos a la versión 2:
Url: https://localhost:44363/api/v2/Education/1
Respuesta:
{
"id": 1,
"startDate": "2020-06-14T14:12:15.0414859Z",
"endDate": null,
"courseName": "ejemplo",
"universityName": "UniversityEjemplo"
}
Como vemos, para el cliente el único cambio que debe realizar para utilizar la nueva API es cambiar la versión y el resultado le vendrá sin el campo de la nota.
Conclusión
- Utilizar Dto y Entidades lo podemos considerar como una buena práctica, por lo que no siempre veremos sus beneficios nada más comenzar a desarrollarlo o trabajar con ella. Pero posiblemente si los veamos en caso de que tengamos que modificar la aplicación en el futuro o integrarla con otros servicios.
- Separar ambos conceptos nos dará independencia en el código, y cierto cambios NO se afectan unos a otros. Este es uno de los principales motivos por los que las aplicaciones antiguas son tan difíciles de mantener, Porque tienen todos los conceptos mezclados. No sabes donde ciertos cambios pueden afectar de una forma rápida, y eso tiene un impacto directo a la hora de avanzar. Separar conceptos nos lleva a un mantenimiento mucho más sencillo.
- Ambas clases NO deben compartir información, Por ejemplo si tanto el DTO como la entidad utilizan el objeto “curso” NO debemos utilizarlo como una propiedad en ambas, sino que tendremos tanto un DTO para un caso como una entidad para el otro. del mismo modo que si tenemos dos clases con una propiedad repetida no la hacemos implementar una clase con esa propiedad común.