0% encontró este documento útil (0 votos)
496 vistas20 páginas

Implementando DDD y Arquitectura Hexagonal en PHP Con Laravel

Este documento describe cómo implementar el Domain-Driven Design (DDD) y la arquitectura hexagonal en proyectos Laravel. Explica que aunque Laravel es bueno para proyectos pequeños, una arquitectura limpia como la hexagonal permite mayor escalabilidad y mantenibilidad a largo plazo. Describe conceptos clave de DDD como bounded context y value objects, y cómo la arquitectura hexagonal organiza el código en capas de infraestructura, aplicación y dominio para reducir la dependencia de detalles tecnológicos.

Cargado por

RonaldMartinez
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
496 vistas20 páginas

Implementando DDD y Arquitectura Hexagonal en PHP Con Laravel

Este documento describe cómo implementar el Domain-Driven Design (DDD) y la arquitectura hexagonal en proyectos Laravel. Explica que aunque Laravel es bueno para proyectos pequeños, una arquitectura limpia como la hexagonal permite mayor escalabilidad y mantenibilidad a largo plazo. Describe conceptos clave de DDD como bounded context y value objects, y cómo la arquitectura hexagonal organiza el código en capas de infraestructura, aplicación y dominio para reducir la dependencia de detalles tecnológicos.

Cargado por

RonaldMartinez
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 20

Implementando DDD y Arquitectura Hexagonal

en PHP con Laravel


Fabio Schettino Aug 21, 2020 · 14 min read

Desde los comienzos de mi experiencia profesional como desarrollador (y también como


diseñador UX, pero esta es otra historia), he intentado buscar y encontrar soluciones que en
el medio y largo plazo me permitieran trabajar de la manera más agradable y rápida
posible, tanto a mí como a mis compañeros de equipo, garantizando a la vez la entrega de
productos de calidad.

Esto, desde luego, incluye la posibilidad de reutilizar el código en diferentes proyectos.

Por estos motivos, por mi pasión por las arquitecturas y los patrones de diseño de
software, por el código limpio y legible, y porque en el último periodo he tenido la
oportunidad de trabajar en proyectos desarrollados en PHP que utilizaban como
framework Laravel, he decidido escribir este artículo.

Existe mucha información que trata de los aspectos tácticos del Domain Driven Design
(DDD) y también mucha relativa a la implementación de clean architectures o
arquitecturas limpias en PHP, pero muy poca que lo reúna todo y lo aplique a Laravel que
como framework PHP está teniendo cada vez más auge debido a la facilidad con la que
puedes desarrollar aplicaciones en tiempos muy rápidos y con un código elegante.

Ahora bien, la primera pregunta que podría surgir es:

¿Pero porque si un proyecto tiene una perspectiva a medio/largo plazo debería utilizar
Laravel y no un framework como Symfony por ejemplo?

Una de las posibles respuestas podría ser porque no todos los proyectos tienen esa
perspectiva al principio, a menudo empiezan siendo pequeños y por eso necesitan ser
desarrollados rápidamente y Laravel es perfecto para esos casos.
Pero con el tiempo, pueden llegar a ser cada vez más grandes y convertirse en un negocio
que funciona y necesita de mantenimiento y escalabilidad a medio/largo plazo y claro, no
todo el mundo se puede permitir refactorizar todo de nuevo con los costes de tiempo,
know-how y mano de obra que eso implica.

Es el caso de muchas start-ups que nacen de una idea, un producto mínimo viable (MVP)
y terminan convirtiéndose en un negocio rentable que hay que cuidar y hacer crecer.

Eso si, en cuanto las cosas empiecen a crecer mucho en algún momento habrá que
plantearse un cambio en la infraestructura y esto por supuesto, entre otras cosas, podría
incluir pasar a un framework más robusto.

La segunda pregunta que podría surgir es:

¡Ok! ¿Pero porque debería implementar una arquitectura limpia como la arquitectura
hexagonal, que sin duda añade complejidad accidental, cuando uno de los puntos de fuerza
de Laravel es precisamente la sencillez y rapidez con la que nos permite desarrollar?

Con muy pocas lineas de código y abastecido de la suficiente cantidad de comida y bebida la
aplicación ya está hecha.

Y la respuesta es porque cuando llegue el momento de cambiar nuestra infraestructura, y


por alguna u otra razón este momento antes o después llegará si el proyecto empieza a
crecer, te lo garantizo, el haber implementado una arquitectura limpia será lo que te
permitirá ahorrar muchísimo tiempo, dinero y quebraderos de cabeza.

Además recuerda, que lo que no estás haciendo tú como desarrollador, lo está haciendo
Laravel detrás del telón, y esto está bién si desarrollas aplicaciones pequeñas, si pero
necesitas evolucionar tu producto, o tu infraestructura cambia, si necesitas más control
sobre tu código porque el proyecto está empezando a tener éxito y pasas de tener 4
peticiones (Tu, tu novia y tus padres) a tener 100.000 o más, puede que tu base de datos o
el mismo framework, o hasta la misma versión de framework ya no sean suficientes y
tengas que orientarte hacia otras soluciones que te proporcionen más robustez, nuevas
funcionalidades o más niveles de seguridad y fiabilidad.

Es en ese momento que entran en juego las arquitecturas limpias en todo su esplendor.
Estas, tienen el objetivo entre otros, de desacoplarnos de la infraestructura o, como en el
caso de Laravel cuyo ORM es Eloquent que utiliza el patrón active record, de reducir al
mínimo los llamados leaks de infraestructura a los cuales Laravel, por su naturaleza,
está más expuesto como veremos más adelante.

…aquí ya empezamos a entrar en lo técnico, pero vamos por pasos.

¿Cuales son las principales características de una clean architecture?

Desde el punto de vista técnico:

Muy bajo o nulo nivel de acoplamiento y dependencia de los detalles de


implementación (framework, database, etc.).

Facilidad de mantenimiento y flexibilidad al cambio (introducir nuevas


funcionalidades resulta ser una tarea mucho más liviana y rápida).

El software se vuelve intrínsecamente testeable (¡Si! Hay que hacerlo, si no quieres


empezar a rezar cada vez que haces una subida a producción tienes que escribir los tests).

Estabilidad, robustez y escalabilidad.

Desde el punto de vista del negocio eso se traduce en:

Un producto de mayor calidad al que se les pueden añadir mas fácilmente todas las
funcionalidades que nuestros clientes demandan (…bueno todas no, antes hay que
filtrar las que si y las que no).

Tiempos de desarrollo reducidos debido a la menor incidencia de deuda técnica (…y


como bien sabemos, el tiempo es dinero).

Y por último, pero no menos importante, un equipo de profesionales felices y


orgullosos de trabajar en un entorno en el que todo fluye de manera más ágil y
organizada.

Y ahora, después de esta extensa pero necesaria introducción, vamos a entrar más en
detalles.
Antes de pasar al código es importante definir algunos conceptos.

DDD — Domain Driven Design


En DDD uno de los primeros conceptos con los que nos encontramos es el de bounded
context.

La definición de bounded context y su identificación en los procesos de negocio puede


llegar a ser amplia y compleja, pero en este caso, para no extendernos mucho y simplificar
la tarea, utilizaré la definición que Eric Evans, autor del libro Domain-Driven Design:
Tackling Complexity in the Heart of Software y uno de los personajes más influyentes del
panorama DDD, da de este concepto.

“Un bounded context es una parte bien definida de un software en la cual términos,
definiciones y reglas concretas pueden aplicarse de manera consistente”.

…ok… suena bien, pero puede que todavía no nos haya quedado muy claro, y entonces,
ya que a menudo se dice que un ejemplo vale más de mil palabras intentaré explicarlo
mejor con un ejemplo.

Imaginemos que nuestro proyecto prevé el desarrollo de una aplicación para los clientes
de una cadena de talleres de auto, y de un sistema de gestión para los empleados del
servicio de atención al cliente.

Es muy probable que, aunque en parte compartan según que terminología o concepto,
estos dos elementos tengan reglas de negocio que se aplican de manera diferente y por eso
la aplicación para clientes y el software de gestión del centro de atención pueden ser
identificados como dos bounded context diferentes, cada uno con su lógica.

Otro concepto del DDD alrededor del cual todo se mueve es el de dominio y este puede
resumirse como el campo de actividad en el cual se desarrolla nuestra lógica de negocio,
en él podemos encontrar los modelos de dominio que son la representación de las
entidades involucradas en los procesos.

Tomando como ejemplo el de la cadena de talleres de auto, algunas de las entidades


podrían ser: Cliente, Taller, Coche etc.
Y por terminar con esta breve disertación tenemos los value objects que pueden ser
descritos como los componentes fundamentales que nos permiten modelar las entidades.
Vendrían a ser las caracteristicas que las definen dicho muy superficialmente.

En el caso de una entidad Cliente por ejemplo, sus value objects podrían ser el id, su
nombre, el email etc.

Ahora ya debería haber quedado más claro, si pero así no fuese, en internet puedes
encontrar toneladas de material al respecto que sin duda podrán ayudarte a entenderlo
mejor.

Arquitectura Hexagonal
La arquitectura hexagonal es un tipo de arquitectura limpia que se estructura en tres capas
o niveles de profundidad.

La primera y más externa es la capa de infraestructura, la segunda es la de aplicación y


la tercera, la más profunda, la que se encuentra en el corazón de nuestra arquitectura por
así decirlo, es la capa de dominio.

Podéis imaginárosla como círculos concéntricos uno dentro de otro cuyos elementos
tienen que respectar una regla muy sencilla: solo se pueden comunicar con otros
elementos que pertenecen a la misma capa o la siguiente más interna pero nunca con una
externa, es decir, desde fuera hacia dentro y nunca al revés.

Más en concreto, los elementos de la capa de infraestructura solo se pueden comunicar


con otros elementos en su mismo nivel y con la capa de aplicación, a su vez, los elementos
de la capa de aplicación solo se pueden comunicar con otros elementos en su mismo nivel
y con la capa de dominio y los elementos de la capa de dominio solo pueden comunicarse
entre si.

La comunicación entre capas ocurre por medio de interfaces o puertos (la arquitectura
hexagonal también es llamada arquitectura puerto-adaptador, en inglés port-adapter).

Para una explicación más exhaustiva de este tipo de arquitectura os recomiendo leer un
articulo de Chris Fidao que lo explica muy bien. Hexagonal Architecture.
Representación de la Arquitectura Hexagonal

Más en detalle, las capas están organizadas de la siguiente manera:

Infraestructura
Aquí podemos encontrar elementos como los controladores y las implementaciones de
los repositorios, todo aquello que representa un punto de contacto con nuestra
infraestructura y que potencialmente podría estar sujeto a los famosos leaks que
mencionaba al principio. Son los puntos de entrada y salida de nuestro flujo.

Aplicación
En esta capa solemos tener los casos de uso, también llamados acciones o servicios de
aplicación.

Los elementos de esta capa reciben en entrada el input que proviene de los elementos de
infraestructura como los controladores y se comunican con el dominio.

Dominio
Aquí es donde se encuentra la lógica de negocio, en esta capa vamos a encontrar
elementos como los modelos de dominio, sus value objects, servicios, eventos,
excepciones de dominio, las interfaces implementadas por los repositorios que se
encuentran en la capa de infraestructura etc.

Show me the code!


Para este ejemplo, he creado un proyecto de Laravel en su versión 7 cuyo código puedes
encontrar en este repositorio git

Puede que en el momento en el que estás leyendo este articulo el código del repositorio haya
cambiado debido a la evolución del proyecto pero los conceptos siguen siendo los mismos,
osea, los de diseño guiado por dominio y de arquitectura hexagonal.

En la siguiente imagen se puede observar la estructura de un proyecto de Laravel recién


creado, el único añadido es la carpeta src (a veces también llamada core), ese es el núcleo
de nuestra arquitectura.
Estructura de carpetas Laravel 7

Lo primero que encontramos al desplegar la carpeta src es la carpeta BoundedContext.

El nombre es puramente demostrativo, en un caso real esta carpeta sería nombrada


adecuadamente con el nombre del contexto que estamos modelando, utilizando el
ejemplo anterior de la cadena de talleres, esta carpeta podría renombrarse en
CustomerCareMS.
Estructura arquitectura y capas

En el bounded context podemos encontrar nuestros dominios o entidades, en este caso


solo hay una, la entidad User, he utilizado esta entidad para este ejemplo porque Laravel
ya viene con un modelo User (que no tiene nada que ver con nuestra entidad) y para
mantener la instalación intacta he preferido no tocar absolutamente nada (bueno algo he
añadido pero son detalles muy pequeños como veremos dentro de poco).

En el módulo User podemos ver las tres capas de la arquitectura hexagonal,


Infrastructure, Application y Domain, y en cada una de ellas los elementos
mencionados el la descripción de la arquitectura que he dado antes.

Los controladores y el repositorio de Eloquent en la capa de infraestructura, es


decir, IN y OUT del ciclo de vida de la petición dentro de nuestra arquitectura.

Los casos de uso en la capa de aplicación.

El modelo de dominio User, sus value objects y la interfaz con el repositorio en la


capa de dominio.

Ciclo de vida de una petición


Antes de adentrarnos en como funciona el ciclo de vida de una petición te mostraré lo que
he añadido a la instalación de base de Laravel.

Se trata de cuatro controladores de Laravel en la carpeta Controllers, uno para cada


acción, sus respectivas rutas en el archivo api.php, un Json Resource para formatear la
respuesta, y unos tests de aceptación.
Elementos añadidos a instalación base de Laravel

Lo que hacen los controladores es simplemente tramitar la request al controlador en la


capa de infraestructura de nuestra arquitectura, recibir la respuesta dentro de un
resource y devolverla al solicitante, nada más.

Podríamos enriquecerlos con validaciones utilizando el facade Validator que Laravel nos
proporciona pero no es el objeto de este articulo.

Para este ejemplo solo nos centraremos en la implementación de la arquitectura DDD-


Hexagonal.

En el constructor del controlador de Laravel inyectamos el controlador de la capa de


infraestructura, lo inicializamos y en el método __invoke lo llamamos pasandolo como
argumento del UserResource.

Al final del ciclo devolveremos el resultado de la request y su status code utilizando el


objeto response.
Laravel CreateUserController

Al controlador en la capa de infraestructura se le inyecta el repositorio de Eloquent por


constructor.

Al principio del archivo, en las declaraciones “use” podrás observar que el controlador solo se
está comunicando con la capa de aplicación e infraestructura.
CreateUserController en la carpeta Infrastructure

En el método __invoke del controlador extraemos los datos de la request que hemos
recibido, instanciamos el caso de uso CreateUserUseCase, le pasamos el repositorio por
constructor, luego llamamos el método __invoke del caso de uso y le pasamos los datos.

En este controlador también instanciamos otro caso de uso, el


GetUserByCriteriaUseCase por medio del cual recuperamos el usuario creado y lo
devolvemos al controlador de Laravel.

CreateUserController en la carpeta Infrastructure


Vamos a ver ahora lo que ocurre una vez hayamos instanciado el caso de uso
CreateUserUseCase y les hayamos pasado los datos.

Observa las declaraciones “use”, el caso de uso solo se está comunicando con la capa de
dominio.

En su constructor se le inyecta la interfaz UserRepositoryContract.

CreateUserUseCase en la carpeta Application

El método __invoke recibe los datos tipados de la request que han sido extraídos en el
controlador e instancia los value objects del modelo de dominio (aquí el tipo de dato que
se le pasa por constructor a cada value object es muy importante y tiene que coincidir con su
declaración).

Sucesivamente crea un nuevo usuario utilizando el named constructor create del modelo
de dominio User y lo persiste utilizando el método save del repositorio
EloquentUserRepository, el que hemos inyectado al instanciar el caso de uso y que
implementa la interfaz UserRepositoryContract.
CreateUserUseCase en la carpeta Application

Antes de pasar al método save del EloquentUserRepository vamos a mirar como está
estructurado un value object y el modelo de dominio User al que pertenece.

Cuando instanciamos un value object desde el caso de uso le pasamos por constructor el
dato correspondiente (un email en este caso) del tipo correspondiente, en el caso de este
value object en concreto, espera recibir un dato de tipo string y lo primero que hace una
vez se haya instanciado el objeto lo valida por medio del método privado validate,
sucesivamente lo inicializa para que podamos acceder a el desde el exterior a través del
método value.
UserEmail value object en la capa de dominio

Al modelo de dominio User se le pasan por constructor todas las propriedades que
lo definen, esto nos obliga a que cada vez que vamos a crear un nuevo usuario el modelo
disponga de todos los datos necesarios para poder guardarlo.

Se suele utilizar este enfoque para garantizar integridad de datos en nuestro sistema de
persistencia y evitar encontrarnos con entidades en nuestra base de datos que tengan datos
parciales y que sean por ende inconsistentes.
Modelo de dominio User en la capa de dominio

El modelo también dispone de unos métodos (getters) que nos permiten acceder a sus
propriedades, de un named constructor (el método create) que a partir de los datos que
recibe instancia el modelo, y podría contener otros métodos que definen su
comportamiento, esto porque la idea a la base de este tipo de enfoque es tener modelos
ricos en comportamiento y poder aplicar el principio del “Tell don’t Ask” planteado por
Martin Fowler.

Modelo de dominio User en la capa de dominio


El método create es el que se utiliza en el caso de uso CreateUserUseCase para crear un
nuevo usuario a partir del modelo User y que después se pasa al método save del
EloquentUserRepository que lo guarda en base de datos.

Ahora si podemos pasar a ver lo que hace el método save una vez reciba el nuevo usuario
que ha sido creado.

Este es el punto en el que se manifiesta el mayor leak de infraestructura de la


arquitectura al ser implementada en Laravel debido a la integración de Eloquent y sus
modelos con el framework.

Eloquent está basado en el patrón active record, y esto representa la mayor diferencia
con respecto a un framework como Symfony, cuyo ORM es Doctrine que por lo contrario
utiliza el patrón data mapper.

Para reducir al mínimo el problema y evitar contaminar nuestro modelo de dominio


User, lo que hacemos aquí es utilizar el modelo User de Laravel (al cual le asignamos el
alias EloquentUserModel) e instanciarlo en el constructor del repositorio.

Esto nos permite tener acceso a todas las propriedades y métodos del modelo User del
Laravel y aprovechar los métodos que Eloquent nos proporciona.

En definitiva lo que hacemos es crear una capa de abstracción que nos permite mantener
nuestra arquitectura a salvo de posibles contaminaciones y al mismo tiempo aprovechar
las funcionalidades que el framework y su ORM nos brindan.
Repositorio EloquentUserRepository en la capa de infraestructura

El método save, lo que hace es recibir el modelo de dominio User como parámetro y
mapear sus atributos en un array que se le pasa al método create del EloquentUserModel
para que lo persista.

Repositorio EloquentUserRepository en la capa de infraestructura

Una vez haya sido guardado el nuevo usuario, el controlador de nuestra arquitectura lo
recupera utilizando el caso de uso GetUserByCriteriaUseCase y lo devuelve al
controlador de Laravel que lo envía al solicitante de la petición.

Conclusiones
Aunque DDD y Arquitectura Hexagonal parezcan temas complejos al principio, y sin duda
lo son a nivel teórico, a nivel practico su implementación no se traduce en absoluto en
miles de líneas de código, todo lo contrario.
De hecho nuestros métodos son mucho más atómicos, las responsabilidades están bien
definidas, el software es más robusto, fiable, escalable, y detalle a tomar en cuenta,
reutilizable.

Otro punto a favor es que al margen de intervenciones en los detalles de infraestructura,


que precisamente por utilizar una arquitectura de este tipo se vuelven algo relativamente
trivial gracias al hecho de que nuestra lógica está encapsulada en la arquitectura, con solo
copiar la carpeta src y llevártela a otro lado debería bastar para que todo funcione.

Agradecimientos
Espero que el contenido de este articulo pueda ayudar a quien como yo sigue buscando
soluciones intentando mejorar día tras día y noche tras noche.

Todo está en proceso y las cosas siempre se pueden mejorar pero por algo se empieza y
cualquier feedback será bienvenido.

Quiero agradecer a Rafael Gomez y Javier Ferrer de CodelyTV por compartir a través de
su canal en Youtube sus conocimientos y experiencia con toda la comunidad de
desarrolladores de una manera muy clara y divertida.

¡Hay cosas finísimas en el canal!

Gracias por haber llegado hasta el final y hasta pronto.

Hexagonal Architecture Domain Driven Design Laravel

About Write Help Legal

Get the Medium app

También podría gustarte