Essence, or not Essence, that is the question


Patrón de Desarrollo Essence

El documento "Essence Pattern" de Andy Carlson, con fecha de 4 de julio de 1998, describe un patrón de diseño que aborda un problema específico en la creación de objetos. No confundir con el metamodelo de procesos del mismo nombre definido como estándar de la OMG.

Este patrón se ha establecido como pilar base para la creación de modelos de dominio en el proyecto actual en el que me encuentro colaborando. No sin polémicas y debates sobre su uso, zanjados por dirección de proyecto. De esta manera no he podido resistir la tentación de redactar estas líneas.

Problema que busca resolver el patrón Essence

Muchas clases, especialmente las persistentes, tienen un subconjunto de propiedades (atributos y/o relaciones) que deben ser válidas antes de que una instancia del objeto pueda considerarse válida. El desafío surge en entornos distribuidos o basados en componentes, donde el cliente que crea las instancias puede estar fuera del control de diseño. Se desea forzar a los clientes a especificar estas propiedades al crear nuevas instancias, y a veces, asegurar que no puedan ser alteradas después de la creación.

Las soluciones tradicionales, como constructores con largas listas de parámetros o permitir la creación de un objeto inválido seguido de un paso de validación separado, presentan inconvenientes. Por ejemplo, un constructor con muchos parámetros puede ser "engorroso" y no manejar bien diferentes "permutaciones" de parámetros válidos. Una validación posterior al momento de creación no siempre es posible o deseable, especialmente si se necesitan propiedades inmutables.

La Solución propuesta por el patrón Essence

Este patrón propone utilizar un objeto separado, llamado "Essence object", para recibir las propiedades obligatorias del objeto que se está creando (llamado "CreationTarget").
  1. Creación del Objeto Essence: La creación del objeto Essence es irrestricta.
  2. Especificación de Propiedades: Se asigna un método separado en la clase Essence para cada propiedad obligatoria. Estos métodos almacenan el valor del parámetro en el objeto Essence. Para propiedades que no deben cambiarse después de la creación, los métodos setter solo se proporcionan en la clase Essence.
  3. Creación del Objeto Destino: Se fuerza a los clientes a obtener nuevas instancias de la clase CreationTarget a través de un método de la clase Essence (típicamente createTarget() o fromEssence()).
  4. Validación y Construcción: Este método en el objeto Essence valida las propiedades suministradas. Si son válidas, el Essence crea la nueva instancia del CreationTarget, pasándose a sí mismo al constructor del CreationTarget para que este recupere los valores de los parámetros e inicialice sus propias propiedades. Si la validación falla, se informa al cliente (por ejemplo, devolviendo un valor nulo o lanzando una excepción).
Eso sería el resumen de la propuesta documentada por Andy Carlson aquel 4 de julio.

Beneficios:

  • El objeto se crea sólo si los valores de las propiedades son válidos.
  • No es necesario proporcionar métodos setter para propiedades inmutables en el objeto final.
  • Permite flexibilidad en la especificación de diferentes permutaciones de parámetros sin sobrecargar el constructor
  • Un solo objeto Essence puede crear múltiples instancias del CreationTarget si los parámetros difieren ligeramente.

Inconvenientes:

  • Introduce una clase adicional al diseño, lo que puede causar confusión a los desarrolladores. 
  • La responsabilidad de la validación de atributos se divide entre dos lugares.
  • Requiere una dependencia circular entre las clases Essence y CreationTarget.

Desafío de Implementación:

El desafío principal es evitar que los clientes creen instancias de CreationTarget directamente. Como todo "patrón creacional" su función es esa. No se debería caer en la tentación de usar el objeto Essence para otras responsabilidades.

Este objetivo se puede lograr con constructores privados y clases "friend" (C++), o constructores protegidos y paquetes (Java), con Extensiones (Dart), con factorías, builder y algunos otros mecanismos.

Uso del Patrón Essence en Aplicaciones Basadas en DDD + Arquitectura Hexagonal Actuales

Basándonos en la descripción original del patrón, el "Essence Pattern" es un patrón de creación de objetos que resuelve problemas relacionados con la validez de las propiedades en el momento de la instanciación.

Dicho esto, su uso en aplicaciones actuales basadas en DDD (Domain-Driven Design) y Arquitectura Hexagonal es posible pero no es una práctica dominante ni particularmente común.

En el contexto DDD:

DDD enfatiza la creación de modelos de dominio robustos donde los objetos (Entidades, Agregados, Value Objects) siempre estén en un estado válido. Esto a menudo se logra mediante constructores bien definidos que aceptan todos los parámetros necesarios para la creación de un objeto válido, o mediante Factorías que encapsulan la lógica de creación compleja.

El patrón Essence podría ser una forma de implementar una Factoría (incluso un Builder) para un Aggregate Root o una Entidad, asegurando que todas las propiedades obligatorias estén presentes antes de que el objeto de dominio sea instanciado. Podría ser útil si la creación de un objeto de dominio implica muchos pasos o variaciones en la forma en que se proporcionan los datos iniciales, y se necesita una validación progresiva.

Sin embargo, muchas implementaciones DDD prefieren mantener la creación de objetos de dominio lo más directa posible a través de constructores explícitos o métodos de fábrica simples dentro del propio dominio o un servicio de dominio, ya que un objeto Essence externo introduce una clase adicional y una lógica de validación potencialmente duplicada; así mismo si la clase Essence es interna se pierde la esencia del modelo de dominio, dotándolo de funcionalidades que no le corresponden y código duplicado.

En el contexto Arquitectura Hexagonal:

La Arquitectura Hexagonal se centra en la separación de "preocupaciones", manteniendo el Dominio (Core) agnóstico a las tecnologías externas. La lógica de validación de negocio inherente a la creación de un objeto de dominio reside naturalmente dentro del Dominio.

Si el patrón Essence se usara, el CreationTarget sería un objeto de dominio, y el Essence actuaría como un "adaptador de entrada" o una "capa de aplicación" que orquesta la recolección de los datos de entrada y su validación inicial antes de intentar construir el objeto de dominio. Esto encajaría con la idea de que los "puertos" de entrada de la arquitectura hexagonal manejen los detalles de cómo se reciben los datos y luego llamen al dominio. Pero no debería ir dentro del objeto de dominio, pues se acoplaría directamente.

No obstante, la Arquitectura Hexagonal no prescribe un patrón específico para la creación de objetos; simplemente exige que la lógica de creación esté aislada y que el dominio no dependa de los detalles de la infraestructura.

En resumen, aunque el patrón Essence es una solución válida para el problema que describe, no es tan ampliamente adoptado o discutido en las comunidades de DDD o Arquitectura Hexagonal como otras técnicas. Los patrones como "Builder" (que tiene similitudes funcionales en la construcción de objetos complejos) o simplemente constructores con validación integrada o Factorías de dominio son más comunes y recomendables.

¿Es recomendable su uso en Aplicaciones Flutter?

Para aplicaciones Flutter, la situación es similar:
  • Directamente en la UI/Gestión de Estado: El patrón Essence no es directamente aplicable a la construcción de la UI de Flutter (widgets, diseño) o a los patrones de gestión de estado (Provider, BLoC, Riverpod, GetX, etc.). Estos se centran en cómo se construye y actualiza la interfaz de usuario y cómo se maneja el estado de la aplicación.
  • Para la creación de modelos de datos/objetos de dominio en Flutter (si aplica DDD): Si estás aplicando DDD en tu aplicación Flutter (lo cual es muy recomendable para aplicaciones complejas con lógica de negocio rica), podrías considerar usar el patrón Essence para la creación de tus objetos de dominio inmutables o con propiedades obligatorias.
  • Para casos simples: Probablemente sea una sobre-ingeniería. Un constructor simple con validaciones o una Factoría estática en la propia clase es a menudo suficiente y más legible.
  • Para la creación de objetos de dominio complejos: Si la creación de un objeto de dominio implica:
  • Múltiples propiedades obligatorias.
  • Varias formas válidas de especificar esas propiedades (ej. por ID o por nombre).
  • Necesidad de una validación progresiva o retroalimentación al usuario antes de la creación final.
  • La necesidad de que algunas propiedades sean inmutables después de la creación.
  • Clientes (por ejemplo, el código de la capa de aplicación o presentación) que no puedes controlar completamente (menos común en una app de un solo monolito Flutter, pero posible si tienes módulos de equipo separados). 
Entonces, el patrón Essence podría ser una solución viable y robusta.


En general, en Flutter, la tendencia es hacia la simplicidad y la inmutabilidad de los modelos de datos (a menudo generados con paquetes como freezed o equatable).

El patrón Essence es una herramienta específica para un problema particular de creación de objetos. Se debería evaluar si el problema que describe el patrón (garantizar la validez en la creación desde clientes no controlados y manejar propiedades obligatorias/inmutables complejas) es realmente relevante para cada caso de uso en Flutter antes de adoptarlo indiscriminadamente a todo. Para la mayoría de las aplicaciones Flutter, es probable que no sea necesario.
 
¿Qué opinas tu?
 
Hace meses lancé una pequeña encuesta sobre el tema en linkedIn y de voz con colegas de profesión. La mayoría de las respuesta fue "¿qué es eso de Essence?

 

 

 

Comentarios