Template Gradle (Kotlin DSL) para generar microservicios Quarkus siguiendo las convenciones de Nova Platform: arquitectura hexagonal con principios de Clean Architecture, envelope de respuesta HTTP unificado, persistencia JPA/Panache y convention plugins compartidos.
El template en sí no compila: contiene placeholders (
__PACKAGE__,__DOMAIN__,__ARTIFACT__). Solo el proyecto generado por la tarearenamecompila y se ejecuta.
| Arquetipo | Propósito | Estado |
|---|---|---|
service |
Modela negocio (dominio + reglas + persistencia) | ✅ Disponible |
bff |
Compone/orquesta llamadas a otros servicios | ⬜ Pendiente |
acl |
Aísla un sistema externo (anti-corruption layer) | ⬜ Pendiente |
./gradlew rename \
-PgroupId=pe.utp.nova \
-PartifactId=ms-academic-course \
-Ppackage=pe.utp.nova \
-Pdomain=academic \
-Ptype=service \
-PoutputDir=/ruta/a/otra/carpeta| Parámetro | Ejemplo | Reemplaza en el template |
|---|---|---|
-PgroupId |
pe.utp.nova |
El group de Gradle (gradle.properties), y todo __GROUP__ en el código. |
-PartifactId |
ms-academic-course |
El nombre del proyecto (settings.gradle.kts, rootProject.name), y todo __ARTIFACT__. |
-Ppackage |
pe.utp.nova |
El paquete Java raíz. Reemplaza __PACKAGE__ y renombra físicamente la carpeta __PACKAGE__/ a pe/utp/nova/. |
-Pdomain |
academic |
El nombre del bounded context / microservicio. Reemplaza __DOMAIN__ y renombra la carpeta __DOMAIN__/ (dentro del paquete) al valor dado — ej. academic/. |
-Ptype |
service |
Qué arquetipo copiar desde src-styles/<type>/ (service, bff o acl). |
-PoutputDir (opcional) |
/d/Proyectos/mi-proyecto |
Si se indica, genera en esa carpeta aparte en vez de sobrescribir el template. Recomendado siempre que se quiera probar sin arriesgar el repo del template. |
Si se omite -PoutputDir, la tarea reescribe el propio directorio del template
(uso previsto solo al instanciar un repo nuevo desde "Use this template").
cd /ruta/al/proyecto/generado
export DB_URL="jdbc:postgresql://<host>:<puerto>/<basededatos>?sslmode=require"
export DB_USER="<usuario>"
export DB_PASSWORD="<password>"
./gradlew quarkusDevquarkusDevlevanta la app en modo desarrollo con hot reload, enhttp://localhost:8080.- Las 3 variables (
DB_URL,DB_USER,DB_PASSWORD) son obligatorias para los perfiles%dev/%qa/%prod— ver sección Configuración. - Para solo compilar y correr los tests:
./gradlew build.
- Run → Edit Configurations...
- Selecciona (o crea) la configuración Gradle para la tarea
quarkusDev. - Busca el campo Environment variables (bajo "Modify options" si no
aparece directo) y agrega, una por línea:
DB_URL=jdbc:postgresql://<host>:<puerto>/<basededatos>?sslmode=require DB_USER=<usuario> DB_PASSWORD=<password> - Apply → OK.
- Ejecuta esa configuración (▶ verde).
El arquetipo service combina dos escuelas de arquitectura, tomando de cada
una lo que mejor sirve al template:
- De Hexagonal (Ports & Adapters): el vocabulario y la separación
port/in·port/out·adapter/in·adapter/out. El dominio expone puertos (interfaces); los adapters son quienes implementan esos puertos contra una tecnología concreta (HTTP, JPA, un cliente REST). - De Clean Architecture (Uncle Bob): la regla de dependencia — las
capas internas nunca dependen de las externas. El dominio no importa nada
de
adapter/,service/, ni de ningún framework. Esta regla se enforce automáticamente con ArchUnit (ArchitectureTest) en cada build.
| Concepto | Qué es en este template |
|---|---|
| Bounded Context (BC) | El límite dentro del cual un modelo de dominio es consistente y tiene un único significado. Cada __DOMAIN__/ generado (ej. academic/) ES un bounded context — un microservicio, un contexto. |
| Domain | Las reglas de negocio puras, sin tecnología. Vive en __DOMAIN__/domain/: no importa Quarkus, JPA, ni HTTP. |
| Model | Los objetos que representan el conocimiento del dominio: el aggregate root (entidad raíz que protege sus invariantes), los value objects (inmutables, comparados por valor, sin identidad propia) y las entities internas (con identidad, pero sin existir fuera del agregado). |
| Aggregate | Un agrupamiento de objetos tratado como una unidad para efectos de cambios de datos. Solo se modifica a través de su raíz — nadie muta sus partes internas directo. |
| Port | Un contrato (interfaz) que el dominio declara — de entrada (qué operaciones ofrece) o de salida (qué necesita del exterior). |
| Adapter | La implementación concreta de un puerto contra una tecnología real (un controller REST, un repositorio JPA, un cliente HTTP). |
__PACKAGE__/ (ej. pe.utp.nova)
├── Application.java ← entry point, en la RAÍZ
│
│ ── cross-cutting, FUERA del dominio, a nivel raíz ──
├── authentication/ resolvers de autenticación
├── commons/ tipos compartidos entre dominios
│ ├── exception/ ErrorCode, DomainException (base)
│ └── response/ ApiResponse<T>, ApiErrorItem
├── config/ configuración transversal
├── events/ infraestructura de eventos
├── utils/ utilidades genéricas
├── web/ filtros/mappers HTTP transversales
│ ├── ApiResponseWrapperFilter envuelve toda respuesta 2xx
│ ├── DomainExceptionMapper traduce DomainException → envelope
│ └── GenericExceptionMapper catch-all, ningún error escapa
│
└── __DOMAIN__/ (ej. academic — el bounded context)
│
├── adapter/ ← capa EXTERNA (Clean: infraestructura)
│ ├── in/
│ │ ├── web/ Controller + mapper/ + request/
│ │ ├── listener/ eventos entrantes
│ │ └── scheduler/ tareas programadas
│ └── out/
│ ├── persistence/ Entity JPA + Mapper + Repository
│ └── client/ clientes REST a sistemas externos
│
├── domain/ ← capa INTERNA (Clean: entities)
│ ├── model/ agregado + value objects + entities
│ └── event/ eventos de dominio
│
├── exception/ excepciones del dominio (hermano de domain/)
│
├── port/ ← frontera entre dominio y exterior
│ ├── in/ PLANO: XxxUseCase, XxxCommand,
│ │ XxxQuery, XxxResult
│ └── out/ XxxRepository (interfaces de salida)
│
└── service/ ← capa de aplicación (Clean: use cases)
implementa port/in, orquesta domain/
La regla de dependencia, en una línea: adapter → service → port →
domain. Nunca al revés. domain/ no conoce ni siquiera que adapter/
existe.
| Capa / carpeta | Responsabilidad | Depende de | Nunca conoce |
|---|---|---|---|
domain/model |
Reglas de negocio, invariantes del agregado | Nada (Java puro) | JPA, HTTP, Quarkus |
domain/event |
Eventos que ocurrieron en el dominio | domain/model |
JPA, HTTP |
exception/ |
Excepciones semánticas del dominio (ErrorCode) |
commons/exception |
HTTP, status codes |
port/in |
Contrato de entrada (qué operaciones ofrece el dominio) | domain/model |
Implementación concreta |
port/out |
Contrato de salida (qué necesita el dominio del exterior) | domain/model |
JPA, el motor de BD específico |
service/ |
Orquesta el dominio para cumplir un caso de uso; implementa port/in |
domain/, port/out |
HTTP, JPA |
adapter/in/web |
Traduce HTTP → comando de aplicación | port/in (la interfaz) |
La implementación del use case |
adapter/out/persistence |
Traduce dominio ↔ tabla de BD; implementa port/out |
domain/model, JPA |
El use case, otros adapters |
commons/ |
Contratos compartidos entre todos los __DOMAIN__/ |
Nada | Un __DOMAIN__ específico |
web/ (raíz) |
Mecanismos HTTP transversales (envelope, manejo de errores) | commons/ |
Un __DOMAIN__ específico |
Toda respuesta de la API sigue el mismo contrato, alineado con el template BFF NestJS de Nova:
{
"success": true,
"status": 200,
"data": { },
"errors": []
}En error:
{
"success": false,
"status": 404,
"data": null,
"errors": [
{ "code": "NOT_FOUND", "message": "...", "field": null }
]
}- Los éxitos (2xx) se envuelven automáticamente vía
ApiResponseWrapperFilter— ningún controller construye el envelope a mano. - Los errores de dominio pasan por
DomainExceptionMapper, que traduce elErrorCodede la excepción alstatusHTTP correspondiente. - Cualquier excepción no anticipada cae en
GenericExceptionMapper, que garantiza que ninguna respuesta escape del contrato — sin filtrar detalle interno al cliente.
| Componente | Versión | Notas |
|---|---|---|
| Java | 25 (Temurin) | No Java 21 — el toolchain está fijado en nova.java-conventions.gradle.kts. |
| Gradle | 9.5.1 | Wrapper incluido (./gradlew), no requiere instalación aparte. |
| Quarkus | 3.33.2.1 LTS | Definido en gradle.properties (quarkusPlatformVersion). |
| Kotlin DSL | — | El build system (.gradle.kts) usa Kotlin, no Groovy. |
El template usa sintaxis moderna de Java donde aporta claridad, no como exhibición:
- Records (Java 16+) — todos los value objects (
ClassSectionId,StudentId,Capacity,ApiResponse,ApiErrorItem, los futurosCommand/Query/Result) sonrecord, con constructor compacto para validar invariantes. - Lambdas y streams — ej.
Optional.orElseThrow(() -> ...)(perezoso, no construye la excepción salvo que haga falta),stream().anyMatch(...)para buscar en colecciones sin loops explícitos. switchexpression (Java 14+) — usado enDomainExceptionMapperpara mapearErrorCode→ status HTTP sinbreakni fall-through.var— se evita a propósito en las firmas públicas (preferimos tipos explícitos para legibilidad de API), pero es válido en variables locales internas.
./gradlew testCorre los tests unitarios y de integración (@QuarkusTest, ArchUnit). El
reporte HTML queda en build/reports/tests/test/index.html.
⚠️ No configurado todavía. Este template aún no incluye JaCoCo ni ninguna otra herramienta de cobertura en el convention plugin. Cuando se agregue, el comando esperado sería./gradlew test jacocoTestReport, con el reporte enbuild/reports/jacoco/test/html/index.html.
El proyecto generado usa src/main/resources/application.properties (formato
.properties, no .yml) — es el que trae el archetype por defecto y el que
mantiene consistencia con el resto de configuración de Quarkus del template.
Los valores por ambiente se separan con el prefijo de perfil de Quarkus
(%dev., %qa., %prod., %test.).
| Propiedad | Valor / origen | Descripción |
|---|---|---|
quarkus.http.port |
8080 |
Puerto HTTP de la app. |
quarkus.http.root-path |
/api |
Prefijo de todos los endpoints de negocio. |
quarkus.application.name |
__ARTIFACT__ |
Nombre de la app (se resuelve al generar). |
quarkus.smallrye-health.root-path |
/health |
Endpoint de health check (fuera de /api). |
quarkus.hibernate-orm.schema-management.strategy |
ver tabla de perfiles abajo | Estrategia de schema. No usar quarkus.hibernate-orm.database.generation — está deprecada en Quarkus 3.33+. |
quarkus.datasource.db-kind |
postgresql |
Motor de base de datos. |
quarkus.datasource.jdbc.url |
${DB_URL} |
URL JDBC, inyectada por variable de entorno. |
quarkus.datasource.username |
${DB_USER} |
Usuario de BD, inyectado por variable de entorno. |
quarkus.datasource.password |
${DB_PASSWORD} |
Password de BD, inyectada por variable de entorno. Nunca se escribe en el archivo. |
quarkus.log.level |
INFO |
Nivel de log global. |
quarkus.log.category."__PACKAGE__".level |
DEBUG |
Nivel de log específico del paquete de la app. |
| Perfil | schema-management.strategy |
Datasource | Motivo |
|---|---|---|---|
%test |
drop-and-create |
Auto (Dev Services, contenedor Postgres desechable — requiere Docker) | Cada corrida de tests necesita un schema limpio; no hay BD persistente que mantener. |
%dev |
validate |
Real, por variables de entorno | Local, pero contra datos reales o compartidos con el equipo. Nunca se altera el schema desde la app. |
%qa |
validate |
Real, por variables de entorno | Ambiente de pruebas del equipo. |
%prod |
validate |
Real, por variables de entorno | Producción. La app jamás crea ni modifica tablas — se asume que existen (creadas manualmente o por la herramienta de migración que el equipo adopte). |
El DDL exacto que cada entity espera encontrar está documentado en el javadoc
de la propia clase *Entity.java correspondiente (ej. ClassSectionEntity).
Apache 2.0.