Arquitectura limpia del tío Bob: ¿una clase de entidad / modelo para cada capa?

44

FONDO :

Estoy tratando de usar la arquitectura limpia del tío Bob en mi aplicación de Android. Estudié muchos proyectos de código abierto que intentan mostrar la forma correcta de hacerlo, y encontré una implementación interesante basada en RxAndroid.

Lo que noté:

En cada capa (presentación, dominio y datos), hay una clase de modelo para la misma entidad (UML parlante). Además, hay clases de mapeadores que se encargan de la transformación del objeto cada vez que los datos cruzan los límites (de una capa a otra).

PREGUNTA

¿Es necesario tener clases de modelo en cada capa cuando sé que todas terminarán con los mismos atributos si se necesitan todas las operaciones CRUD? ¿O es una regla o una práctica recomendada cuando se utiliza la arquitectura limpia?

Rami Jemli
fuente

Respuestas:

52

En mi opinión, no es así en absoluto. Y es una violación de DRY.

La idea es que el objeto de entidad / dominio en el medio está modelado para representar el dominio lo mejor y lo más conveniente posible. Está en el centro de todo y todo puede depender de él, ya que el dominio en sí no cambia la mayor parte del tiempo.

Si su base de datos en el exterior puede almacenar esos objetos directamente, entonces asignarlos a otro formato en aras de separar capas no solo no tiene sentido, sino que crea duplicados del modelo y esa no es la intención.

Para empezar, la arquitectura limpia se realizó con un entorno / escenario típico diferente en mente. Aplicaciones de servidor empresarial con capas externas gigantes que necesitan sus propios tipos de objetos especiales. Por ejemplo, bases de datos que producen SQLRowobjetos y necesitan SQLTransactionsa cambio de actualizar elementos. Si usara los del centro, violaría la dirección de dependencia porque su núcleo dependería de la base de datos.

Con ORM livianos que cargan y almacenan objetos de entidad, ese no es el caso. Ellos hacen el mapeo entre su SQLRowdominio interno y el suyo. Incluso si necesita poner una @Entitiyanotación del ORM en su objeto de dominio, diría que esto no establece una "mención" de la capa externa. Debido a que las anotaciones son solo metadatos, ningún código que no las busque específicamente las verá. Y lo más importante, nada necesita cambiar si los elimina o los reemplaza con una anotación de base de datos diferente.

Por el contrario, si cambias tu dominio e hiciste todos esos mapeadores, tienes que cambiar mucho.


Enmienda: Arriba está un poco simplificado e incluso podría estar equivocado. Porque hay una parte en la arquitectura limpia que quiere que crees una representación por capa. Pero eso tiene que verse en el contexto de la aplicación.

A saber, lo siguiente aquí https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

Lo importante es que las estructuras de datos simples y aisladas se pasan a través de los límites. No queremos engañar y pasar entidades o filas de bases de datos. No queremos que las estructuras de datos tengan ningún tipo de dependencia que viole la Regla de dependencia.

Pasar entidades desde el centro hacia las capas externas no viola la regla de dependencia, pero se mencionan. Pero esto tiene una razón en el contexto de la aplicación prevista. Pasar entidades alrededor movería la lógica de la aplicación hacia el exterior. Las capas externas necesitarían saber cómo interpretar los objetos internos, efectivamente tendrían que hacer lo que se supone que deben hacer las capas internas como la capa de "caso de uso".

Además de eso, también desacopla las capas para que los cambios en el núcleo no requieran necesariamente cambios en las capas externas (vea el comentario de SteveCallender). En ese contexto, es fácil ver cómo los objetos deben representar específicamente el propósito para el que se usan. Además, las capas deben comunicarse entre sí en términos de objetos que están hechos específicamente para el propósito de esta comunicación. Esto puede incluso significar que hay 3 representaciones, 1 en cada capa, 1 para el transporte entre capas.

Y hay https://blog.8thlight.com/uncle-bob/2011/11/22/Clean-Architecture.html que aborda arriba:

Otras personas se han preocupado de que el resultado neto de mi consejo sería una gran cantidad de código duplicado y muchas copias de memoria de una estructura de datos a otra a través de las capas del sistema. Ciertamente tampoco quiero esto; y nada de lo que he sugerido conduciría inevitablemente a la repetición de estructuras de datos y a una excesiva copia de campo.

Esa OMI implica que la copia simple de objetos 1: 1 es un olor en la arquitectura porque en realidad no estás usando las capas y / o abstracciones adecuadas.

Más tarde explica cómo imagina todas las "copias"

Separa la interfaz de usuario de las reglas de negocio al pasar estructuras de datos simples entre los dos. No deja que sus controladores sepan nada sobre las reglas comerciales. En cambio, los controladores desempaquetan el objeto HttpRequest en una estructura de datos simple y luego pasan esa estructura de datos a un objeto interactivo que implementa el caso de uso invocando objetos de negocios. El interactor luego reúne los datos de respuesta en otra estructura de datos vainilla y los devuelve a la IU. Las vistas no conocen los objetos comerciales. Simplemente miran esa estructura de datos y presentan la respuesta.

En esta aplicación, hay una gran diferencia entre las representaciones. Los datos que fluyen no son solo las entidades. Y esto garantiza y exige diferentes clases.

Sin embargo, se aplica a una aplicación simple de Android como un visor de fotos donde la Photoentidad tiene aproximadamente 0 reglas de negocio y el "caso de uso" que trata con ellas es casi inexistente y en realidad está más preocupado por el almacenamiento en caché y la descarga (ese proceso debería ser IMO representado más explícitamente), el punto de hacer representaciones separadas de una foto comienza a desaparecer. Incluso tengo la sensación de que la foto en sí misma es el objeto de transferencia de datos mientras falta la verdadera capa de núcleo de lógica de negocios.

Existe una diferencia entre "separar la interfaz de usuario de las reglas de negocio al pasar estructuras de datos simples entre los dos" y "cuando desee mostrar una foto, renómbrela 3 veces en el camino" .

Además de eso, el punto en el que veo que esas aplicaciones de demostración no representan la arquitectura limpia es que agregan un gran énfasis en la separación de capas en aras de la separación de capas, pero ocultan efectivamente lo que hace la aplicación. Eso contrasta con lo que se dice en https://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html , es decir, que

la arquitectura de una aplicación de software grita sobre los casos de uso de la aplicación

No veo ese énfasis en separar capas en la arquitectura limpia. Se trata de la dirección de dependencia y de centrarse en representar el núcleo de la aplicación (entidades y casos de uso) en Java de forma ideal sin dependencias hacia el exterior. No se trata tanto de dependencias hacia ese núcleo.

Entonces, si su aplicación realmente tiene un núcleo que representa las reglas de negocios y los casos de uso, y / o diferentes personas trabajan en diferentes capas, sepárelas de la manera prevista. Si, por el contrario, solo escribe una aplicación simple, no se exceda. 2 capas con límites fluidos pueden ser más que suficientes. Y las capas se pueden agregar más adelante también.

zapl
fuente
1
@RamiJemli Idealmente, las entidades son las mismas en todas las aplicaciones. Esa es la diferencia entre las "reglas de negocio de toda la empresa" y las "reglas de negocio de la aplicación" (a veces lógica de negocio versus aplicación). El núcleo es una representación muy abstracta de sus entidades que es lo suficientemente genérica como para que pueda usarla en todas partes. Imagine un banco que tiene muchas aplicaciones, una para atención al cliente, otra que se ejecuta en cajeros automáticos, una como interfaz de usuario web para los propios clientes. Todos ellos podrían usar lo mismo, BankAccountpero con reglas específicas de la aplicación, qué puede hacer con esa cuenta.
44
Creo que un punto importante en la arquitectura limpia es que al usar la capa de adaptador de interfaz para convertir (o como dice mapa) entre la representación de la entidad en las diferentes capas, se reduce la dependencia de dicha entidad. Si se produce un cambio en las capas Usecase o Entity (es de esperar que sea improbable, pero a medida que cambien los requisitos, estas capas también lo harán), el impacto del cambio está contenido en la capa del adaptador. Si elige utilizar la misma representación de la entidad en toda su arquitectura, el impacto de este cambio sería mucho mayor.
SteveCallender
1
@RamiJemli es bueno usar marcos que simplifican la vida, el punto es que debes tener cuidado cuando tu arquitectura se basa en ellos y comienzas a ponerlos en el centro de todo. Aquí hay incluso un artículo sobre el blog de RxJava.8thlight.com/uncle-bob/2015/08/06/let-the-magic-die.html : no está diciendo que no debas usarlo. Es más como: He visto esto, va a ser diferente en un año y cuando su aplicación aún esté disponible, estará atrapado en ella. Hágalo un detalle y haga las cosas más importantes en el viejo java mientras aplica los viejos y sólidos principios SOLID.
zapl
1
@zapl ¿Sientes lo mismo por una capa de servicio web? En otras palabras, ¿pondrías @SerializedNameanotaciones de Gson en un modelo de dominio? ¿O crearía un nuevo objeto responsable de asignar la respuesta web al modelo de dominio?
tir38
2
@ tir38 La separación en sí misma no proporciona el beneficio, es el costo de los cambios futuros que se reducen. => Depende de la aplicación. 1) le cuesta tiempo crear y mantener la etapa adicional que se transforma entre diferentes representaciones. Por ejemplo, agregar un campo al dominio y olvidar agregarlo en otro lugar no es desconocido. No puede suceder con el enfoque simple. 2) Cuesta pasar a una configuración más compleja más tarde en caso de que lo necesite. Agregar capas no es fácil, por lo tanto, es más fácil en aplicaciones grandes justificar más capas que no se necesitan de inmediato
zapl
7

En realidad lo entendiste bien. Y no hay violación de DRY porque acepta SRP.

Por ejemplo: tiene un método empresarial createX (nombre de cadena), entonces puede tener un método createX (nombre de cadena) en la capa DAO, llamado dentro del método empresarial. Pueden tener la misma firma y tal vez solo haya una delegación, pero tienen diferentes propósitos. También puede tener un createX (nombre de cadena) en UseCase. Incluso entonces no es redundante. Lo que quiero decir con esto es: las mismas firmas no significan la misma semántica. Elija otros nombres para que tenga clara la semántica. Nombrarse a sí mismo esto no afecta a SRP en absoluto.

UseCase es responsable de la lógica específica de la aplicación, el objeto comercial es responsable de la lógica independiente de la aplicación y el DAO es responsable del almacenamiento.

Debido a la semántica diferente, todas las capas pueden tener su propio modelo de representación y comunicación. A menudo ve "entidades" como "objetos comerciales" y, a menudo, no ve la necesidad de separarlos. Pero en los proyectos "enormes" se debe hacer un esfuerzo para separar adecuadamente las capas. Cuanto mayor sea el proyecto, aumenta la posibilidad de que necesite las diferentes semánticas representadas en diferentes capas y clases.

Puedes pensar en diferentes aspectos de la misma semántica. Un objeto de usuario debe mostrarse en la pantalla, tiene algunas reglas de coherencia interna y debe almacenarse en algún lugar. Cada aspecto debe estar representado en una clase diferente (SRP). Crear los mapeadores puede ser una molestia, por lo que en la mayoría de los proyectos que trabajé en estos aspectos se fusionan en una sola clase. Esto es claramente una violación de SRP pero a nadie realmente le importa.

Llamo a la aplicación de arquitectura limpia y SÓLIDA "no socialmente aceptable". Trabajaría con eso si me lo permiten. Actualmente no tengo permitido hacerlo. Espero el momento en que tenemos que pensar en tomar SOLID en serio.

usuario205407
fuente
Creo que ningún método en la capa de datos debe tener la misma firma que cualquier método en la capa de dominio. En la capa de Dominio, usa convenciones de nomenclatura relacionadas con la empresa, como registrarse o iniciar sesión, y en la capa de Datos, usa guardar (si es un patrón DAO) o agregar (si es un Repositorio porque este patrón usa Colección como una metáfora). Finalmente, no estoy hablando de entidades (Datos) y modelo (Dominio), estoy destacando la inutilidad de UserModel y su Mapper (capa de presentación). Puede llamar a la clase Usuario del dominio dentro de la presentación y esto no viola la regla de dependencia.
Rami Jemli
Estoy de acuerdo con Rami, el mapeador es innecesario porque puedes hacer el mapeo directamente en la implementación del interactor.
Christopher Perry
5

No, no necesita crear clases de modelo en cada capa.

Entidad ( DATA_LAYER): es una representación total o parcial del objeto Base de datos.DATA_LAYER

Mapper ( DOMAIN_LAYER): en realidad es una clase que convierte Entity en ModelClass, que se utilizará enDOMAIN_LAYER

Echa un vistazo: https://github.com/lifedemons/photoviewer

deathember
fuente
1
Por supuesto, no estoy en contra de las entidades en la capa de datos, pero, en su ejemplo, la clase PhotoModel en la capa de presentación tiene los mismos atributos que la clase Photo en la capa de dominio. Técnicamente es la misma clase. ¿Es eso necesario?
Creo que algo está apagado en su ejemplo como capa de dominio no debe depender de otras capas como en el ejemplo de sus creadores de mapas dependen de las entidades en la capa de datos que la OMI, debería ser al revés
navid_gh