# El diseño que el repositorio puede comprobar

Hay algo que he visto repetirse en muchos proyectos: la arquitectura está muy bien documentada, pero cuando miras el código, el repositorio y lo que realmente está desplegado, empiezas a encontrar diferencias.

El diagrama dice una cosa. El código hace otra. Y cuando ocurre un incidente, el equipo pierde tiempo tratando de entender cómo encajan las piezas.

Por eso creo que una buena arquitectura no debería depender únicamente de documentos o de que alguien recuerde seguir las convenciones. Debería poder comprobarse en el día a día del desarrollo.

¿Cómo? Con cosas concretas: un repositorio con un responsable claro, nombres coherentes entre el código y la infraestructura, contratos versionados, pruebas automatizadas, secretos fuera del código y logs que permitan seguir una petición de punta a punta.

Son decisiones que quizá no luzcan tanto en una presentación, pero que marcan una diferencia enorme cuando el sistema está en producción.

Estas son algunas de las prácticas que considero fundamentales para que la arquitectura no se quede en el papel.

1. Cada componente necesita un lugar y un responsable El repositorio es solo una pieza de la arquitectura. Para entender cómo encaja en el sistema, necesitamos saber a qué dominio de negocio pertenece, qué sistema forma parte de él y qué componente se despliega.

![](https://cdn.hashnode.com/uploads/covers/64a79aba336591d2a1481aae/024f24ea-7618-4794-9b18-27357ce929d0.png align="center")

Cada nivel debe tener un equipo responsable.

Parece algo básico, pero pensemos en un incidente a las dos de la mañana: si nadie sabe quién es responsable de un componente, encontrar a la persona adecuada puede convertirse en otro problema.

Por eso, la ficha del componente debería vivir en el mismo repositorio que el código. Así, la información evoluciona junto con el sistema y no depende de que alguien recuerde actualizar una wiki.

También conviene distinguir dos situaciones que pueden parecer iguales: un componente que todavía no se ha construido y uno que ya tiene un repositorio, pero aún no está registrado en el catálogo. Confundirlos puede llevar a duplicar soluciones o dejar componentes en producción sin un responsable claro.

Y hay otro detalle importante: el ciclo de vida.

Un componente experimental, uno en producción y uno deprecado no deberían tratarse de la misma manera. Cuando algo se retira, hay que dejarlo claramente identificado para evitar que otro equipo lo tome como punto de partida para un desarrollo nuevo.

2. Los nombres también forman parte de la arquitectura Un identificador consistente facilita mucho más que la organización de los repositorios.

Si seguimos una convención que combina producto, capacidad y tipo de componente, podemos reconocer rápidamente qué estamos viendo. El nombre también debe respetar los límites de longitud de la plataforma cloud donde se desplegará.

Además, el tipo de artefacto debería ser reconocible desde el principio: servicio, job, función, frontend, librería, script o documentación.

![](https://cdn.hashnode.com/uploads/covers/64a79aba336591d2a1481aae/b3160d33-fdc5-4dfb-b80d-ebe534465267.png align="center")

Pero lo realmente importante es mantener ese identificador consistente en tres lugares:

El repositorio. Los tags de la infraestructura. La configuración del servicio en tiempo de ejecución.

Cuando los tres coinciden, resulta mucho más sencillo relacionar una alerta con el servicio afectado y llegar hasta el código responsable.

Cuando no coinciden, cada incidente empieza con una búsqueda y una tabla de equivalencias que alguien tiene que mantener.

Las variantes regionales también deben contemplarse en la convención de nombres. No conviene resolverlas improvisando convenciones paralelas o creando ramas que permanecen abiertas durante meses.

Dentro del código, los paquetes deberían reflejar las capas y responsabilidades definidas para el proyecto. La idea es que un desarrollador pueda reconocer la estructura general sin tener que leer primero todo el documento de arquitectura.

3. Mejor partir de una plantilla que copiar otro repositorio Copiar un servicio existente parece una forma rápida de comenzar. El problema es que también copiamos decisiones que quizá tenían sentido para ese servicio, pero no para el nuevo.

Podemos terminar arrastrando dependencias innecesarias, versiones desactualizadas, configuraciones particulares o comentarios que ya no tienen sentido.

Prefiero partir de una plantilla ejecutable, diseñada para el tipo de carga que necesitamos construir.

![](https://cdn.hashnode.com/uploads/covers/64a79aba336591d2a1481aae/dfca62fe-14a6-4c11-bb6e-0bbd69b469ff.png align="center")

Por ejemplo:

API síncrona: una estructura preparada para implementar el contrato, organizar las capas y habilitar la observabilidad básica. Job: un proceso por lotes con un arranque controlado y códigos de salida claros. Frontend embebido: una estructura que permita integrar la interfaz dentro de una aplicación existente. Función de corta duración: un handler delgado, con la lógica de negocio organizada en casos de uso independientes.

La plataforma puede compartir módulos comunes para la integración con la nube, el logging y las pruebas. Otros componentes, como persistencia, clientes remotos, eventos o caché, se incorporan cuando el caso realmente los necesita.

No todo tiene que estar presente desde el primer día. Añadir dependencias «por si acaso» también tiene un costo: aumenta la complejidad, el mantenimiento y la superficie de posibles fallos.

Las plantillas, además, necesitan su propio ciclo de vida. Debemos distinguir las versiones vigentes de las retiradas y definir cómo migrar los proyectos existentes.

Por ejemplo, si cambia el contrato de errores de una librería común, no basta con actualizar la versión en el proyecto padre. También hay que revisar el código que interpreta esos errores.

Una plantilla útil no solo acelera la creación de servicios; ayuda a mantener una arquitectura coherente a medida que los sistemas evolucionan.

4. No confundamos patrones, diseños y arquetipos En las revisiones de arquitectura es fácil mezclar conceptos que están relacionados, pero que cumplen funciones distintas.

Un patrón describe una solución reutilizable a un problema recurrente. Puede ser un consumidor idempotente, un proceso programado o una forma de componer interfaces.

Una cápsula de arquitectura, en cambio, representa un diseño prescriptivo que una organización ha definido y versionado. Tiene un estado, como publicada o deprecada, y establece qué decisiones deben seguirse.

El arquetipo es el código ejecutable que materializa ese diseño al crear un repositorio.

Y el servicio es lo que finalmente construimos y llevamos a producción.

![](https://cdn.hashnode.com/uploads/covers/64a79aba336591d2a1481aae/7da979ef-1314-4f7c-865a-615b1fa8872e.png align="center")

¿Por qué importa distinguirlos?

Porque citar un patrón conocido no significa que exista un diseño aprobado para nuestra organización. Y disponer de un generador de código tampoco garantiza que el servicio resultante cumpla todos los requisitos.

Cuando un documento de solución utiliza una cápsula, debería identificarla con su versión completa. Si el caso necesita apartarse de ella, la diferencia debe quedar registrada en una decisión de arquitectura.

Los cambios importantes pueden requerir una nueva aprobación, mientras que los ajustes menores pueden seguir el proceso ordinario de revisión.

Y cuando no existe una cápsula para el caso, lo correcto es documentar la decisión, no inventar una referencia que parezca oficial.

El objetivo no es acumular documentación, sino poder entender qué decisión se tomó, por qué y con qué consecuencias.

5. El contrato debería estar definido antes de implementar Otro punto que considero fundamental es definir y versionar el contrato antes de escribir la implementación.

Esto aplica tanto a una API síncrona como a un evento o a una comunicación entre procesos.

El contrato define cómo se comunican los componentes y qué pueden esperar unos de otros. Si cambia sin control, los consumidores terminan dependiendo de comportamientos que quizá nadie pretendía mantener.

También es importante normalizar la estructura de los errores e incluir un identificador de traza que permita relacionar la respuesta recibida por el consumidor con los registros internos del servicio.

Esto cobra especial importancia cuando una interfaz es utilizada por terceros o debe mantenerse estable en varios mercados.

Un campo que cambia de nombre en una región, pero no en las demás, puede terminar generando diferencias permanentes en el código de los clientes.

Por eso, no basta con exigir que los contratos estén documentados. También necesitamos un catálogo de interfaces publicadas que permita comprobar que realmente existen y están versionadas.

Si el estándar exige contratos, pero el catálogo está vacío, todavía no tenemos una práctica operativa: tenemos una intención.

6. Cada capa debe tener una responsabilidad clara Dentro de un servicio, separar responsabilidades no es una cuestión estética. Es una forma de evitar que los cambios se propaguen por toda la aplicación.

La capa de entrada debería validar la petición y extraer el contexto necesario. No debería consultar directamente la base de datos ni terminar conteniendo reglas de negocio.

La lógica de negocio y la gestión de las transacciones corresponden a la capa de servicio. La persistencia tiene su propia responsabilidad, al igual que la comunicación con sistemas externos.

![](https://cdn.hashnode.com/uploads/covers/64a79aba336591d2a1481aae/9373cf9b-8345-48a4-bbbb-1b8d451b9828.png align="center")

También prefiero que cada sistema externo tenga su propio cliente de integración. Así, un cambio de contrato, un timeout o una particularidad de comunicación se pueden gestionar sin distribuir esas decisiones por toda la aplicación.

Las consultas a la base de datos tampoco deberían aparecer dispersas en el código de negocio, y la configuración de los entornos no debería quedar incrustada en el binario.

Esta separación facilita las pruebas, reduce el acoplamiento y permite modificar una parte del sistema sin tener que revisar cada una de las demás.

No significa que todos los proyectos deban tener decenas de capas o una estructura innecesariamente compleja. Significa que cada responsabilidad debe estar en el lugar adecuado para el tamaño y las necesidades del sistema.

7. Cada servicio debe ser dueño de sus datos Compartir tablas entre servicios puede acelerar el primer entregable, pero suele complicar los cambios posteriores.

Cuando varios servicios dependen de las mismas tablas, una modificación aparentemente pequeña puede convertirse en un cambio coordinado entre múltiples equipos.

El objetivo debería ser que cada servicio sea dueño de sus datos y que otros componentes accedan a ellos mediante contratos definidos.

Por supuesto, esto no siempre se consigue de un día para otro, especialmente durante una modernización.

![](https://cdn.hashnode.com/uploads/covers/64a79aba336591d2a1481aae/75d0af82-0dcc-4711-87ee-2d8f77cb50f1.png align="center")

Cuando el sistema anterior debe seguir funcionando mientras extraemos capacidades nuevas, necesitamos una estrategia de convivencia. La captura de cambios (CDC) puede ayudar a sincronizar datos, mientras que un componente de enrutamiento permite decidir qué sistema atiende cada capacidad.

![](https://cdn.hashnode.com/uploads/covers/64a79aba336591d2a1481aae/e04db58b-ff68-49c6-b4e9-b3ce40f20615.png align="center")

De esta manera, podemos migrar progresivamente sin exigir un corte único para todo el sistema.

En mensajería hay otro aspecto que no podemos pasar por alto: los mensajes pueden entregarse más de una vez.

El consumidor debe estar preparado para reconocer un mensaje repetido y evitar que vuelva a producir el mismo efecto de negocio.

Pensemos en un cobro, una notificación o un asiento contable. Un reintento técnico no debería terminar generando una segunda operación de negocio.

Aquí la idempotencia es esencial.

Y cuando estamos separando componentes, también debemos revisar cómo se comunican. Si sustituimos una integración acoplada por otra llamada directa entre los mismos módulos, quizá hayamos cambiado la implementación, pero no el problema de fondo.

Los eventos, las colas y los procesos asíncronos pueden ayudar a desacoplar responsabilidades cuando el caso lo justifica.

8. Varios mercados no deberían significar varios códigos Cuando una aplicación opera en distintos países o regiones, es habitual encontrar diferencias de configuración, requisitos de negocio y condiciones operativas.

La primera opción debería ser resolver esas diferencias mediante configuración y feature flags, manteniendo una base de código común.

Crear una rama permanente por país puede parecer una solución rápida, pero con el tiempo aparecen divergencias, conflictos al fusionar cambios y comportamientos difíciles de mantener.

Hay situaciones en las que la latencia o los requisitos de residencia de datos obligan a separar la infraestructura o los despliegues. Eso no implica necesariamente mantener implementaciones distintas.

La clave está en separar las decisiones de despliegue de las decisiones sobre el código.

Si realmente necesitamos artefactos regionales diferentes, esa diferencia debe quedar identificada y gobernada por una convención clara.

9. La observabilidad y la seguridad forman parte del diseño

Un servicio no está listo simplemente porque compile y pase sus pruebas funcionales.

También necesitamos saber qué está haciendo cuando se ejecuta, cómo detectar sus fallos y cómo responder cuando algo sale mal.

Los logs estructurados en JSON deberían incluir información que permita correlacionar las operaciones, como el identificador de traza, el span y el nombre del servicio.

También conviene definir cuándo registrar un error y cuándo estamos simplemente ante un resultado de negocio esperado. Una operación rechazada por una regla de negocio no debería tratarse automáticamente como un fallo inesperado del sistema.

La información sensible requiere un tratamiento específico. Los logs no deben exponer datos de pago, credenciales ni identificadores sensibles; la clasificación de los datos debe determinar qué se excluye o enmascara.

Y las alertas necesitan procedimientos de respuesta. Una alerta sin instrucciones claras puede avisarnos de un problema, pero no necesariamente ayuda a resolverlo.

La topología de los sistemas también debería poder descubrirse a partir de metadatos consistentes de infraestructura. Siempre que sea posible, la plataforma debe gestionar la instrumentación de forma estandarizada, en lugar de depender de instalaciones manuales distintas en cada servicio.

Con los secretos, la regla es todavía más clara: no deben entrar al repositorio, ni en texto plano ni cifrados.

Deben inyectarse durante la ejecución desde un almacén de secretos. Los hooks locales pueden detectar exposiciones antes del commit, y el pipeline debe reforzar ese control.

Si una credencial llega a publicarse en Git, hay que considerarla comprometida y rotarla. Borrar el archivo en un commit posterior no elimina su presencia del historial.

Por último, antes de llegar a producción, el pipeline debe exigir los controles definidos para el proyecto: pruebas, análisis estático y revisión de dependencias, entre otros.

La idea es sencilla: la seguridad y la observabilidad no deberían depender de que alguien se acuerde de incorporarlas al final.

10. El diseño también debe adaptarse al tamaño del cambio No todos los cambios necesitan el mismo nivel de documentación.

Un ajuste pequeño no tiene el mismo impacto que una modernización completa de un dominio. Por eso, resulta útil definir categorías o tallas que determinen los plazos de revisión y las secciones obligatorias del diseño.

![](https://cdn.hashnode.com/uploads/covers/64a79aba336591d2a1481aae/af5d27c9-a3ca-4b47-a722-e73b71ad2b55.png align="center")

Lo importante es que el tamaño del cambio no se convierta en una excusa para omitir controles fundamentales.

Un cambio tecnológico, por ejemplo, puede tener un diff muy pequeño y aun así afectar la seguridad, la resiliencia o la operación del servicio.

La revisión debe considerar el impacto real, no solo la cantidad de líneas modificadas.

Si una sección obligatoria no aplica, conviene justificarlo mediante un registro de decisión arquitectónica. Dejarla vacía sin explicación genera ambigüedad y hace difícil distinguir una excepción deliberada de un olvido.

El proceso debería contemplar la solicitud, la clasificación del cambio, la elaboración del diseño, las revisiones técnicas y de seguridad, la aprobación y, finalmente, la socialización con los equipos que van a implementarlo.

Este último paso suele infravalorarse.

Un diseño aprobado que el equipo de desarrollo no ha revisado puede terminar convertido en un documento que nadie consulta. Las dudas aparecerán durante la implementación, cuando cambiar el diseño sea más costoso y la presión por entregar favorezca las soluciones improvisadas.

La arquitectura no termina con la aprobación del documento. También necesita llegar a las personas que van a convertirla en código.

11. ¿Cómo comprobamos que la arquitectura se está cumpliendo? Para mí, aquí está la parte más importante.

No necesitamos abrir un documento de arquitectura para comprobar cada una de estas prácticas. Muchas pueden verificarse directamente en los repositorios, los pipelines y los sistemas en ejecución.

Podemos empezar con preguntas concretas:

¿Cada componente tiene un responsable, pertenece a un sistema y está asociado a un dominio de negocio? ¿Su ficha está disponible junto al código? ¿El identificador del servicio coincide en el repositorio, la infraestructura y el entorno de ejecución? ¿El proyecto se creó a partir de una plantilla adecuada para su tipo de carga? ¿El contrato está publicado y versionado? ¿Los errores permiten correlacionar una petición con sus trazas? ¿Las capas tienen responsabilidades claras y cada servicio controla sus propios datos? ¿Un mensaje duplicado puede provocar que se repita una operación de negocio? ¿Los secretos están fuera del historial del repositorio? ¿Las alertas tienen procedimientos de respuesta? ¿Las excepciones al diseño están justificadas y registradas?

Estas preguntas convierten principios arquitectónicos en comprobaciones concretas.

Algunas pueden automatizarse en los pipelines. Otras requieren revisar decisiones, responsabilidades o comportamientos en producción. No todo se puede reducir a una regla automática, pero sí podemos hacer que muchas de nuestras expectativas sean verificables.

Y esa diferencia importa: no se trata de tener más controles por tenerlos, sino de detectar desviaciones cuando todavía es sencillo corregirlas.

Para cerrar Un diagrama puede seguir siendo correcto mientras la arquitectura real empieza a desviarse.

Por eso, creo que el trabajo de un arquitecto de software no consiste únicamente en definir componentes, elegir tecnologías o producir documentos. También consiste en crear las condiciones para que las decisiones arquitectónicas sobrevivan al desarrollo, a los cambios de equipo y a la operación diaria.

Los nombres, los contratos, las plantillas, las pruebas, la seguridad y la observabilidad pueden parecer detalles independientes. En realidad, son parte de una misma idea: hacer que la arquitectura sea visible, verificable y sostenible en el tiempo.

La arquitectura aporta mucho más valor cuando deja de ser algo que simplemente documentamos y se convierte en algo que podemos comprobar.

¿En tu organización cómo verifican que la arquitectura definida es realmente la que termina ejecutándose en producción?

Enjoy!

Joe
