Índice
-
DDD en la Práctica: De Lenguaje Ubícuo en DSL a Proyecto Completo de Spring Boot con Kotlin
-
ZenWave Domain Model Language: Un Lenguaje de Modelado que funciona como Lenguaje Ubícuo
- 3.1. Origen e inspiración
- 3.2. De Event Storming a ZDL
- 3.3. Beneficios clave
-
Caso Práctico: Customers Service – Del Modelo ZDL al Proyecto Spring-Boot
- 6.1. Creando un ZDL para nuestro Customers Service
- 6.2. Modelando Comandos y Eventos de Dominio
- 6.3. Conectando el Servicio con el Exterior vía APIs
- 6.4. Convertir ZDL a OpenAPI
- 6.5. Convertir ZDL a AsyncAPI
- 6.6. Modelando el Agregado de Clientes
- 6.7. Sobre Modelos Relacionales y/o Documentales
- 6.8. Fichero de Scripts ZenWave SDK
-
Customización del Código Generado
- 7.1. Tipos de Customización Disponibles
- 7.2. Customización Básica
- 7.2.1. Layouts de Arquitectura
- 7.2.2. Configuración de Paquetes
- 7.2.3. Monolitos Modulares
- 7.3. Customización Intermedia
- 7.3.1. Plantillas Personalizadas
- 7.4. Customización Avanzada
-
Proyecto Completo de Spring-Boot con Kotlin
- 8.1. Stack Tecnologico
- 8.2. Funcionalidad y Modelo de Dominio
- 8.2.1. Entidades y Value Objects
- 8.2.2. Operaciones Disponibles
- 8.3. Modelos de ZenWave SDK
- 8.4. Ejecución del Proyecto
1. Introducción
Domain-Driven Design (DDD) es un enfoque de diseño de software que sigue plenamente vigente tras más de dos décadas. Su objetivo principal es alinear el modelo de software con el modelo mental de los expertos de negocio, creando un lenguaje compartido que facilite la comunicación entre perfiles técnicos y no técnicos.
En este artículo veremos cómo aplicar DDD en la práctica utilizando el Lenguaje Ubicuo como punto de partida y llevándolo hasta un proyecto completo en Spring Boot con Kotlin, apoyándonos en el ecosistema ZenWave 360 y su Domain Model Language (ZDL).
A lo largo del recorrido mostraremos:
- Cómo descubrir y capturar el Lenguaje Ubicuo del dominio de negocio.
- Cómo documentarlo y estructurarlo con ZDL, un lenguaje de modelado pensado para DDD y arquitecturas API-First y Event-Driven.
- Cómo generar automáticamente artefactos de software (código, APIs, tests, documentación) a partir de ese modelo.
- Un caso práctico: la construcción de un servicio maestro de clientes (Customers Service) y su implementación en un proyecto Spring Boot con Kotlin.
Este enfoque busca cerrar la brecha entre la teoría de DDD y la práctica diaria, facilitando la propagación del conocimiento del dominio a lo largo de todo el ciclo de desarrollo y garantizando consistencia entre el lenguaje de negocio y el software entregado.
2. Importancia del Lenguaje Ubicuo en Domain Driven Design
Domain-Driven Design tiene ya 20 años y sigue estando más vigente que nunca. Sin embargo, debido a la gran amplitud de su propuesta, DDD no es una técnica sencilla.
No se trata de una receta cerrada, sino de un enfoque de diseño que abarca distintos niveles de abstracción:
- DDD Estratégico: identifica dominios y subdominios, los clasifica según su impacto en la estrategia de negocio (core, supporting o generic) y define bounded contexts y sus interacciones mediante context maps.
- DDD Táctico: desciende al modelo detallado, trabajando con entidades, agregados, value objects, eventos de dominio, servicios y repositorios.
Aunque ambos niveles interesan a perfiles diferentes, están profundamente interconectados.
Es imprescindible comprender el contexto estratégico para tomar buenas decisiones en el nivel táctico.
Por ejemplo:
- Para un dominio core, puede ser adecuado aplicar una arquitectura clean/hexagonal, modelando entidades ricas, eventos de dominio y un modelo de persistencia desacoplado, ya que aquí descubrimos reglas de negocio complejas con impacto estratégico.
- En cambio, para un subdominio genérico o de soporte, la simplicidad y facilidad de mantenimiento son prioritarias, y un modelo de dominio complejo puede ser incluso contraproducente.
La propuesta central de DDD es construir un Lenguaje Ubicuo que una a expertos de negocio y técnicos.
Un lenguaje común que se hable en reuniones, se plasme en diagramas, se use en el código, en las pruebas y en la documentación.

El problema es que, aunque se intente, el Lenguaje Ubicuo suele perderse durante el ciclo de desarrollo:
hay demasiados puntos de traducción (analistas, arquitectos, desarrolladores, testers…) donde el lenguaje compartido se diluye.
Por eso, además de descubrir y visibilizar el Lenguaje Ubicuo, necesitamos mecanismos automáticos que lo propaguen a lo largo de todo el proceso.
Aquí es donde aparece ZenWave Domain Model Language (ZDL):
un lenguaje compacto y expresivo que permite capturar conceptos de negocio y técnicos en un mismo formato.
ZDL sirve como Lenguaje Ubicuo y, además, puede transformarse en código ejecutable, definiciones de APIs, tests automatizados, documentación y glosarios de negocio.
De esta forma, el conocimiento del dominio fluye de manera consistente desde los expertos de negocio hasta los desarrolladores, manteniendo coherencia en todo el ciclo de vida del software.
3. ZenWave Domain Model Language: Un Lenguaje de Modelado que funciona como Lenguaje Ubicuo
ZenWave Domain Model Language (ZDL) es un lenguaje de modelado diseñado para describir el contenido de servicios backend apoyándose en los principios de Domain-Driven Design (DDD), API-First y Arquitecturas Event-Driven.
ZDL se caracteriza por ser:
- Compacto: su sintaxis permite expresar conceptos de negocio y técnicos de forma concisa.
- Legible: tanto expertos técnicos como de negocio pueden comprenderlo.
- Expresivo: captura comandos, eventos, entidades, agregados y políticas de negocio en un mismo lenguaje.
Gracias al ecosistema de Plugins de ZenWave SDK, un modelo definido en ZDL puede convertirse automáticamente en diferentes artefactos de software y documentación, propagando así el Lenguaje Ubicuo a través de todo el proceso de desarrollo.
3.1. Origen e inspiración
ZDL toma como base la sintaxis de JHipster Domain Language (JDL), extendiéndola más allá de la definición de entidades y relaciones típicas de aplicaciones CRUD.
Aporta capacidades adicionales como:
- Definición de servicios, comandos y eventos de dominio.
- Integración con especificaciones API-First: OpenAPI, AsyncAPI y Avro.
- Soporte directo para arquitecturas Event-Driven y microservicios.
3.2. De Event Storming a ZDL
ZDL está especialmente pensado para recoger los descubrimientos de una sesión de Event Storming a nivel de diseño.
En este contexto, un Bounded Context se convierte en el espacio ideal para:
- Documentar comandos de entrada.
- Describir los eventos de dominio que se producen.
- Modelar agregados, entidades y value objects.
- Especificar reglas y políticas de negocio.
De esta manera, ZDL actúa como un formato de Lenguaje Ubicuo, a la vez comprensible para humanos y procesable por máquinas.

3.3. Beneficios clave
- Representar comandos de entrada al bounded context (APIs REST, eventos asíncronos, disparadores temporales).
- Documentar y propagar eventos internos y externos generados por el dominio.
- Modelar agregados, entidades, value objects y relaciones.
- Enlazar estos elementos con especificaciones técnicas OpenAPI, AsyncAPI o Avro.
En el repositorio oficial de ZenWave encontrarás ejemplos de modelos ZDL listos para usar, tanto en contextos de microservicios como en monolitos modulares.
4. Modelado de Dominio de Negocio con ZenWave 360º
Un buen modelo es aquel que permite acomodar cambios en la dirección y perspectiva de los expertos de negocio.
El enfoque de Modelado de Dominio de Negocio con ZenWave 360º se basa en tres pilares fundamentales:
- Comprender el modelo mental de los expertos de negocio.
- Usar el Lenguaje de Modelado ZDL para capturar ese modelo mental en un Lenguaje Ubicuo.
- Transformar el modelo ZDL con ZenWave SDK en diferentes artefactos de software que aceleren el desarrollo.

4.1. Comprender el Modelo Mental de los expertos de negocio
DDD pone el foco en entender el dominio de negocio antes de escribir código.
Sin esta comprensión, ninguna cantidad de patrones técnicos (Código Limpio, Arquitectura Hexagonal, etc.) garantizará un software mantenible y de calidad.
Una técnica clave para ello es Event Storming, ya que:
- Tiene baja barrera de entrada para los expertos de negocio.
- Es ideal para arquitecturas orientadas a eventos.
- Facilita descubrir procesos, interacciones y reglas clave del dominio.

El lenguaje ZDL permite documentar el modelo mental de los expertos de negocio de forma estructurada, reteniendo su vocabulario.
Con él, los desarrolladores pueden narrar de vuelta la historia del dominio a los expertos en su propio lenguaje, validando que el modelo es correcto.
4.2. Convertir el Lenguaje Ubicuo en diferentes Artefactos de Software con ZenWave SDK
La mejor manera de validar un modelo conceptual es implementarlo.
ZenWave SDK facilita convertir los modelos ZDL en software funcional y pruebas automatizadas.
Esto permite:
- Validar rápidamente las ideas con retroalimentación temprana.
- Propagar el Lenguaje Ubicuo del ZDL a código, APIs, tests, documentación y diagramas.
- Reducir el riesgo de pérdida de conocimiento entre negocio y desarrollo.

El software generado se convierte en el medio más efectivo para evaluar la validez del modelo conceptual y refinarlo de manera iterativa, alineando negocio y tecnología.
5. ZenWave 360: Inicio Rápido para los más Impacientes
Esta sección está pensada para quienes quieren probar ZenWave 360 de extremo a extremo sin rodeos, generando un proyecto completo de Spring Boot a partir de un modelo ZDL en cuestión de minutos.
En las siguientes secciones entraremos en detalle sobre el modelado del dominio, pero aquí encontrarás una guía directa para empezar.
5.1. Pasos para la generacion rapida
-
Clona el proyecto base
Descarga el proyecto base.
También puedes iniciar desde Spring Initializr o desde un arquetipo corporativo propio, ya que ZenWave genera únicamente código de negocio y no interfiere con la estructura base.Nota: en este caso deberás configurar manualmente el OpenAPI Maven Generator y el ZenWave SDK Maven Plugin para AsyncAPI.
-
Instala las herramientas necesarias
Sigue las instrucciones de Getting Started para instalar:- ZenWave SDK CLI
- ZenWave Domain Model Editor for ZDL (plugin para IntelliJ)
-
Ejecuta los plugins
En IntelliJ, abre el archivozenwave-scripts.zwy ejecuta cada plugin individualmente con el botón verde de Play en el margen izquierdo. -
Plugins que se ejecutaran
ZDLToOpenAPIPlugin: genera una definicion OpenAPI a partir del modelo ZDL.ZDLToAsyncAPIPlugin: genera una definicion AsyncAPI v3 a partir del modelo ZDL.BackendApplicationDefaultPlugin: genera el core de la aplicacion Spring Boot (sin controladores ni adaptadores).OpenAPIControllersPlugin: genera los controladores REST.SpringWebTestClientPlugin: genera pruebas de integracion para los controladores.OpenAPIKaratePlugin: genera pruebas de aceptacion basadas en KarateDSL.

Ver fichero
Text
zenwave-scripts.zw
(click para expandir)
|
Text
|
|
|---|---|
|
zenwave-scripts.zw
|
|
|
Java
|
|
|---|---|
|
// This is a ZenWave Scripts File (.zw)
config { zdlFile "zenwave-model.zdl" plugins { ZDLToOpenAPIPlugin { idType integer idTypeFormat int64 targetFile "src/main/resources/public/apis/openapi.yml" } ZDLToAsyncAPIPlugin { asyncapiVersion v3 idType integer idTypeFormat int64 targetFile "src/main/resources/public/apis/asyncapi.yml" } BackendApplicationDefaultPlugin { useLombok true includeEmitEventsImplementation true } OpenAPIControllersPlugin { openapiFile "src/main/resources/public/apis/openapi.yml" } SpringWebTestClientPlugin { openapiFile "src/main/resources/public/apis/openapi.yml" } OpenAPIKaratePlugin { openapiFile "src/main/resources/public/apis/openapi.yml" } } } |
|
5.2. Revision manual necesaria
Aunque el proyecto generado es completamente funcional, algunas areas requieren tu atencion:
- Mappers de MapStruct → verificar conversiones complejas.
- Tests automatizados → completar la logica y datos de prueba.
- Implementacion de servicios → adaptar a la logica de negocio real (mas alla de CRUD).
5.3. Ventajas clave
- Genera solo codigo de negocio limpio, sin dependencias exoticas.
- Compatible con cualquier starter o arquetipo corporativo.
- Integra tecnologias estandar: Spring Boot, Spring Cloud, MapStruct, Lombok.
- Permite tener un proyecto completo en minutos:
- API-First con OpenAPI y AsyncAPI
- Arquitectura por capas o Clean/Hexagonal
- Tests unitarios, de integracion y de aceptacion listos para extender
6. Caso Práctico: Customers Service – Del Modelo ZDL al Proyecto Spring-Boot
Ahora que has experimentado las capacidades de ZenWave 360, vamos a modelar paso a paso un servicio sencillo para la gestión de un Maestro de Clientes.
Desde el punto de vista estratégico, se trata de un subdominio genérico o de soporte: no aporta valor competitivo directo a la empresa, pero resulta crucial para el funcionamiento de otros subdominios más críticos.
Este servicio maestro de clientes gestionará:
- La creación, actualización y eliminación de clientes.
- La notificación de cambios de estado a otros servicios interesados mediante colas de mensajería.
Aunque se trate de un subdominio genérico, es fundamental modelarlo utilizando el lenguaje de los expertos de negocio que interactuarán con él.
Por ejemplo, si los expertos se refieren a los clientes como “Customer” en lugar de “Client” o “Prospect”, debemos adoptar ese término exacto en el modelo para evitar ambigüedades y asegurar que el lenguaje ubicuo permee todo el sistema.
En este caso práctico, y basándonos en el modelo mental de los expertos, utilizaremos Customer como entidad principal con sus atributos clave.
Aunque es un subdominio de soporte, es fundamental modelarlo usando el lenguaje de los expertos de negocio.
Por ejemplo, si los expertos dicen Customer y no Client o Prospect, debemos reflejar exactamente ese termino en el modelo.
6.1. Creando un ZDL para nuestro Customers Service
Comenzaremos creando un archivo con extensión .zdl para nuestro modelo.
Los archivos ZDL suelen comenzar con:
- Una sección de documentación global, que describe el propósito del modelo.
- Una sección de configuración, donde se definen propiedades globales utilizadas por todos los plugins de ZenWave SDK.
Estas dos secciones son opcionales, pero si se incluyen, deben aparecer juntas al inicio del archivo ZDL.
|
Java
|
|
|---|---|
|
/**
* Servicio Maestro de Clientes. * Este modelo describe un servicio maestro de clientes que gestiona los datos de los clientes... */ config { title "Servicio Maestro de Clientes" basePackage "io.zenwave360.example" persistence jpa databaseType postgresql // ... } |
|
6.2. Modelando Comandos y Eventos de Dominio
Existen múltiples enfoques para comenzar a modelar un servicio: desde el modelo interno, los comandos de entrada, la API REST o los eventos publicados.
En este caso, comenzaremos modelando los comandos de entrada y los eventos de dominio que forman parte de este bounded context, es decir, su interfaz pública.
Agruparemos los comandos en un servicio y documentaremos a través de qué APIs dicho servicio expone sus comandos y eventos.
En ZDL, cada servicio gestiona siempre uno o varios agregados.
Como aún no hemos definido ninguno, crearemos una entidad vacía Customer y la utilizaremos como raíz del agregado.
Creamos lo siguiente:
- Un servicio
CustomerServicepara gestionar los clientes (el agregadoCustomer). -
Un comando de entrada
enrollCustomerque:- Recibe los datos de entrada de un
CustomerInput. - Devuelve el agregado recién creado
Customer. - Emite un evento de dominio
CustomerEnrolled.
- Recibe los datos de entrada de un
Nota: Los comandos también pueden recibir como parámetro de entrada la propia entidad agregada, en este caso
Customer.
|
Java
|
|
|---|---|
|
@aggregate
entity Customer { } /** ReadModel: contains the information needed to create a customer */ input CustomerInput { /** Customer name */ name String required maxlength(254) email String required maxlength(254) pattern(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/) } service CustomerService for (Customer) { /** Enroll Customer Command: creates a new customer */ enrollCustomer(CustomerInput) Customer withEvents CustomerEnrolled } event CustomerEnrolled { customerId String customer Customer } |
|
6.3. Conectando el Servicio con el Exterior vía APIs
Ahora vamos a documentar cómo nuestro CustomerService se comunicará con el mundo exterior a través de APIs REST y asíncronas.
Para ello utilizaremos ciertas anotaciones (decoradores) que permiten describir la forma en que el servicio expone sus comandos y eventos:
|
Java
|
|
|---|---|
|
@rest("/customers")
service CustomerService for (Customer) { @post enrollCustomer(CustomerInput) Customer withEvents CustomerEnrolled } @asyncapi({ channel: "CustomerEvents", topic: "customer.events" }) event CustomerEnrolled { //... } |
|
Estas anotaciones no solo documentan cómo el servicio se comunica con el exterior, sino que además, gracias a los plugins de ZenWave SDK (ZDLToOpenAPIPlugin y ZDLToAsyncAPIPlugin), permiten generar automáticamente una versión preliminar de dichas APIs.
El formato ZDL ha evolucionado como un lenguaje IDL capaz de definir, de manera casi completa, tanto APIs de tipo OpenAPI como AsyncAPI:
6.4. Convertir ZDL a OpenAPI
6.3.1. Convertir ZDL a OpenAPI
La sintaxis heredada de JHipster JDL permite definir el payload de las peticiones y respuestas de manera compacta:
- Nombre de los campos: utilizando el lenguaje ubicuo del dominio.
- Tipos de datos:
String,Integer,Long,Boolean, etc. - Validaciones:
required,maxlength,pattern,min,max, etc. - Relaciones y entidades anidadas: para estructuras de datos complejas.
Para definir los distintos endpoints utilizamos las siguientes anotaciones en los servicios y comandos:
Configuración del servicio:
@rest("/<path>"): define la ruta base del servicio.
Métodos HTTP:
@post,@get,@put,@delete,@patch: especifican el método HTTP para cada comando.
Configuración avanzada de endpoints:
@put("/{customerId}"): define rutas con parámetros en el path.-
@post({ path: "/search", params: {search: String}, status: 200 }):- Permite incluir query params con su tipo correspondiente.
- Soporta códigos de respuesta personalizados (por ejemplo, un
POSTpor defecto genera201, pero en el caso de una búsqueda lo más adecuado es200).
@paginated: indica que el endpoint devuelve resultados paginados.@filedownload("documentData.data"): define un endpoint de descarga binaria de archivos, dondedocumentData.dataes el campo que contiene los datos.@fileupload("myfile"): define un endpoint multipart con el campomyfilede tipo binario para la subida de archivos.
El siguiente ejemplo muestra un servicio completo con operaciones CRUD:
|
Java
|
|
|---|---|
|
@rest("/customers")
service CustomerService for (Customer) { @post createCustomer(Customer) Customer withEvents CustomerEvent @get("/{id}") getCustomer(id) Customer? @put("/{id}") updateCustomer(id, Customer) Customer? withEvents CustomerEvent @delete("/{id}") deleteCustomer(id) withEvents CustomerEvent @post({ path: "/search", status: 200 }) @paginated searchCustomers(CustomerSearchCriteria) Customer[] } |
|
El siguiente modelo ZDL, al utilizar el plugin ZDLToOpenAPIPlugin, generaría esta definición OpenAPI.
Para más detalles sobre cómo definir APIs REST en ZDL, consulta la documentación: Exponiendo una API REST.
6.5. Convertir ZDL a AsyncAPI
Definir APIs AsyncAPI con ZDL es igualmente sencillo y directo.
El proceso consiste en crear entidades de tipo event que siguen las mismas reglas de sintaxis que las entidades entity: nombres de campos, tipos de datos, validaciones, documentación, anotaciones y entidades anidadas.
Una vez definidos los eventos, los anotamos con @asyncapi para especificar:
channel: Nombre del canal donde se publican los eventos. Múltiples eventos pueden compartir el mismo canal, especialmente útil para eventos de tipo AVROtopic: Nombre del tópico específico para la publicación. En AsyncAPI v3 corresponde al campoaddressde la definición del canal
Ejemplo práctico:
|
Java
|
|
|---|---|
El mismo modelo ZDL, al utilizar el plugin ZDLToAsyncAPIPlugin, generaría una definición completa de AsyncAPI que incluye todos los esquemas de eventos y las configuraciones de canales correspondientes.
6.6. Modelando el Agregado de Clientes
Ahora definiremos la estructura interna del agregado Customer, aplicando los principios de Domain-Driven Design (DDD):
- Lenguaje ubicuo: utilizamos la estructura, los nombres de campos y las descripciones (javadoc) para capturar y documentar el vocabulario específico del dominio.
- Agregado: marcamos la entidad
Customercon@aggregatepara indicar que es la raíz del agregado.
Por simplicidad, en este ejemplo empleamos un agregado centrado en datos, aunque podríamos modelar un agregado más rico que incorpore comportamiento específico del dominio.
|
Java
|
|
|---|---|
|
/**
* Customer entity: represents a customer in our system with their basic information and contact details */ @aggregate entity Customer { /** Customer name */ name String required maxlength(254) /** Customer familiar name is how we refer to the customer in a friendly way */ familiarName String required maxlength(254) /** Customer email is used to contact the customer */ email String required maxlength(254) pattern(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/) /** Customer can have multiple addresses, but at least one is required and max 5 */ @json addresses Address[] minlength(1) maxlength(5) { street String required maxlength(254) city String required maxlength(254) } } /** PaymentMethod entity: represents a payment method associated with a customer */ entity PaymentMethod { type PaymentMethodType required cardNumber String required } enum PaymentMethodType { VISA(1), MASTERCARD(2) } relationship OneToMany { /** Customer has multiple payment methods, but at least one is required and max 3 */ Customer{paymentMethods required maxlength(3)} to PaymentMethod{customer required} } |
|
6.6.1 Sobre Modelos Relacionales y/o Documentales
ZDL soporta tanto el diseño de modelos relacionales (heredado de JHipster JDL) como de modelos documentales, utilizando entidades anidadas para lograr mayor expresividad y eficiencia.
Ejemplos de entidades anidadas:
Los dos siguientes esquemas son equivalentes:
|
Java
|
|
|---|---|
|
Java
|
|
|---|---|
|
Java
|
|
|---|---|
Este esquema muestra una colección de entidades anidadas que reutiliza la definición anterior, ya que en ZDL cada entidad debe tener un nombre único.
|
Java
|
|
|---|---|
6.8. Fichero de Scripts ZenWave SDK
El archivo .zw define la configuración y plugins a ejecutar.
Ejemplo:
|
Java
|
|
|---|---|
|
config {
zdlFile "zenwave-model.zdl" plugins { ZDLToOpenAPIPlugin { idType integer idTypeFormat int64 targetFile "src/main/resources/public/apis/openapi.yml" } ZDLToAsyncAPIPlugin { asyncapiVersion v3 targetFile "src/main/resources/public/apis/asyncapi.yml" } BackendApplicationDefaultPlugin { includeEmitEventsImplementation true } } } |
|
Ventajas:
- Configuración compartida en equipo.
- Ejecución con un clic desde IntelliJ.
- Reproducibilidad y trazabilidad.

7. Customizacion del Codigo Generado
ZenWave SDK surge de la necesidad personal de contar con una herramienta de análisis y diseño capaz de generar proyectos —inicialmente en Java y Spring Boot— con la suficiente flexibilidad para adaptarse a las necesidades de cada proyecto, equipo y cliente.
7.1. Tipos de Customizacion Disponibles
ZenWave SDK ofrece diferentes niveles de customización que se ajustan a distintos escenarios y grados de complejidad:
-
Customización Básica
- Layouts de arquitectura: elige entre diferentes estilos arquitectónicos (Layered, Clean/Hexagonal, Simple packaging, etc.).
- Configuración de paquetes: personaliza la estructura de los
packagesgenerados. - Monolitos modulares: configura múltiples módulos compartiendo clases base comunes.
-
Customización Intermedia
- Plantillas personalizadas: modifica plantillas específicas para pequeños ajustes (anotaciones, starters propios, etc.).
-
Customización Avanzada
- Extensión del classpath: añade dependencias y plugins personalizados vía
JBang. - Customización total: ejemplo de sustitución completa, reemplazando Java por Kotlin.
- Creación de plugins: desarrolla plugins completamente nuevos para casos específicos.
- Extensión del classpath: añade dependencias y plugins personalizados vía
7.2. Customizacion Basica
7.2.1. Layouts de Arquitectura
Podemos elegir el layout arquitectónico más adecuado al proyecto:
|
Java
|
|
|---|---|
|
config {
title "Mi Aplicacion Backend" basePackage "io.zenwave360.example" persistence jpa databaseType postgresql // Layouts disponibles: // DefaultProjectLayout, LayeredProjectLayout, SimpleDomainProjectLayout // CleanHexagonalProjectLayout, HexagonalProjectLayout, CleanArchitectureProjectLayout layout CleanHexagonalProjectLayout } |
|
Ejemplos de layouts en el repositorio de ZenWave SDK:
7.2.2. Configuracion de Paquetes
Puedes personalizar cualquier ruta de paquete en el layout elegido:
|
Java
|
|
|---|---|
|
config {
layout CleanHexagonalProjectLayout // Sobrescribir paquetes concretos layout.entitiesPackage "{{basePackage}}.core.model" layout.openApiApiPackage "{{basePackage}}.web" layout.openApiModelPackage "{{basePackage}}.web.dtos" } |
|
7.2.3. Monolitos Modulares
En un principio, ZenWave SDK fue diseñado para generar microservicios, aunque nada impide utilizarlo también para generar módulos dentro de un monolito.
La principal particularidad a la hora de construir monolitos modulares es la necesidad de compartir clases base entre los distintos módulos, con el fin de evitar la duplicación de código.
Por lo tanto, es necesario configurar el nombre del paquete base del módulo, que será el paquete raíz para dicho módulo (equivalente a basePackage en microservicios).
|
Java
|
|
|---|---|
|
// Configuración especifica del módulo
config { title "Modulo de Clientes" layout CleanHexagonalProjectLayout layout.moduleBasePackage "io.zenwave360.example.modules.customers" } |
|
Y además configurar los paquetes base comunes para compartir clases entre módulos, ya que probablemente se encuentren a un nivel superior al de los propios módulos.
|
Java
|
|
|---|---|
|
// Configuración común para todos los módulos (fichero.zw)
config { title "Monolito Modular" basePackage "io.zenwave360.example" // Paquetes compartidos entre módulos layout.commonPackage "{{basePackage}}.common" layout.infrastructureRepositoryCommonPackage "{{commonPackage}}" layout.adaptersWebMappersCommonPackage "{{commonPackage}}.mappers" } |
|
7.3. Customizacion Intermedia
7.3.1. Plantillas Personalizadas
Para realizar modificaciones específicas sin necesidad de crear un plugin completo, puedes sobrescribir plantillas individuales.
Proceso:
- Localiza la plantilla original en el repositorio de ZenWave SDK.
- Copia la plantilla a la ruta
.zenwave/templates/[ruta-original]dentro de tu proyecto. - Modifica el contenido según tus necesidades.
Ejemplo: personalizar repositorios de Spring Data para que extiendan una clase base de un starter propio:
|
Bash
|
|
|---|---|
|
.zenwave/templates/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/
src/main/java/core/outbound/jpa/imperative/EntityRepository.java.hbs |
|
Limitaciones: este enfoque permite modificar plantillas, pero no permite añadir nuevos helpers ni cambiar la lógica interna de generación.
7.4. Customizacion Avanzada
7.4.1. Extension del Classpath con JBang
Para customizaciones avanzadas que requieren nuevas dependencias o plugins, puedes utilizar la magia de JBang.
Por ejemplo, creando un archivo jbang-catalog.json en la raíz de tu proyecto, es posible sobrescribir el classpath de ZenWave SDK cuando se invoque desde ese directorio.
|
Text
|
|
|---|---|
|
{
"catalogs": {}, "aliases": { "zw": { "script-ref": "io.zenwave360.sdk:zenwave-sdk-cli:RELEASE", "dependencies": [ "org.slf4j:slf4j-simple:1.7.36", "<tu-dependencia-personalizada-en-formato-maven>", // ... dependencias estándar de ZenWave ], "main": "io.zenwave360.sdk.Main" } } } |
|
Nota importante: las dependencias añadidas al inicio tendrán prioridad y sobrescribirán las clases incluidas en ZenWave SDK.
7.4.2. Customizacion total: Ejemplo de sustitucion completa reemplazando Java por Kotlin
ZenWave SDK permite una customización total.
De hecho, la propia distribución incluye una implementación que reemplaza completamente Java por Kotlin.
Puedes consultar el código fuente de la customización en Kotlin para ver cómo está implementada.
En términos prácticos, para utilizar la customización en Kotlin, basta con configurar la opción templates en aquellos plugins que lo soporten, asignándole la clase correspondiente:
|
Java
|
|
|---|---|
|
config {
zdlFile "zenwave-model.zdl" plugins { BackendApplicationDefaultPlugin { templates "new io.zenwave360.sdk.plugins.kotlin.BackendApplicationKotlinTemplates()" } OpenAPIControllersPlugin { openapiFile "src/main/resources/public/apis/openapi.yml" templates "new io.zenwave360.sdk.plugins.kotlin.OpenAPIControllersKotlinTemplates()" } SpringWebTestClientPlugin { openapiFile "src/main/resources/public/apis/openapi.yml" templates "new io.zenwave360.sdk.plugins.kotlin.SpringWebTestClientKotlinTemplates()" } } } |
|
7.4.3. Creacion de Plugins Personalizados
Para necesidades específicas, puedes crear plugins completamente nuevos que generen cualquier tipo de artefacto: código, APIs, documentación, tests, etc.
Herramientas disponibles:
- Parser de ZDL.
- Parser de OpenAPI/AsyncAPI con resolución de referencias.
- Formatters de código: Java (Google, Palantir, Spring) y Kotlin (
ktfmt). - Motor de plantillas Handlebars.
Puedes basarte en cualquiera de los plugins existentes y utilizar el plugin ZdlToJsonPlugin para inspeccionar la estructura del modelo ZDL y comprender las transformaciones necesarias para alcanzar tu objetivo.
|
Bash
|
|
|---|---|
|
jbang zw -p ZdlToJsonPlugin zdlFile=zenwave-model.zdl
|
|
8. Proyecto Completo de Spring-Boot con Kotlin
Como demostración práctica de lo explicado, hemos desarrollado un proyecto completo que implementa los principios de DDD utilizando ZenWave SDK:
📌 Repositorio de ejemplo: Kustomer with Address and Payment Methods in JPA
8.1. Stack Tecnologico
Hemos elegido un stack tecnológico sencillo pero completo, que puede servir como base sólida para cualquier microservicio empresarial moderno:
- Spring Boot 3.5.x
- Hibernate / Spring Data JPA
- MapStruct
- OpenAPI Generator
- AsyncAPI Generator (ZenWave SDK)
- Avro + Avro Compiler
- Spring Cloud Streams
- Spring Security
- TestContainers con Docker Compose
8.2. Funcionalidad y Modelo de Dominio
El proyecto implementa la gestión completa del aggregate Customer con las siguientes características:
8.2.1. Entidades y Value Objects
- Customer → raíz del agregado.
- Address → colección de direcciones persistida como JSON en la base de datos.
- PaymentMethod → relación
OneToManygestionada por JPA/Hibernate. - PaymentMethodType → enumerado persistido como entero con conversor personalizado.
8.2.2. Operaciones Disponibles
- ✅ CRUD completo vía API REST.
- 🔍 Búsqueda paginada con filtros.
- 📡 Eventos de dominio publicados en Kafka para cada operación.
- 🔐 Autenticación con Spring Security.
8.3. Modelos de ZenWave SDK
El proyecto se construyó utilizando los siguientes modelos de ZenWave SDK:
Ver Modelo ZDL (click para expandir)
|
Java
|
|
|---|---|
|
/**
* Sample ZenWave Model Definition. * Use zenwave-scripts.zdl to generate your code from this model definition. */ config { title "Project Name" basePackage "io.zenwave360.examples.kotlin" persistence jpa databaseType postgresql // you can choose: DefaultProjectLayout, CleanHexagonalProjectLayout, LayeredProjectLayout, SimpleDomainProjectLayout, HexagonalProjectLayout, CleanArchitectureProjectLayout layout LayeredProjectLayout // these should match the values of openapi-generator-maven-plugin // used by the OpenAPIControllersPlugin and SpringWebTestClientPlugin layout.openApiApiPackage "{{basePackage}}.web" layout.openApiModelPackage "{{basePackage}}.web.dtos" openApiModelNameSuffix DTO } /** * Customer entity */ @aggregate @auditing // adds auditing fields to the entity entity Customer { name String required maxlength(254) /** Customer name */ email String required maxlength(254) pattern(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/) /** Customer Addresses can be stored in a JSON column in the database. */ @json addresses Address[] minlength(1) maxlength(5) { street String required maxlength(254) city String required maxlength(254) } } @auditing entity PaymentMethod { type PaymentMethodType required cardNumber String required } enum PaymentMethodType { VISA(1), MASTERCARD(2) } relationship OneToMany { @eager Customer{paymentMethods required maxlength(3)} to PaymentMethod{customer required} } // you can create 'inputs' as dtos for your service methods, or use entities directly input CustomerSearchCriteria { name String email String city String state String } @rest("/customers") service CustomerService for (Customer) { @post createCustomer(Customer) Customer withEvents CustomerEvent @get("/{customerId}") getCustomer(id) Customer? @put("/{customerId}") updateCustomer(id, Customer) Customer? withEvents CustomerEvent @delete("/{customerId}") deleteCustomer(id) withEvents CustomerEvent @post({path: "/search", status: 200}) @paginated searchCustomers(CustomerSearchCriteria) Customer[] } @copy(Customer) @asyncapi({ channel: "CustomersChannel", topic: "customers" }) event CustomerEvent { id Long version Integer // all fields from Customer are copied here, but not relationships paymentMethods PaymentMethod[] } |
|
Ver Scripts ZDL (click para expandir)
|
Java
|
|
|---|---|
|
@import("io.zenwave360.sdk.plugins.customizations:kotlin-backend-application:2.1.0-SNAPSHOT")
config { zdlFile "zenwave-model.zdl" plugins { ZDLToOpenAPIPlugin { idType integer idTypeFormat int64 targetFile "src/main/resources/public/apis/openapi.yml" } ZDLToAsyncAPIPlugin { asyncapiVersion v3 idType integer idTypeFormat int64 targetFile "src/main/resources/public/apis/asyncapi.yml" // includeKafkaCommonHeaders true } BackendApplicationDefaultPlugin { templates "new io.zenwave360.sdk.plugins.kotlin.BackendApplicationKotlinTemplates()" includeEmitEventsImplementation true haltOnFailFormatting false --force // overwite all files } OpenAPIControllersPlugin { openapiFile "src/main/resources/public/apis/openapi.yml" templates "new io.zenwave360.sdk.plugins.kotlin.OpenAPIControllersKotlinTemplates()" } SpringWebTestClientPlugin { openapiFile "src/main/resources/public/apis/openapi.yml" templates "new io.zenwave360.sdk.plugins.kotlin.SpringWebTestClientKotlinTemplates()" } SpringWebTestClientPlugin { openapiFile "src/main/resources/public/apis/openapi.yml" templates "new io.zenwave360.sdk.plugins.kotlin.SpringWebTestClientKotlinTemplates()" groupBy businessFlow businessFlowTestName CreateUpdateDeleteCustomerIntegrationTest operationIds createCustomer,updateCustomer,deleteCustomer,getCustomer } OpenAPIKaratePlugin { openapiFile "src/main/resources/public/apis/openapi.yml" } OpenAPIKaratePlugin { openapiFile "src/main/resources/public/apis/openapi.yml" groupBy businessFlow businessFlowTestName CreateUpdateDeleteCustomerKarateTest operationIds createCustomer,updateCustomer,deleteCustomer,getCustomer } } } |
|
O visita los ficheros directamente:
- zenwave-model.zdl: Definición del modelo de dominio
- zenwave-scripts.zw: Scripts de generación de código
8.4. Ejecucion del Proyecto
Sigue las instrucciones del README.md para ejecutar el proyecto:
|
Text
|
|
|---|---|
|
# Levantar infraestructura (Kafka, PostgreSQL)
docker-compose up -d # Ejecutar la aplicación mvn spring-boot:run -Dspring-boot.run.profiles=local |
|
Puedes acceder a la funcionalidad a través de:
- SwaggerUI → http://localhost:8080/swagger-ui/index.html
- Credenciales de prueba →
admin/password
Conclusión
En este artículo exploramos cómo ZenWave 360º cierra la brecha entre la teoría de Domain-Driven Design (DDD) y la implementación práctica. Demostramos cómo ZenWave Domain Language (ZDL) funciona como un Lenguaje Ubícuo efectivo, capaz de capturar el modelo mental de los expertos de negocio y propagarlo automáticamente a lo largo de todo el proceso de desarrollo.
Puntos clave:
- De Event Storming a código: ZDL mapea de forma efectiva los descubrimientos de Event Storming en un formato estructurado que preserva el lenguaje de negocio y permite la generación automática de código.
- Lenguaje Ubícuo en la práctica: A diferencia de las implementaciones tradicionales de DDD, donde el lenguaje común a menudo se pierde en la traducción, ZDL asegura consistencia desde las conversaciones de negocio hasta el código ejecutable.
- Generación completa de proyectos: Con ZenWave SDK es posible generar un proyecto Spring Boot con Kotlin completamente funcional, que incluye arquitectura limpia, APIs REST, eventos de dominio y pruebas exhaustivas.
- Flexibilidad de customización: El framework ofrece múltiples niveles de personalización, desde simples sobreescrituras de plantillas hasta transformaciones arquitectónicas completas.
La ventaja de ZenWave 360º radica en su capacidad para mantener la coherencia del conocimiento del dominio a lo largo de todo el ciclo de vida del desarrollo. Los expertos de negocio pueden reconocer su lenguaje en el código generado, los arquitectos pueden garantizar una implementación adecuada de DDD y los desarrolladores pueden enfocarse en la lógica de negocio en lugar de en la infraestructura repetitiva.
Al combinar el pensamiento estratégico de DDD con la implementación táctica a través de la generación automática de código, ZenWave 360º permite a los equipos construir software que refleja fielmente el dominio de negocio, manteniendo al mismo tiempo una alta calidad de código y consistencia arquitectónica.
¿Listo para comenzar? Visita www.zenwave360.io y explora el proyecto completo de ejemplo en Kotlin para comprobar cómo ZenWave 360º puede acelerar tu próximo proyecto DDD.