ESTADO: Propuesto
FECHA: 2026-05-11
AUTOR: Carlos Alberto Torres Camargo (arquitecto)
REVISORES PENDIENTES: gatekeepers del proyecto (líderes técnicos backend y frontend)
Nebula declara desde la concepción del proyecto tres libraries compartidas entre microservicios:
nebula-commons — utilidades, helpers y base classes.nebula-models — DTOs, Records y Value Objects que viajan por la red.nebula-shared — contratos de comunicación REST (interfaces de cliente, excepciones).Estas libraries son consumidas por los 19 microservicios backend Nebula y se publican como SNAPSHOT/RELEASE a Nexus vía pipeline shared library (libraryPipeline.groovy).
Una auditoría sobre los 22 repos backend al 2026-05-11 muestra que el equipo no ha seguido la separación de responsabilidades declarada en el documento de diseño original:
| Library | Estado real | Versiones en Nexus | Diagnóstico |
|---|---|---|---|
nebula-commons |
1 archivo (solo NebulaCommonsApplication) |
1 SNAPSHOT inicial sin actividad posterior | Vacío. Las utilidades viven duplicadas en los microservicios. |
nebula-models |
107 archivos: DTOs + Records + mappers | 21 SNAPSHOTs (versión 0.0.3) | Único activo. El equipo lo usa como cajón general. |
nebula-shared |
2 archivos (GlobalConstants + Application) |
1 SNAPSHOT inicial sin actividad posterior | Mezcla constantes que pertenecen a commons. No tiene interfaces de cliente REST. |
DateUtil, validadores de NIT y constantes están copiados dentro de cada microservicio.nebula-shared como dependency y consumir la interfaz del cliente, copia DTOs locales y construye el WebClient artesanalmente. Esto produce versiones divergentes del mismo DTO en distintos microservicios.DISENO_NEBULA_COMMON_SHARED.md v1.0 hablaba de componentes inexistentes (ColombiaUtils, AccountingCore) y no mencionaba siquiera nebula-models.El camino correcto requiere:
Vs. el camino incorrecto: copiar la clase en el microservicio actual. Sin barrera técnica, el equipo elige sistemáticamente la opción de menor fricción.
Esta ADR formaliza tres elementos:
Adoptar la tabla de decisión definida en DISENO_NEBULA_COMMON_SHARED v2.0 sección 2 como fuente de verdad sobre qué clase va en qué library.
Como parte de la adopción del ADR, se ejecuta un refactor que deja el código actual coherente con las reglas:
nebula-shared/GlobalConstants.java en sus componentes:
nebula-commons/constants/.X-Nebula-*) → mantener en nebula-shared/constants/HttpHeaders.*Util, *Helper, *Validator, validadores Spring @Component) y promoverlos a nebula-commons.nebula-models.nebula-arch-rules)Crear una nueva library nebula-arch-rules (groupId com.centrica.nebula, artifactId nebula-arch-rules) que provee reglas ArchUnit ejecutables como tests JUnit. Cada microservicio Nebula declara la dependencia con <scope>test</scope> y un test runner común valida en mvn verify que:
..nebula.models.. no están anotadas con @Service, @RestController, @Repository, @Component, ni con @Entity o anotaciones JPA...nebula.shared.. son interfaces (@HttpExchange/@FeignClient), excepciones runtime, enums o clases de constantes...nebula.commons.. no son @Service/@RestController/@Repository (sí pueden ser @Configuration).*Dto, *Response, *Request) en código de microservicio que repliquen nombre con clases existentes en nebula-models fallan el build con mensaje guía.El pipeline microservicePipeline.groovy ejecuta mvn verify por defecto, así que el test falla bloqueando el build y el merge.
nebula-arch-rules se desarrolla como repo independiente. No se integra inmediatamente en los 22 repos backend. La integración requiere:
nebula-arch-rules en el pom parent (o en cada repo) y agregar la stage Architecture Rules al pipeline shared.| Alternativa | Pros | Contras | Veredicto |
|---|---|---|---|
| A. Solo documentación (status quo + actualizar wiki) | Esfuerzo mínimo. Sin cambios en pipelines. | Ya se intentó: el doc v1.0 existe desde Sprint inicial y el equipo no lo respeta. La fricción de seguir la regla supera a la de no seguirla. | Descartada. |
| B. Sonar custom rules | Sonar ya está en la pipeline. Algunos quality gates podrían capturar duplicación de DTOs. | Custom rules en Sonar son costosas de mantener (lenguaje propietario, falta de tooling). No granularidad de "esta clase no debe ir aquí". | Descartada como reemplazo, podría complementar para detectar duplicación. |
| C. PR review con CODEOWNERS estricto | Solución cultural. No requiere herramientas. | Cuello de botella en el arquitecto. No escala con sprints paralelos. No previene merges hechos en branches que evitan code review (hot-fixes). | Descartada como única medida. Sí se mantiene como capa adicional. |
| D. ArchUnit (esta decisión) | Tests JUnit estándar. Mensajes de error guían al dev hacia la corrección. Falla el build, bloquea el merge. Open source maduro. | Esfuerzo inicial: definir reglas precisas. Cualquier regla muy estricta genera fricción innecesaria; muy laxa no captura nada. | Adoptada. |
| E. Multi-module Maven con boundary-control | Forzar via Maven que microservicio NO pueda compilar clases de modelo "fuera de lugar". | Romper la independencia de repos por microservicio. Centrica eligió 1 repo por microservicio explícitamente. | Descartada (rompe arquitectura existente). |
Architecture Rules agrega ~30-60s a cada build.GlobalConstants y la promoción de duplicados a nebula-commons requiere PRs coordinados.nebula-arch-rules se versiona como cualquier library; un dev puede pinning version mientras se arregla la regla.A los 60 días de adoptado el ADR:
nebula-commons con al menos 5 utilidades reales (validadores, helpers, autoconfig).nebula-shared con al menos 3 interfaces de cliente REST en uso.| Fase | Acción | Owner | Estado |
|---|---|---|---|
| 1 | Reescribir DISENO_NEBULA_COMMON_SHARED.md v2.0 con tabla de decisión |
Arquitecto | Completado 2026-05-11 |
| 2 | Emitir esta ADR-002 en estado Propuesto | Arquitecto | Completado 2026-05-11 |
| 3 | Crear repo nebula/core/nebula-arch-rules y scaffold inicial (sin integrar) |
Arquitecto | Completado 2026-05-11 |
| 4 | Presentar ADR-002 a gatekeepers, recibir feedback, ajustar | Arquitecto + gatekeepers | Pendiente |
| 5 | Cambiar estado de ADR a Aprobado | Arquitecto | Pendiente fase 4 |
| 6 | Ejecutar refactor inicial (sección 3.2) | Equipo backend (PRs por dev) | Pendiente fase 5 |
| 7 | Definir versión 1.0 de reglas ArchUnit y publicar SNAPSHOT a Nexus | Arquitecto | Pendiente fase 5 |
| 8 | Integrar dependency en los 22 repos backend (PR coordinado) | Arquitecto + equipo | Pendiente fase 7 |
| 9 | Agregar stage Architecture Rules al microservicePipeline.groovy v1.1.0 |
Arquitecto | Pendiente fase 8 |
| 10 | Comunicar al equipo: a partir de fecha X, MRs que violen reglas se bloquean en CI | Arquitecto | Pendiente fase 9 |
Las reglas finales viven en nebula-arch-rules. Este es un boceto del estilo:
package com.centrica.nebula.archrules;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
@AnalyzeClasses(packages = "com.centrica.nebula")
public class NebulaArchitectureRules {
@ArchTest
static final ArchRule models_no_spring_beans =
noClasses()
.that().resideInAPackage("..nebula.models..")
.should().beAnnotatedWith("org.springframework.stereotype.Service")
.orShould().beAnnotatedWith("org.springframework.stereotype.Component")
.orShould().beAnnotatedWith("org.springframework.stereotype.Repository")
.orShould().beAnnotatedWith("org.springframework.web.bind.annotation.RestController")
.as("nebula-models debe contener solo DTOs, Records, mappers y enums. Spring beans no permitidos.");
@ArchTest
static final ArchRule models_no_jpa =
noClasses()
.that().resideInAPackage("..nebula.models..")
.should().beAnnotatedWith("jakarta.persistence.Entity")
.orShould().beAnnotatedWith("jakarta.persistence.Table")
.as("Entidades JPA viven en el microservicio dueño del dominio, no en nebula-models.");
}
Mensaje de error claro al fallar el build:
[ERROR] ArchRule: nebula-models debe contener solo DTOs, Records, mappers y enums.
Spring beans no permitidos. — was violated (1 times)
[ERROR] class com.centrica.nebula.models.accounting.SomeService is annotated with @Service