Головна / Статті / Центральний репозиторій конфігурації для мікросервісів: інтеграція та синхронізація за одну команду

Центральний репозиторій конфігурації для мікросервісів: інтеграція та синхронізація за одну команду

Створіть центральний репозиторій конфігурації з скриптами налаштування, синхронізації та щоденного оновлення, щоб кожен сервіс NestJS та React використовував однакові налаштування лінтингу, редактора та Docker.

2452 слів

Коли команда керує десятком мікросервісів на Node.js, найскладнішою частиною першого тижня роботи нового співробітника часто є не код, а середовище: правильна версія Node, відповідні правила ESLint та Prettier, коректні значення файлу .env та налаштування Docker, які працюють так само, як у всіх інших. Кожен сервіс поступово відхиляється від стандарту, і старші інженери змушені виступати у ролі служби підтримки. У цій статті пояснюється, як вирішити цю проблему за допомогою єдиного репозиторію конфігурації та кількох скриптів Bash, які за одну команду налаштовують машину, копіюють спільні параметри до кожного сервісу та підтримують їх актуальність. Ви також дізнаєтесь, які з цих скриптів потребують додаткового захисту, перш ніж їх можна буде використовувати без обережності.

Як поступово виникає відхилення конфігурації

Уявіть собі досвідченого розробника, який приєднується до команди, що керує дванадцятьма сервісами. Перший день проходить у встановленні Node та Docker, налаштуванні стилю ESLint у проекті, спробах визначити, які змінні мають бути у тому чи іншому файлі .env, а також у запитах у чаті щодо версії NestJS, яку використовують сервіси. Потім auth-service припиняє працювати через помилку CORS, спричинену трохи іншими локальними налаштуваннями, а згодом перший pull request провалюється на етапі тестування, оскільки спільна конфігурація Prettier змінилась місяць тому, а локальна копія залишилась незмінною.

Жодна з цих проблем не є критичною, але разом вони спричиняють значні втрати. Коли кожен сервіс має свої власні, трохи відмінні правила лінтингу, Dockerfiles, структуру папок та налаштування редактора, це призводить до втрати днів у новачків та годин щотижня у досвідчених інженерів, які відповідають на запитання щодо налаштувань.

Один репозиторій як джерело істини

Рішенням є спеціалізований репозиторій, тут названий dev-environment-config, який містить усе, що потрібно для роботи з машинами розробки: шаблони проектів, правила лінтингу та форматування, налаштування VS Code, базову конфігурацію Docker, скрипти автоматизації та спільну бібліотеку. Коли хтось оновлює його, кожна машина розробника автоматично отримує ці зміни наступного робочого ранку.

Загальна структура виглядає так: центральний репозиторій живить кожен сервіс:

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   │
└──────────┘ └──────────┘ └──────────┘

Створення системи крок за кроком

Структура центрального репозиторію

Усередині репозиторію кожна функція має власну папку. У шаблонах зберігаються повні стартові проекти NestJS та React, у shared/ — файли для ESLint, Prettier, EditorConfig, правила ігнорування Git та commitlint, у vscode/ — налаштування редактора, а у scripts/ — скрипти автоматизації.

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

Файл manifest.json є тим елементом, який уможливлює решту автоматизації. Він записує загальну версію, час та особу останньої зміни, версії для кожної області конфігурації та список сервісів, які належать до робочого простору.

{
  "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"
  ]
}

Правило просте: будь-яка зміна у спільній конфігурації підвищує версію найвищого рівня. Скрипт оновлення потім порівнює версії, щоб визначити, чи є щось для синхронізації; тож якщо забути підвищити версію, зміни непомітно так і не поширяться. Перевірка в середовищі CI, яка провалюється, коли файли в каталозі shared/ змінюються без підвищення версії, є простим засобом захисту.

Інтеграція за одну команду

Найбільшою перевагою є скрипт bootstrap. Новий розробник встановлює VS Code, а потім виконує одну команду:

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

Приблизно через десять хвилин робоче середовище готове. Передача віддаленого скрипту безпосередньо до bash є зручною, але вимагає обережності: скрипт виконується з правами користувача та з тим, що є на відповідному бранчі у цей момент. Для внутрішнього інструменту це зазвичай прийнятно, проте багато команд воліють, щоб люди клонували репозиторій та виконували скрипт локально, щоб спочатку його прочитати.

Під капотом скрипт працює за фіксованою послідовністю:

#!/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"

Є кілька деталей, на які варто звернути увагу. set -e призупиняє виконання при першій невдалій команді, що запобігає тому, щоб напівналаштоване робоче середовище виглядало завершеним. Допоміжна функція check_and_install не показана та мусить бути написана окремо для кожної операційної системи. Парсинг JSON за допомогою вбудованої однорядкової команди Python працює, але скрипт оновлення згодом використовує jq, тож стандартизація на один інструмент скорочує кількість необхідних умов. Копіювання файлу .env.example забезпечує безпечні значення за замовчуванням, але це працює лише за умови, що прикладні файли залишаються повними та не містять справжніх конфіденційних даних.

Синхронізація спільної конфігурації з кожним сервісом

Скрипт синхронізації проходить по всіх сервісах та копіює спільні файли типу dotfile та налаштування VS Code в кожен з них, перезаписуючи наявні дані.

#!/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

Цей фрагмент припускає, що SCRIPT_DIR та SERVICES вже визначені, тому справжній скрипт має самостійно їх отримати (наприклад, з $0 та маніфесту). Виклики cp -f є навмисними: саме шляхом перезапису усувається відхилення. Недолік полягає у тому, що будь-які локальні зміни до цих файлів втрачаються під час наступної роботи, тому налаштування для кожного сервісу мають зберігатися у окремих файлах, як описано нижче. Також варто подумати, чи справді файл типу .gitignore має бути ідентичним у всіх місцях, адже сервіси часто потребують власних записів.

Щоденна автоматична операція оновлення

Це та частина, яка підтримує синхронність машин без необхідності постійного нагадування про щось. Щодня у робочі дні о 8 годині ранку задача cron запускає 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"

Розглядайте цей скрипт як відправну точку, а не щось, що можна розгортати без змін:

  • Зміни зберігаються, але ніколи не використовуються, тому розробник, який покинув роботу без збереження змін на ніч, повинен знати, як виконати команду git stash pop; інакше його робота, здається, зникає.
  • git diff HEAD~1 перевіряє лише останнє комітування. Якщо операція pull принесла кілька комітів, зміни до package.json, зроблені у попередньому коміті, залишаються непоміченими; порівняння з комітом, який був до pull, є надійнішим.
  • Якщо під час rebase виникають конфлікти, репозиторій залишається у стані rebase без нагляду. Використання команди git pull --ff-only та пропуск запуску сервісу у разі помилки є безпечнішим для автономних завдань.
  • notify-send існує лише в Linux-десктопах, а cron не працює на пристрої, який знаходиться у режимі сну о 8 годині ранку, тому користувачам macOS може знадобитися launchd або інший механізм запуску.
  • Цей повний процес з’єднує дії старшого інженера з роботою кожного розробника наступного ранку:

    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│
        └─────────────────────┘
    

    Спільна бібліотека замість копіювання-вставки

    Функції захисту, інтерсептори, фільтри винятків та DTO часто дублюються між сервісами, а потім починають відрізнятися. Їх упакування у приватний модуль npm, @company/common-lib, дозволяє кожному сервісу використовувати один і той самий імпорт:

    // 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,
        });
      }
    }
    

    Контролер може використовувати спільні механізми автентифікації та контролю ролей, інтерсептор відповідей та фільтр винятків HTTP, приймати загальний DTO для сторінкування та використовувати обгортку McpService для інтеграції з Qwen MCP. Коли бібліотека отримує новий механізм контролю чи виправлення для фільтра винятків, завдання автоматичного оновлення впроваджує їх у кожен сервіс.

    Зверніть увагу, що команда npm update дотримується діапазону версій у кожному файлі package.json, тому масштабні оновлення не будуть автоматично впроваджені, що зазвичай і бажано. Спільні бібліотеки також створюють зв’язок між сервісами; стаття за посиланням про те, коли слід використовувати спільний код пояснює, як вирішити, що належить до одного сервісу.

    Створення нових сервісів

    Раніше створення нового сервісу означало цілий день на копіювання існуючого та видалення непотрібного. Скрипт-генератор перетворює цей процес на коротке інтерактивне запитання:

    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
    

    Оскільки кожен новий сервіс створюється за одним і тим самим шаблоном, він починає свою роботу без відхилень.

    Що отримує команда

    Команда, яка прийняла цю схему, повідомила про такі ефекти:

    • Інтеграція за кілька хвилин: одна команда замінила сторінку wiki з сорока кроками, що скоротило час запуску кожного сервісу з приблизно трьох днів до близько п’ятнадцяти хвилин, а запитання щодо налаштувань у чаті майже зникли.
    • Більше жодних відхилень: сервіси поступово починали відрізнятися (ESLint 8 в одному, 7 — в іншому, застарілі Dockerfiles); заплановане перезаписування зберігає їх ідентичністю.
  • Повернення до роботи на вищому рівні: близько години на день на підтримку налаштувань та усунення помилок CI, пов’язаних лише з форматуванням; приблизно п’ять годин на тиждень — повернення до роботи над функціями.
  • Стабільні показники якості: однакові виправлення помилок та форматування під час збереження, обов’язкові повідомлення про комітти від Husky та автоматично встановлені розширення, що дозволяє під час перегляду зосереджуватися на дизайні, а не на проблемах з відступами.
  • Оновлення без оголошень: нове правило, образ Docker, виправлення розширення чи бібліотеки потрапляють до всіх наступного дня.
  • Прогнозована структура: однакові шаблони у директорії src/common/, налаштування Docker та шлях для перевірки стану у кожному сервісі.
  • Уроки для поступового впровадження

    Почніть з найменшої корисної частини

    Немає потреби створювати все це одночасно. Синхронізація лише налаштувань ESLint та Prettier швидко приносить помітну користь. Налаштування Docker, параметри редактора, спільна бібліотека та заплановані оновлення можна додати після того, як команда довірить цей підхід.

    Залиште простір для локальних змін

    Спільна база не повинна бути обмеженням. Сервіс, якому потрібне додаткове правило, може створити файл .eslintrc.local.js, який розширює спільну конфігурацію; оскільки скрипт синхронізації ніколи не торкається цього файлу, зміни залишаються після кожного запуску.

    Робіть оновлення помітними

    Повідомлення на робочому столі здається дрібною деталлю, але воно повідомляє розробникам про існування системи та про зміни. Тихі оновлення часто викликають плутанину, коли інструменти починають поводитися інакше раптово.

    Версіонуйте все

    Без версії маніфесту завдання оновлення не може визначити, чи щось змінилося, і буде відновлювати синхронізацію щоразу під час виконання. Версіонування також надає конкретний орієнтир, коли зміни створюють проблеми.

    Технології, що лежать в основі прикладу

    У налаштуванні прикладу використовуються NestJS та React (обидва на TypeScript), Qwen через MCP, Docker та Docker Compose, Node.js 20 LTS, PostgreSQL 16, Redis 7, GitHub Actions, VS Code, Bash та cron для синхронізації. Ці версії відображають налаштування на момент створення прикладу; перевірте поточні версії LTS перед копіюванням.

    Основні висновки

    • Як тільки у вас більше кількох сервісів та розробників, централізований репозиторій конфігурації швидко окупає себе.
  • Основними елементами є репозиторій конфігурації з шаблонами та спільними файлами, скрипт налаштування за один командний запит, скрипт синхронізації, запланований оновлювач, спільна бібліотека npm та генератор сервісів.
  • Перезапис спільних файлів допомагає уникнути відхилень, тому з самого початку потрібно створювати спеціальні файли для їх перезапису.
  • Операції в Git без нагляду є найбільш ризикованими; краще використовувати операції типу fast-forward pull, відновлювати збережені дані та виявляти проблеми, а не приховувати їх.
  • Перевагою є те, що розробники можуть присвятити свій час розробці продукту, а не відновленню середовищ один одного.