Centralne repozytorium konfiguracji dla mikrosług: szybkie wdrożenie i synchronizacja za pomocą jednego polecenia
Stwórz centralne repozytorium konfiguracji z skryptami do konfiguracji, synchronizacji oraz codziennych aktualizacji, aby wszystkie usługi NestJS i React dzieliły się ustawieniami sprawdzania kodu, edytora oraz Dockera.
Gdy zespół uruchamia tuzin mikrosług Node.js, najtrudniejszą częścią pierwszego tygodnia nowego pracownika jest często nie sam kod, lecz środowisko: odpowiednia wersja Node, zgodne zasadami ESLint i Prettier, poprawne wartości pliku .env oraz konfiguracja Dockera, która funkcjonuje tak samo jak u innych. Każda z usług ma tendencję do niewielkich odchyleń, przez co starsi inżynierowie muszą pełnić rolę punktu pomocy. Ten artykuł pokazuje, jak to naprawić za pomocą jednego repozytorium konfiguracji oraz kilku skryptów Bash, które w jednym poleceniu uruchamiają maszynę, kopiują wspólne ustawienia do każdej usługi i utrzymują je aktualne. Zobaczysz również, jakie modyfikacje są potrzebne w tych skryptach, zanim będziesz mógł na nie polegać.
Jak pojawia się dewiacja konfiguracji
Załóżmy, że do zespołu składającego się z dwunastu usług dołącza doświadczony programista. Pierwszy dzień upływa na instalowaniu Node i Dockera, ustalaniu stylu ESLint w zespole, domyślaniu się, które zmienne należą do którego pliku .env, oraz pytaniu w czacie o wersję NestJS używaną przez usługi. Następnie auth-service przestaje działać z powodu błędu CORS spowodowanego nieco innymi ustawieniami lokalnymi, a później pierwszy pull request zostaje odrzucony przez system CI, ponieważ wspólna konfiguracja Prettier została zmieniona miesiąc temu, podczas gdy lokalna kopia pozostała niezmieniona.
Żaden z tych problemów nie jest katastrofalny, ale łącznie powodują duże straty czasu. Gdy każda usługa ma swoje własne, nieco różne zasady lintingu, pliki Dockerfile, strukturę folderów oraz ustawienia edytora, koszt ten objawia się utraconymi dniami dla nowicjuszy oraz godzinami tygodniowo poświęcanymi przez doświadczonych inżynierów na odpowiadanie na pytania dotyczące konfiguracji.
Jeden repozytorium jako źródło prawdy
Rozwiązaniem jest dedykowany repozytorium, tutaj nazwane dev-environment-config, które zawiera wszystko, czego potrzebuje maszyna do rozwoju: szablony projektów, zasady lintingu i formatowania, ustawienia VS Code, podstawową konfigurację Dockera, skrypty automatyzacji oraz bibliotekę wspólną. Gdy ktoś je aktualizuje, każda maszyna programisty automatycznie otrzymuje tę zmianę następnego dnia roboczego rano.
Ogólny układ wygląda w ten sposób, przy czym centralne repozytorium zaopatruje każdą usługę:
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 │
└──────────┘ └──────────┘ └──────────┘
Budowanie systemu krok po kroku
Struktura centralnego repozytorium
Wewnątrz repozytorium każda kategoria ma swoją własną folder. Szablony zawierają kompletnych starterów dla NestJS i React, shared/ przechowuje pliki konfiguracyjne dla ESLint, Prettier, EditorConfig, zasad ignorowania plików w Git oraz commitlint, vscode/ zawiera ustawienia edytora, a scripts/ przechowuje skrypty automatyzacji.
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
Plik manifest.json umożliwia resztę procesu automatyzacji. Zawiera informacje o ogólnej wersji, datie i osoby, która ostatnio dokonała zmiany, wersjach dla poszczególnych obszarów konfiguracji oraz listę usług należących do przestrzeni roboczej.
{
"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"
]
}
Zasada jest prosta: każda zmiana w wspólnej konfiguracji zwiększa wersję najwyższego poziomu. Skrypt aktualizacyjny porównuje później wersje, aby ustalić, czy istnieją coś do zsynchronizowania; zaniedbanie podniesienia wersji oznacza, że zmiana nie zostanie przeniesiona. Sprawdzenie w środowisku CI, które zawodzi, gdy pliki w katalogu shared/ ulegają zmianie bez podniesienia wersji, stanowi tanie zabezpieczenie.
Wdrożenie za pomocą jednego polecenia
Największą zaletą jest skrypt bootstrapowy. Nowy programista instaluje VS Code, a następnie uruchamia pojedyncze polecenie:
curl -sSL https://raw.githubusercontent.com/company/dev-environment-config/main/scripts/setup-new-developer.sh | bash
Mniej więcej po dziesięciu minutach przestrzeń robocza jest gotowa. Przekierowanie zdalnego skryptu bezpośrednio do bash jest wygodne, ale wymaga ostrożności: skrypt uruchamia się z uprawnieniami użytkownika oraz z treścią znajdującą się w danym branchu w tym momencie. W przypadku narzędzia wewnętrznego jest to zazwyczaj do przyjęcia, jednak wiele zespołów woli, aby ludzie sklonowali repozytorium i uruchomili skrypt lokalnie, aby najpierw mogli go przeczytać.
W rzeczywistości skrypt działa zgodnie ze stałą sekwencją kroków:
#!/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"
Warto zwrócić uwagę na kilka szczegółów. Polecenie set -e zatrzymuje wykonywanie po pierwszym nieudanym poleceniu, co zapobiega temu, by częściowo skonfigurowane środowisko wyglądało na kompletnie ustawione. Funkcja pomocnicza check_and_install nie jest pokazana i musi zostać napisana specjalnie pod daną system operacyjny. Analiza plików JSON za pomocą jednoliniowego skryptu w Pythonie działa, ale późniejszy skrypt aktualizacyjny wykorzystuje jq, więc ustandaryzowanie na jednym narzędziu zmniejsza wymagania wstępne. Skopiowanie pliku .env.example zapewnia bezpieczne wartości domyślne, ale działa to tylko wtedy, gdy pliki przykładowe są kompletne i nie zawierają prawdziwych tajemnic.
Synchronizacja wspólnej konfiguracji z każdą usługą
Skrypt synchronizacyjny przechodzi przez wszystkie usługi i kopiuje wspólne pliki typu dotfile oraz ustawienia VS Code do każdej z nich, nadpisując istniejące wartości.
#!/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
Fragment ten zakłada, że SCRIPT_DIR oraz SERVICES są już zdefiniowane, dlatego rzeczywisty skrypt musi je sam wyznaczyć (na przykład na podstawie $0 i pliku manifestu). Wywołania cp -f są celowe – właśnie przez nadpisywanie usuwa się różnice. Wadą jest to, że wszelkie lokalne zmiany w tych plikach zostaną utracone przy następnym uruchomieniu, dlatego personalizacja dla poszczególnych usług musi znajdować się w oddzielnych plikach, jak opisano poniżej. Rozważ również, czy plik taki jak .gitignore rzeczywiście powinien być identyczny we wszystkich miejscach, ponieważ usługi często potrzebują własnych wpisów.
Kod automatycznego aktualizowania codziennie
To właśnie ten mechanizm utrzymuje maszyny w spójności, bez konieczności ciągłego przypominania o czymkolwiek. Każdego dnia roboczego o 8 rano zadanie cron uruchamia 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"
Traktuj ten skrypt jako punkt wyjścia, a nie coś, co należy wdrożyć bez zmian:
- Zmiany są przechowywane, ale nigdy nie przywracane, więc programista, który opuścił biuro bez zapisania zmian na noc, musi wiedzieć, jak uruchomić
git stash pop; w przeciwnym razie jego praca wydaje się zniknąć. git diff HEAD~1sprawdza tylko ostatni commit. Jeśli operacja pull przyniosła kilka komitów, zmiana w plikupackage.jsonz wcześniejszego commitu może zostać przeoczona; porównanie z commitem przed pulliem jest bardziej wiarygodne.- Rebase, który napotyka konflikty, pozostawia repozytorium w trakcie procesu rebase bez żadnej nadzoru. Użycie
git pull --ff-onlyoraz pominięcie uruchomienia usługi w przypadku błędu jest bezpieczniejsze przy pracy bez nadzoru.
notify-send istnieje tylko w systemach Linux, a cron nie działa na komputerze, który jest w trybie uśpienia o 8 rano, więc użytkownicy macOS mogą potrzebować launchd lub innego mechanizmu uruchamiania.Całościowy proces łączy działania starszego inżyniera z pracą każdego programisty następnego ranka:
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│
└─────────────────────┘
Biblioteka współdzielona zamiast kopiowania i wklejania
Bezpieczniki, intercepcje, filtry wyjątków oraz DTO-y często są kopiowane pomiędzy usługami, co prowadzi do różnic w ich funkcjonowaniu. Pakietowanie ich jako prywatnego modułu npm, @company/common-lib, umożliwia każdej usłudze jedno importowanie:
// 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,
});
}
}
Kontroler może stosować wspólne mechanizmy autoryzacji i kontrolę ról, interceptor odpowiedzi oraz filtr wyjątków HTTP, przyjmować wspólny format DTO do paginacji i używać obudowy McpService dla integracji z Qwen MCP. Gdy biblioteka otrzymuje nowy mechanizm bezpieczeństwa lub poprawkę do filtru wyjątków, zadanie automatycznego aktualizowania wprowadza je do każdej usługi.
Należy pamiętać, że polecenie npm update uwzględnia zakres wersji semver w każdym pliku package.json, więc większe aktualizacje nie będą automatycznie pobierane, co zazwyczaj jest pożądane. Biblioteki wspólne powodują również powiązania pomiędzy usługami; artykuł na temat kiedy powinien istnieć kod wspólny wyjaśnia, jak zdecydować, co powinno znajdować się w jednej z usług.
Budowanie nowych usług
Kiedyś tworzenie nowej usługi oznaczało cały dzień spędzony na kopiowaniu istniejącej i usuwaniu niepotrzebnych elementów. Skrypt generujący zamienia to w krótkie, interaktywne polecenie:
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
Ponieważ każda nowa usługa wychodzi od tego samego szablonu, od początku nie ulega dewiacjom.
Korzyści dla zespołu
Zespół, który przyjął tę konfigurację, odnotował następujące efekty:
- Wdrożenie w kilka minut: jedno polecenie zastąpiło stronę wiki składającą się z czterdziestu kroków, co skróciło czas uruchamiania każdej usługi z około trzech dni do około piętnastu minut, a pytania dotyczące konfiguracji w czacie praktycznie zniknęły.
- Brak dewiacji: usługi mogły się potajemnie różnić (ESLint 8 w jednej, 7 w drugiej, przestarzałe pliki Dockerfile); zaplanowane nadpisywanie utrzymuje je w identyczności.
src/common/, konfiguracja Dockera oraz ścieżka do sprawdzania stanu we wszystkich usługach.Lekcje z wdrażania
Zacznij od najmniejszej użytecznej części
Nie ma potrzeby budowania tego wszystkiego od razu. Synchronizacja jedynie ustawień ESLint i Prettier szybko przynosi widoczne korzyści. Konfiguracja Dockera, ustawienia edytora, biblioteka współdzielona oraz zaplanowane aktualizacje mogą zostać wdrożone, gdy zespół zaufa temu podejściu.
Zostaw miejsce na lokalne modyfikacje
Wspólna baza nie powinna być sztywnym ograniczeniem. Usługa, która potrzebuje dodatkowej reguły, może dodać plik .eslintrc.local.js, który rozszerza wspólną konfigurację; ponieważ skrypt synchronizacji nigdy nie dotyka tego pliku, modyfikacja przetrwa każde uruchomienie.
Uczyń aktualizacje widocznymi
Powiadomienie na pulpicie wydaje się drobnostką, ale informuje programistów o istnieniu systemu oraz o tym, że coś się zmieniło. Ciche aktualizacje często mylą ludzi, gdy ich narzędzia nagle zaczynają zachowywać się inaczej.
Zawieraj wersje wszystkiego
Bез wersji manifestu zadanie aktualizacji nie może stwierdzić, czy coś się zmieniło, i musiałoby synchronizować dane przy każdym uruchomieniu. Wersjonowanie dostarcza również konkretnego punktu odniesienia, gdy zmiana powoduje problemy.
Technologie użyte w przykładzie
W przykładowym ustawieniu połączono NestJS i React (oba w TypeScript), Qwen dostępny przez MCP, Docker oraz Docker Compose, Node.js 20 LTS, PostgreSQL 16, Redis 7, GitHub Actions, VS Code oraz Bash wraz z cron do synchronizacji. Te wersje odzwierciedlają ustawienie z tamtego czasu; przed ich skopiowaniem sprawdź aktualne wersje LTS.
Główne wnioski
- Gdy masz już więcej niż kilka usług i programistów, centralne repozytorium konfiguracji szybko się opłaca.
Literatura pokrewna
- Weryfikacja vs. Ruch: Gdzie kompilator Go w TypeScript 7 pasuje do twojego zestawu narzędzi — Dowiedz się, co naprawdę przyspiesza wbudowany kompilator Go w TypeScript 7, dlaczego narzędzia takie jak Angular i typescript-eslint pozostają w tyle oraz jak oddzielić weryfikację typów od wykonywania kodu.
- Budowanie osobistego agenta AI, któremu można ufać: pamięć, narzędzia i zdobyty dostęp — Jak osobisty agent taki jak Hermes Agent łączy pamięć, narzędzia oraz routowanie modeli, oraz jak bezpiecznie wdrożyć go przy użyciu ograniczonych uprawnień, źródeł o niskim ryzyku i zdobytego dostępu.