Inicio / Artículos / Un repositorio de configuración central para microservicios: integración y sincronización con un solo comando

Un repositorio de configuración central para microservicios: integración y sincronización con un solo comando

Cree un repositorio central de configuración con scripts de configuración, sincronización y actualización diaria para que todos los servicios de NestJS y React compartan las configuraciones de linting, editor y Docker.

2452 palabras

Cuando un equipo ejecuta una docena de microservicios Node.js, la parte más difícil de la primera semana de un nuevo empleado suele ser no el código sino el entorno: la versión correcta de Node, reglas compatibles con ESLint y Prettier, los valores adecuados del .env y una configuración de Docker que funcione como la de los demás. Cada servicio tiende a desviarse un poco, y los ingenieros senior terminan actuando como servicio de soporte. Este artículo muestra cómo solucionar esto con un único repositorio de configuración además de algunos scripts en Bash que inicializan una máquina con un solo comando, copian los ajustes compartidos a cada servicio y los mantienen actualizados. También verá en qué aspectos los scripts, tal como están escritos, necesitan mejoras antes de poder confiar en ellos.

Cómo se introduce la desviación en la configuración

Imagínese a un desarrollador experimentado que se une a un equipo responsable de doce servicios. El primer día se pasa instalando Node y Docker, definiendo el estilo ESLint del equipo, intentando adivinar qué variables pertenecen a cada archivo .env y preguntando por chat qué versión de NestJS utilizan los servicios. Luego, auth-service falla debido a un error CORS causado por una configuración local ligeramente diferente, y más tarde la primera solicitud de pull request falla en las pruebas automatizadas porque la configuración compartida de Prettier cambió hace un mes y la copia local nunca se actualizó.

Ninguno de estos fallos es grave, pero juntos resultan costosos. Cuando cada servicio tiene sus propias reglas de lint ligeramente distintas, archivos Dockerfile, estructura de carpetas y configuraciones del editor, el costo se manifiesta en días perdidos para los recién llegados y horas semanales desperdiciadas por los ingenieros senior respondiendo preguntas sobre la configuración.

Un repositorio como fuente de verdad

La solución es un repositorio dedicado, denominado aquí dev-environment-config, que contiene todo lo necesario para una máquina de desarrollo: plantillas de proyecto, reglas de lint y formato, configuraciones de VS Code, configuración básica de Docker, scripts de automatización y una biblioteca compartida. Cuando alguien lo actualiza, cada máquina de desarrollador recibe automáticamente los cambios a la mañana siguiente.

La estructura general es la siguiente, con el repositorio central que suministra datos a cada servicio:

dev-environment-config (Central Repo)
├── templates/          → Project scaffolding (NestJS, React)
├── shared/             → ESLint, Prettier, Git configs
├── vscode/             → Extensions & settings
├── docker/             → Base Docker configs
├── scripts/            → Automation (setup, sync, update)
├── common-lib/         → Shared NPM package
└── manifest.json       → Version tracking

         │  syncs to
         ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ user-svc │ │ auth-svc │ │ pay-svc  │  ... 12 services
│ .eslint  │ │ .eslint  │ │ .eslint  │
│ .vscode  │ │ .vscode  │ │ .vscode  │
│ Docker   │ │ Docker   │ │ Docker   │
└──────────┘ └──────────┘ └──────────┘

Construyendo el sistema paso a paso

Estructura del repositorio central

Dentro del repositorio, cada área tiene su propia carpeta. Las plantillas albergan iniciadores completos para NestJS y React; shared/ contiene archivos de configuración para ESLint, Prettier, EditorConfig, reglas de ignorado en Git y commitlint; vscode/ guarda las configuraciones del editor, y scripts/ alberga los scripts de automatización.

dev-environment-config/
├── templates/
│   ├── nestjs-service/    # Complete NestJS starter
│   └── reactjs-service/   # Complete React starter
├── shared/
│   ├── .eslintrc.js
│   ├── .prettierrc
│   ├── .editorconfig
│   ├── .gitignore
│   └── commitlint.config.js
├── vscode/
│   ├── extensions.json
│   ├── settings.json
│   └── launch.json
├── scripts/
│   ├── setup-new-developer.sh
│   ├── sync-config.sh
│   ├── auto-update.sh
│   └── create-new-service.sh
├── common-lib/            # Shared NPM package
└── manifest.json          # Version tracker

El archivo manifest.json es lo que hace posible el resto de la automatización. Registra la versión general, cuándo y por quién se modificó por última vez, las versiones para cada área de configuración y la lista de servicios que pertenecen al espacio de trabajo.

{
  "version": "1.2.0",
  "lastUpdated": "2024-01-15T10:00:00Z",
  "updatedBy": "senior-dev",
  "configs": {
    "eslint": "3.2.0",
    "prettier": "2.1.0",
    "docker": "1.5.0"
  },
  "services": [
    "user-service",
    "auth-service",
    "payment-service"
  ]
}

La regla es sencilla: cualquier cambio en la configuración compartida aumenta la versión de nivel superior. El script de actualización compara posteriormente las versiones para decidir si hay algo que sincronizar; por lo tanto, olvidarse de aumentarla significa que el cambio nunca se propagará silenciosamente. Una verificación en CI que falla cuando los archivos bajo shared/ cambian sin un aumento de versión constituye una medida de seguridad económica.

Onboarding con un solo comando

La mayor ventaja es el script de arranque. Un nuevo desarrollador instala VS Code y luego ejecuta un único comando:

curl -sSL https://raw.githubusercontent.com/company/dev-environment-config/main/scripts/setup-new-developer.sh | bash

Aproximadamente diez minutos después, el espacio de trabajo está listo. Es conveniente ejecutar directamente un script remoto desde bash, pero se debe actuar con precaución: el script se ejecuta con los permisos del usuario y con lo que haya en la rama en ese momento. Para una herramienta interna esto suele ser aceptable, sin embargo, muchos equipos prefieren que las personas clonen el repositorio y ejecuten el script localmente para poder leerlo primero.

En realidad, el script funciona siguiendo una secuencia fija:

#!/bin/bash
set -e

WORKSPACE_DIR="$HOME/company-workspace"
CONFIG_REPO="https://github.com/company/dev-environment-config.git"

echo "🚀 Setting up your development environment..."

# 1. Check & install prerequisites
check_and_install "git" "Git"
check_and_install "node" "Node.js" "nvm install 20"
check_and_install "docker" "Docker"

# 2. Create workspace & clone everything
mkdir -p "$WORKSPACE_DIR" && cd "$WORKSPACE_DIR"
git clone "$CONFIG_REPO"

# 3. Clone all service repos from manifest
SERVICES=$(cat dev-environment-config/manifest.json | \
  python3 -c "import sys,json; \
  [print(s) for s in json.load(sys.stdin)['services']]")

for service in $SERVICES; do
  git clone "https://github.com/company/${service}.git"
done

# 4. Install VS Code extensions
EXTENSIONS=$(cat dev-environment-config/vscode/extensions.json | \
  python3 -c "import sys,json; \
  [print(e) for e in json.load(sys.stdin)['recommendations']]")

for ext in $EXTENSIONS; do
  code --install-extension "$ext" --force
done

# 5. Sync shared configs to all services
bash dev-environment-config/scripts/sync-config.sh

# 6. Install dependencies everywhere
for service in $SERVICES; do
  cd "$service" && npm install && cp .env.example .env && cd ..
done

# 7. Set up daily auto-update cron (8 AM weekdays)
CRON="0 8 * * 1-5 cd $WORKSPACE_DIR && \
  bash dev-environment-config/scripts/auto-update.sh"
(crontab -l 2>/dev/null; echo "$CRON") | crontab -

echo "✅ Setup complete! Open VS Code: code $WORKSPACE_DIR"

Hay algunos detalles que merecen atención. set -e interrumpe la ejecución en el primer comando que falla, lo cual evita que un espacio de trabajo parcialmente configurado parezca completo. La función auxiliar check_and_install no se muestra y debe escribirse según el sistema operativo. El análisis de JSON mediante una línea de comandos en Python funciona, pero el script de actualización utiliza posteriormente jq; por lo tanto, estandarizar en una sola herramienta reduce los requisitos previos. Copiar .env.example proporciona valores por defecto seguros, pero solo funciona si los archivos de ejemplo se mantienen completos y no contienen secretos reales.

Sincronización de la configuración compartida en cada servicio

El script de sincronización recorre los servicios y copia los archivos dotfiles compartidos así como las configuraciones de VS Code en cada uno, sobrescribiendo lo que ya existe allí.

#!/bin/bash
# sync-config.sh

CONFIG_ROOT="$(dirname "$SCRIPT_DIR")"
WORKSPACE_DIR="$(dirname "$CONFIG_ROOT")"

for service in $SERVICES; do
  SERVICE_DIR="$WORKSPACE_DIR/$service"

  # Copy shared configs
  cp -f "$CONFIG_ROOT/shared/.eslintrc.js" "$SERVICE_DIR/"
  cp -f "$CONFIG_ROOT/shared/.prettierrc" "$SERVICE_DIR/"
  cp -f "$CONFIG_ROOT/shared/.editorconfig" "$SERVICE_DIR/"
  cp -f "$CONFIG_ROOT/shared/.gitignore" "$SERVICE_DIR/"

  # Copy VS Code settings
  mkdir -p "$SERVICE_DIR/.vscode"
  cp -f "$CONFIG_ROOT/vscode/settings.json" "$SERVICE_DIR/.vscode/"
  cp -f "$CONFIG_ROOT/vscode/extensions.json" "$SERVICE_DIR/.vscode/"

  echo "✅ $service synced"
done

El fragmento asume que SCRIPT_DIR y SERVICES ya están definidos, por lo que el script real debe obtenerlos por sí mismo (por ejemplo, a partir de $0 y el manifiesto). Las llamadas a cp -f son intencionales: la sobrescritura es el método para eliminar las desviaciones. La contrapartida es que cualquier edición local en esos archivos se perderá en la siguiente ejecución, por lo que la personalización por servicio debe realizarse en archivos separados, como se explicará a continuación. También considere si un archivo como .gitignore realmente debería ser idéntico en todas partes, ya que los servicios a menudo necesitan sus propias entradas.

La tarea de actualización automática diaria

Esta es la parte que mantiene las máquinas alineadas sin que nadie tenga que acordarse de hacer nada. Cada día laborable a las 8 AM, la tarea cron ejecuta auto-update.sh:

#!/bin/bash
# auto-update.sh — Runs daily at 8 AM

cd "$CONFIG_ROOT"

# 1. Check for config updates
OLD_VERSION=$(cat manifest.json | jq -r '.version')
git pull origin main
NEW_VERSION=$(cat manifest.json | jq -r '.version')

if [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
  echo "🆕 Config updated: $OLD_VERSION → $NEW_VERSION"
  bash "$SCRIPT_DIR/sync-config.sh"
fi

# 2. Pull latest code for all services
for service in $SERVICES; do
  cd "$WORKSPACE_DIR/$service"

  # Stash local changes safely
  git stash push -m "auto-update-$(date +%Y%m%d)" 2>/dev/null

  # Pull latest
  git pull origin $(git branch --show-current) --rebase

  # Update dependencies if package.json changed
  if git diff HEAD~1 --name-only | grep -q "package.json"; then
    npm install
  fi

  # Update shared library
  npm update @company/common-lib
done

# 3. Notify the developer
notify-send "Dev Environment" "✅ Updated to config v$NEW_VERSION"

Trate este script como un punto de partida y no como algo que se debe desplegar sin cambios:

  • Los cambios se guardan temporalmente pero nunca se aplican, por lo que un desarrollador que dejó su trabajo sin guardar durante la noche debe saber cómo ejecutar git stash pop; de lo contrario, el trabajo parece desaparecer.
  • git diff HEAD~1 solo examina el último commit. Si la operación de pull trajo varios commits, se pasarán por alto los cambios en package.json de uno anterior; comparar con el commit que había antes de hacer el pull es más fiable.
  • Un rebase que encuentra conflictos deja el repositorio en medio del proceso sin que nadie lo supervise. Ejecutar git pull --ff-only y omitir el servicio en caso de error es más seguro para tareas sin supervisión.
  • notify-send solo está disponible en escritorios Linux, y cron no se ejecuta en una máquina que está en modo suspensión a las 8 de la mañana, por lo que los usuarios de macOS pueden necesitar launchd o un disparador diferente.
  • El flujo de extremo a extremo conecta el envío de un ingeniero senior con la acción del siguiente día por la mañana de cada desarrollador:

    Senior Dev pushes config change
             │
             ▼
       GitHub Actions
             │
        ┌────┴────┐
        │ Slack   │ Notify team
        │ Publish │ common-lib to NPM
        └────┬────┘
             │
        Next 8 AM
             │
             ▼
      Every developer's cron runs
             │
        ┌────┴────────────────┐
        │ Pull config repo    │
        │ Sync shared configs │
        │ Pull service repos  │
        │ Update dependencies │
        │ Desktop notification│
        └─────────────────────┘
    

    Una biblioteca compartida en lugar de copiar y pegar

    Las protecciones, interceptores, filtros de excepciones y DTOs tienden a duplicarse entre los servicios y luego a divergir. Al empaquetarlos como un módulo npm privado, @company/common-lib, cada servicio dispone de una única importación:

    // In any service, just import and use:
    import {
      AuthGuard,
      RolesGuard,
      ResponseInterceptor,
      LoggingInterceptor,
      HttpExceptionFilter,
      PaginationDto,
      McpService,        // Qwen MCP integration
    } from '@company/common-lib';
    
    @Controller('users')
    @UseGuards(AuthGuard, RolesGuard)
    @UseInterceptors(ResponseInterceptor)
    @UseFilters(HttpExceptionFilter)
    export class UserController {
      constructor(private mcpService: McpService) {}
    
      @Get()
      findAll(@Query() pagination: PaginationDto) {
        return this.userService.findAll(pagination);
      }
    
      @Post('analyze')
      async analyze(@Body() dto: AnalyzeDto) {
        return this.mcpService.callTool('text-analysis', {
          text: dto.text,
        });
      }
    }
    

    Un controlador puede aplicar las protecciones de autenticación y roles compartidas, el interceptor de respuestas y el filtro de excepciones HTTP, aceptar un DTO común para paginación y utilizar un wrapper McpService para la integración con Qwen MCP. Cuando la biblioteca recibe una nueva protección o una corrección para el filtro de excepciones, la tarea de actualización automática la integra en todos los servicios.

    Tenga en cuenta que npm update respeta el rango semver de cada package.json, por lo que una versión mayor no se actualizará automáticamente, lo cual suele ser lo deseado. Las bibliotecas compartidas también generan acoplamiento entre servicios; el artículo en cuándo debería existir código compartido explica cómo decidir qué contenido pertenece a cada una.

    Creación de nuevos servicios

    Antes, crear un servicio nuevo significaba pasar todo un día copiando uno existente y eliminando lo que no era aplicable. Un script generador convierte ese proceso en una breve solicitud interactiva:

    bash scripts/create-new-service.sh
    
    # Prompts:
    # Service name: order-service
    # Description: Handles order processing
    # Port: 3002
    # Type: nestjs
    
    # ✅ Done! Complete service with:
    # - src/common/ (filters, guards, interceptors, DTOs)
    # - Docker setup (Dockerfile + docker-compose)
    # - VS Code settings
    # - ESLint + Prettier
    # - Health check endpoint
    # - Swagger docs
    # - MCP integration
    # - Auto-update scripts
    

    Dado que cada nuevo servicio parte del mismo template, comienza sin desviaciones.

    Beneficios para el equipo

    Un equipo que adoptó esta configuración reportó los siguientes efectos:

    • Onboarding en minutos: un único comando reemplazó una página de wiki con cuarenta pasos, reduciendo el tiempo necesario para ejecutar cada servicio de aproximadamente tres días a unos quince minutos, y las preguntas sobre la configuración en el chat prácticamente desaparecieron.
    • Sin más desviaciones: los servicios solían divergir silenciosamente (ESLint 8 en uno, 7 en otro, Dockerfiles obsoletos); las sobrescripciones programadas mantienen su identidad.
  • Vuelta al trabajo de nivel senior: aproximadamente una hora al día dedicada a soporte de configuración y solución únicamente de fallos en CI relacionados con el formato, unas cinco horas a la semana, para volver al trabajo en nuevas funcionalidades.
  • Signales de calidad consistentes: las mismas correcciones de formato al guardar, mensajes de commit exigidos por Husky y extensiones instaladas automáticamente, de modo que las revisiones se centren en el diseño en lugar del espaciado.
  • Actualizaciones sin anuncios: una nueva regla, imagen base de Docker, corrección de una extensión o biblioteca llega a todos al día siguiente.
  • Estructura predecible: los mismos patrones en src/common/, configuración de Docker y ruta de verificación de estado en cada servicio.
  • Lecciones para su implementación gradual

    Comience con la parte más pequeña y útil

    No es necesario crear todo esto de una sola vez. Sincronizar solo la configuración de ESLint y Prettier aporta beneficios visibles rápidamente. La configuración de Docker, los ajustes del editor, la biblioteca compartida y las actualizaciones programadas pueden implementarse una vez que el equipo confíe en este enfoque.

    Deje espacio para sobrescrituras locales

    Una línea base compartida no debe ser una restricción estricta. Un servicio que necesite una regla adicional puede crear un archivo .eslintrc.local.js que extienda la configuración compartida; como el script de sincronización nunca modifica ese archivo, la sobrescritura se mantiene en cada ejecución.

    Haga que las actualizaciones sean visibles

    La notificación en el escritorio parece un detalle menor, pero informa a los desarrolladores de que el sistema existe y de que algo ha cambiado. Las actualizaciones silenciosas suelen confundir a las personas cuando sus herramientas comienzan a comportarse de manera diferente de la noche a la mañana.

    Versione todo

    Sin la versión del manifiesto, el proceso de actualización no puede determinar si ha habido cambios y tendría que sincronizarse en cada ejecución. La versionado también proporciona un punto de referencia concreto cuando un cambio causa problemas.

    La tecnología detrás del ejemplo

    La configuración del ejemplo combinó NestJS y React (ambos en TypeScript), Qwen al que se accedía a través de MCP, Docker y Docker Compose, Node.js 20 LTS, PostgreSQL 16, Redis 7, GitHub Actions, VS Code y Bash, además de cron para la sincronización. Esas versiones reflejan la configuración en ese momento; verifica las versiones LTS actuales antes de copiarlas.

    Puntos clave

    • Cuando se cuenta con más de unos pocos servicios y desarrolladores, un repositorio central de configuración rinde rápidamente sus frutos.
  • Las piezas fundamentales son un repositorio de configuración con plantillas y archivos compartidos, un script de configuración con una sola orden, un script de sincronización, un actualizador programado, una biblioteca npm compartida y un generador de servicios.
  • Sobrescribir los archivos compartidos es lo que elimina las desviaciones, por lo que se deben diseñar archivos de sobrescritura explícitos desde el primer día.
  • Las operaciones de Git sin supervisión son la parte más arriesgada; es mejor utilizar pulls tipo fast-forward, restaurar los stashes y mostrar los fallos en lugar de ocultarlos.
  • La ventaja es que los desarrolladores pueden dedicar su tiempo al código del producto en lugar de a reconstruir los entornos de los demás.
  • Lecturas relacionadas