Saltar al contenido

Cómo convertir correctamente las entidades de dominio en DTO teniendo en cuenta la escalabilidad y la capacidad de prueba

Estate atento porque en esta crónica vas a hallar el arreglo que buscas.

Solución:

Solución 1: método privado en la capa de servicio para convertir

creo Solucion 1 no funcionará bien, porque sus DTO están orientados al dominio y no al servicio. Por tanto, es probable que se utilicen en diferentes servicios. Por lo tanto, un método de mapeo no pertenece a un servicio y, por lo tanto, no debe implementarse en un servicio. ¿Cómo reutilizaría el método de mapeo en otro servicio?

La solución 1. funcionaría bien si utiliza DTO dedicados por método de servicio. Pero más sobre esto al final.

Solución 2: constructor adicional en el DTO para convertir la entidad de dominio en DTO

En general una buena opción, porque puedes ver el DTO como un adaptador a la entidad. En otras palabras: el DTO es otra representación de una entidad. Estos diseños a menudo envuelven el objeto de origen y proporcionan métodos que le brindan otra vista del objeto envuelto.

Pero un DTO es un dato transferir object para que tarde o temprano se serialice y se envíe a través de una red, por ejemplo, utilizando las capacidades de comunicación remota de Spring. En este caso, el cliente que recibe este DTO debe deserializarlo y, por lo tanto, necesita las clases de entidad en su classpath, incluso si solo usa la interfaz del DTO.

Solución 3: usando Spring’s Converter o cualquier otro Bean externalizado para esta conversión

La solución 3 es la solución que yo también preferiría. Pero yo crearía un Mapper interfaz que es responsable de mapear desde el origen al destino y viceversa. P.ej

public interface Mapper 
     public T map(S source);
     public S map(T target);

La implementación se puede realizar utilizando un marco de mapeo como modelmapper.


También dijiste que un convertidor para cada entidad

no se “escala” tanto a medida que crece mi modelo de dominio. Con muchas entidades, tengo que crear dos convertidores para cada nueva entidad (-> convertir la entidad DTO y la entidad a DTO)

Digo que solo tiene que crear 2 convertidores o un mapeador para un DTO, porque su DTO está orientado al dominio.

Tan pronto como empiece a utilizarlo en otro servicio, reconocerá que el otro servicio normalmente debería o no puede devolver todos los valores que hace el primer servicio. Comenzará a implementar otro mapeador o convertidor para cada uno de los otros servicios.

Esta respuesta sería demasiado larga si empiezo con los pros y los contras de los DTO dedicados o compartidos, por lo que solo puedo pedirle que lea los pros y contras de los diseños de capas de servicio de mi blog.

EDITAR

Acerca de la tercera solución: ¿dónde prefiere poner la llamada al mapeador?

En la capa sobre los casos de uso. Los DTO son objetos de transferencia de datos, porque empaquetan datos en estructuras de datos que son mejores para el protocolo de transferencia. Por eso llamo a esa capa la capa de transporte. Esta capa es responsable de mapear la solicitud del caso de uso y los objetos de resultado desde y hacia la representación de transporte, por ejemplo, estructuras de datos json.

EDITAR

Veo que está de acuerdo con pasar una entidad como parámetro de constructor DTO. ¿También estarías de acuerdo con lo contrario? Quiero decir, ¿pasar un DTO como parámetro de constructor de entidad?

Buena pregunta. Lo contrario no estaría bien para mí, porque luego introduciría una dependencia en la entidad a la capa de transporte. Esto significaría que un cambio en la capa de transporte puede afectar a las entidades y no quiero que los cambios en capas más detalladas afecten a capas más abstractas.

Si necesita pasar datos de la capa de transporte a la capa de entidad, debe aplicar el principio de inversión de dependencia.

Introduzca una interfaz que devuelva los datos a través de un conjunto de captadores, deje que el DTO lo implemente y use esta interfaz en el constructor de entidades. Tenga en cuenta que esta interfaz pertenece a la capa de la entidad y, por lo tanto, no debería tener ninguna dependencia con la capa de transporte.

                                interface
 +-----+  implements     ||   +------------+   uses  +--------+
 | DTO |  ---------------||-> | EntityData |  <----  | Entity |
 +-----+                 ||   +------------+         +--------+

Me gusta la tercera solución de la respuesta aceptada.

Solución 3: usando Spring's Converter o cualquier otro Bean externalizado para esta conversión

Y yo creo DtoConverter De este modo:

Marcador de clase BaseEntity:

public abstract class BaseEntity implements Serializable 

Marcador de clase AbstractDto:

public class AbstractDto 

Interfaz GenericConverter:

public interface GenericConverter 

    E createFrom(D dto);

    D createFrom(E entity);

    E updateEntity(E entity, D dto);

    default List createFromEntities(final Collection entities) 
        return entities.stream()
                .map(this::createFrom)
                .collect(Collectors.toList());
    

    default List createFromDtos(final Collection dtos) 
        return dtos.stream()
                .map(this::createFrom)
                .collect(Collectors.toList());
    


Interfaz CommentConverter:

public interface CommentConverter extends GenericConverter 

Implementación de la clase CommentConveter:

@Component
public class CommentConverterImpl implements CommentConverter 

    @Override
    public CommentEntity createFrom(CommentDto dto) 
        CommentEntity entity = new CommentEntity();
        updateEntity(entity, dto);
        return entity;
    

    @Override
    public CommentDto createFrom(CommentEntity entity) 
        CommentDto dto = new CommentDto();
        if (entity != null) 
            dto.setAuthor(entity.getAuthor());
            dto.setCommentId(entity.getCommentId());
            dto.setCommentData(entity.getCommentData());
            dto.setCommentDate(entity.getCommentDate());
            dto.setNew(entity.getNew());
        
        return dto;
    

    @Override
    public CommentEntity updateEntity(CommentEntity entity, CommentDto dto) 
        if (entity != null && dto != null) 
            entity.setCommentData(dto.getCommentData());
            entity.setAuthor(dto.getAuthor());
        
        return entity;
    


Terminé NO usando una biblioteca de mapeo mágica o una clase de convertidor externo, sino que solo agregué un pequeño frijol propio que tiene convert métodos de cada entidad a cada DTO que necesito. La razón es que el mapeo fue:

cualquiera estúpidamente simple y simplemente copiaría algunos valores de un campo a otro, tal vez con un pequeño método de utilidad,

o era bastante complejo y sería más complicado escribir los parámetros personalizados en alguna biblioteca de mapeo genérica, en comparación con simplemente escribir ese código. Esto es, por ejemplo, en el caso en el que el cliente puede enviar JSON pero, bajo el capó, esto se transforma en entidades, y cuando el cliente recupera el objeto principal de estas entidades nuevamente, se convierte nuevamente en JSON.

Esto significa que puedo llamar .map(converter::convert) en cualquier colección de entidades para recuperar un flujo de mis DTO.

¿Es escalable tenerlo todo en una clase? Bueno, la configuración personalizada para este mapeo tendría que almacenarse en algún lugar, incluso si se usa un mapeador genérico. El código es generalmente extremadamente simple, excepto en un puñado de casos, por lo que no me preocupa demasiado que esta clase aumente en complejidad. Tampoco espero tener docenas más de entidades, pero si lo hiciera, podría agrupar estos convertidores en una clase por subdominio.

Agregar una clase base a mis entidades y DTO para poder escribir una interfaz de convertidor genérico e implementarla por clase tampoco es necesario (¿todavía?) Para mí.

Puntuaciones y comentarios

Si haces scroll puedes encontrar las críticas de otros creadores, tú asimismo tienes la opción de mostrar el tuyo si lo crees conveniente.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *