Jakie jest miejsce Turborepo w monorepo NestJS i kiedy warto go pominąć
Jak Turborepo, pnpm workspaces oraz NestJS dzielą pracę w monorepo typu TypeScript: wspólne pakiety, grafy zadań, buforowanie, ryzyko powiązań oraz kiedy warto ich pominąć.
Źródła: Turborepo + NestJS: The Monorepo Architecture That Makes Large TypeScript Systems Manageable
Tło backendowe NestJS zazwyczaj rozpoczyna się jako pojedyncza API z jednym plikiem package.json, jedną folderem node_modules oraz jednym pipeline’em CI. W miarę rozwoju produktu funkcje takie jak autoryzacja, zamówienia, płatności, zarządzanie zapasami i powiadomienia są dzielone na oddzielne usługi, a pytanie operacyjne zmienia się z „jak skalować tę usługę?” na „jak zarządzać siedmioma aplikacjami w TypeScript bez konieczności kopiowania tej samej konfiguracji, typów i pipeline’ów siedem razy?”. Ten artykuł wyjaśnia, jaką rolę pełnią monorepo, pnpm i Turborepo, w jaki sposób graf zadań i cache oszczędzają czas, co można bezpiecznie udostępniać oraz kiedy koszt konfiguracji przewyższa jej korzyści.
Przez cały czas miej na uwadze jeden kluczowy aspekt. Turborepo nie tworzy mikrosług, nie zastępuje NestJS i sam w sobie nie sprawia, że architektura staje się skalowalna. Rozwiązuje ono węższy problem: koordynację zadań typu build pomiędzy wieloma pakietami w jednym repozytorium.
Od jednej usługi do wielu repozytoriów
Repozytorium rozwijającej się platformy może ostatecznie zawierać folder dla każdej usługi:
auth-service/
order-service/
payment-service/
inventory-service/
notification-service/
shipment-service/
api-gateway/
Dzielenie usług ułatwia skalowanie i określenie odpowiedzialności, ale każda z nich wymaga własnych zależności, konfiguracji, procesów build, testów, reguł lintingu oraz pipeline’u.
Koszt koordynacji w polyrepo
Pomyśl o typowym systemie e-commerce, w którym brama wejściowa obsługuje usługi autoryzacji, zamówień i produktów, usługa zamówień wywołuje funkcje płatności i zarządzania zapasami, a system płatności uruchamia powiadomienia:
API Gateway
|
┌─────────────────┼─────────────────┐
↓ ↓ ↓
Auth Order Product
|
┌────────┴────────┐
↓ ↓
Payment Inventory
|
↓
Notification
Powszechnym pierwszym krokiem jest przydzielenie każdej usłudze własnego repozytorium, czyli zastosowanie podejścia polyrepo:
github.com/company/auth-service
github.com/company/order-service
github.com/company/product-service
github.com/company/payment-service
github.com/company/inventory-service
github.com/company/notification-service
To jest prawidłowa architektura. Problemy pojawiają się, gdy zaglądamy do każdego repozytorium i widzimy, że ta sama struktura jest powtarzana wszędzie:
package.json
tsconfig.json
eslint.config.js
Dockerfile
CI configuration
README.md
node_modules
A co gorsza, te same rodzaje kodu są potrzebne kilku usługom jednocześnie:
DTOs
TypeScript types
logging utilities
configuration
authentication helpers
database utilities
Kafka/RabbitMQ abstractions
testing utilities
Każda zmiana w materiałach współdzielonych staje się wtedy zadaniem synchronizacyjnym. Gdy zmienia się wspólny typ, które repozytoria muszą zostać zaktualizowane? Gdy zmienia się zależność, które usługi wymagają aktualizacji? Gdy zmienia się reguła lint lub proces CI, ile kopii musi zostać edytowanych i utrzymanych w zgodności? Liczba usług nie jest prawdziwym problemem – problemem jest koordynacja.
Monorepo to strategia; Turborepo to narzędzie
Opcją alternatywną jest przechowywanie wszystkich aplikacji oraz współdzielonego kodu w jednym repozytorium, przy czym aplikacje znajdują się pod katalogiem apps/, biblioteki wewnętrzne pod katalogiem packages/, a konfiguracja przestrzeni roboczej i zadań w katalogu głównym:
ecommerce-platform/
│
├── apps/
│ ├── api-gateway/
│ ├── auth-service/
│ ├── order-service/
│ ├── product-service/
│ ├── payment-service/
│ ├── inventory-service/
│ └── notification-service/
│
├── packages/
│ ├── types/
│ ├── logger/
│ ├── config/
│ ├── database/
│ └── messaging/
│
├── package.json
├── pnpm-workspace.yaml
└── turbo.json
Taki układ to monorepo – decyzja dotycząca miejsca przechowywania kodu. Turborepo to narzędzie, które koordynuje pracę w takim monorepo. Można mieć jedno bez drugiego.
Czym właściwie jest Turborepo
Turborepo to system budowania dla monorepos w JavaScript i TypeScript. Koordynuje zadania istniejące w wielu pakietach jednocześnie, takie jak:
build
test
lint
type-check
dev
Bez niego budowanie kilku usług oznacza ręczne odwiedzanie każdej z nich. Najpierw usługa autoryzacji:
cd apps/auth-service
pnpm build
następnie pozostałe po kolei (fragment kodu wykonywa dwa polecenia na jednej linii; każde cd i pnpm build powinno znajdować się na osobnej linii):
cd ../order-service
pnpm buildcd ../payment-service
pnpm build
Z Turborepo deklaruje się zadania tylko raz i uruchamia jeden polecenie z katalogu głównego:
turbo build
Następnie określa, które zadania są potrzebne, które mogą być wykonywane równocześnie oraz jakie wyniki można przywrócić z pamięci cache zamiast obliczać je ponownie.
Szczegółowe ustrukturyzowanie aplikacji NestJS i wspólnych pakietów
wersja repozytorium przeznaczona do użycia w produkcji zapewnia każdej aplikacji Nest własny katalog src/ oraz plik package.json, dzięki czemu każda aplikacja stanowi odrębny pakiet przestrzeni roboczej:
ecommerce-platform/
│
├── apps/
│ │
│ ├── api-gateway/
│ │ ├── src/
│ │ └── package.json
│ │
│ ├── auth-service/
│ │ ├── src/
│ │ └── package.json
│ │
│ ├── order-service/
│ │ ├── src/
│ │ └── package.json
│ │
│ └── payment-service/
│ ├── src/
│ └── package.json
│
├── packages/
│ │
│ ├── types/
│ ├── logger/
│ ├── config/
│ ├── database/
│ └── messaging/
│
├── turbo.json
├── pnpm-workspace.yaml
└── package.json
Koncepcyjnie wyróżniają się dwie warstwy: aplikacje, które to usługi NestJS realizują funkcje biznesowe, oraz pakiety, które zawierają kod wielokrotnie używalny.
Monorepo
|
┌─────────┴─────────┐
↓ ↓
Apps Packages
| |
NestJS Shared Code
Aplikacje implementują poszczególne funkcjonalności, pakiety dostarczają wspólnych elementów budulcowych, a Turborepo koordynuje zadania pomiędzy nimi.
Pakiety wewnętrzne jako jedyny źródło prawdy
Najważniejszą codzienną zaletą monorepo są wewnętrzne pakiety. Pakiet typów może na przykład przechowywać modele, co do których zgadzają się różne usługi:
packages/
└── types/
├── src/
│ ├── user.ts
│ ├── order.ts
│ ├── payment.ts
│ └── pagination.ts
└── package.json
Każda usługa Nest może następnie importować z niego podając nazwę pakietu, która jest rozstrzygana w ramach przestrzeni roboczej, a nie rejestru:
import { OrderStatus } from '@repo/types';
Zamiast wklejać ten sam enum do każdej usługi i mieć nadzieję, że kopie nigdy się nie różnią:
enum OrderStatus {
PENDING = 'pending',
PAID = 'paid',
CANCELLED = 'cancelled',
}
Gdy enum otrzymuje nową wartość, kompilator zaznacza wszystkie dotknięte miejsca w tej samej zmianie.
Dzielenie się kontraktami to nie to samo co dzielenie się własnością
Dzielenie się jest proste w monorepo, co właśnie tłumaczy konieczność dyscypliny. Logika domenowa usługi zamówień należy do tej usługi:
order-service
↓
order-domain
Nie powinna ona po cichu przenosić się do innego pakietu:
packages/
└── order-domain/
Który następnie importują inne usługi:
payment-service
inventory-service
notification-service
Gdy kod obsługujący płatności, zapasy i powiadomienia zależy od wewnętrznych mechanizmów domeny zamówień, zmiana zasad dotyczących zamówień może zakłócić działanie trzech niepowiązanych implementacji. Zasada ogólna brzmi: świadomie dziel się infrastrukturą i umowami, ale nigdy domyślnie nie dzielić się własnością biznesową. Rozsądne kandydaty na wspólne pakiety wyglądają w ten sposób:
@repo/types
@repo/logger
@repo/config
@repo/testing
@repo/messaging
Logika biznesowa powinna normalnie znajdować się w usłudze, która nią zarządza. Aby dowiedzieć się więcej na temat tego kompromisu, zapoznaj się z artykułem duplikacja kontra powiązania: decydowanie o tym, kiedy powinien istnieć wspólny kod.
Graf zależności określa, co się uruchamia
Turborepo analizuje relacje zależności określone w pliku package.json każdego obszaru pracy. W tym przykładzie oba usługi polegają na pakiecie typów, a usługa order service zależy również od pakietu bazy danych:
@repo/types
/ \
/ \
↓ ↓
auth-service order-service
|
↓
@repo/database
Ponieważ Turborepo zna tę strukturę, jeśli edytujesz plik w usłudze order service:
apps/order-service/src/orders/order.service.ts
nie ma powodu do ponownego budowania niepowiązanej usługi, takiej jak:
notification-service
Przy filtrowaniu (na przykład turbo build --filter=...) można ograniczyć wykonywanie zadań do zmienionych pakietów i ich zależności.
Kasowanie pamięci podręcznej: pomijanie już wykonanych zadań
Pierwszy raz, gdy uruchamiasz proces budowania:
turbo build
każde wymagane zadanie faktycznie zostaje wykonywane:
auth-service → BUILD
order-service → BUILD
payment-service → BUILD
inventory-service → BUILD
Turborepo sprawdza odciski palców danych wejściowych każdego zadania (pliki źródłowe, zależności, konfiguracja, istotne zmienne środowiskowe) oraz przechowuje wyniki i logi. Uruchom ponownie ten sam polecenie bez żadnych istotnych zmian:
turbo build
a wynik będzie wyglądał w ten sposób:
auth-service → CACHE HIT
order-service → CACHE HIT
payment-service → CACHE HIT
inventory-service → CACHE HIT
Wyniki zostały przywrócone z pamięci cache. Aby to zadziałało, Turborepo musi wiedzieć, jakie pliki generuje dane zadanie – to właśnie określa ustawienie outputs w pliku turbo.json (dla budowy w Nest zazwyczaj dist/**). Zadanie bez określonych wyników może zgłaszać trafienie do pamięci cache, pozostawiając Cię bez artefaktów budowy.
Dlaczego CI czerpie największe korzyści
Naiwny pipeline powtarza całą pracę przy każdym pushu:
Git Push
↓
Install dependencies
↓
Build everything
↓
Test everything
↓
Lint everything
↓
Deploy
W połączeniu z Turborepo w procesie, każde zadanie jest najpierw sprawdzane w odniesieniu do grafu i pamięci cache, a wykonywane są tylko te zadania, których dane wejściowe uległy zmianie:
Git Push
↓
CI
↓
Turborepo
↓
┌─────────┴─────────┐
↓ ↓
Dependency Task Graph
Analysis ↓
Cache Check
↓
┌─────────┴─────────┐
↓ ↓
Cached Execute
Tasks Tasks
W dużych repozytoriach to eliminuje wiele zbędnych zadań w ramach CI.
Pamięć cache lokalna versus zdalna
Istnieją dwa poziomy cacheowania:
- Pamięć cache lokalna. Twoja własna maszyna ponownie wykorzystuje wyniki zadań, które już uruchomiła, co przyspiesza codzienną pracę nad projektem.
- Pamięć cache zdalna. Wspólna pamięć cache umożliwia programistom i maszynom CI ponowne wykorzystywanie wyników siebie nawzajem.
Ustawienie zdalne wygląda w ten sposób:
Developer A
│
↓
Remote Cache
↑
│
Developer B
↑
│
CI
Jeśli programista już stworzył usługę, a system CI później otrzyma te same dane wejściowe do zadania, system CI pobiera wynik zamiast go ponownie budować. Traktuj zdalną pamięć cache jako część swojego łańcucha dostaw: kontroluj, kto może do niej pisać, ponieważ zatruty plik mógłby zostać dostarczony wszystkim.
Wykonywanie niezależnych zadań równolegle
Część usług nie polega na wynikach budowy innych usług:
auth-service
order-service
product-service
Wykonywane sekwencyjnie, każde czeka na poprzednie:
auth → finish
↓
order → finish
↓
product
Turborepo może zamiast tego wykonywać je jednocześnie, ograniczone dostępną ilością CPU:
┌── auth ────┐
│ │
turbo ──┼── order ───┼──→ Done
│ │
└── product ─┘
Opisywanie relacji między zadaniami
To samo zarządzanie procesami dotyczy każdego typu zadania, nie tylko budowy:
turbo build
turbo test
turbo lint
turbo typecheck
Zależności są deklarowane w pliku turbo.json. W poniższej konfiguracji "^build" oznacza „najpierw zbuduj zależności tego pakietu”, więc pakiety współdzielone są kompilowane przed usługami, które je importują. Zadanie test zależy od zadania build bez znaku kierunkowego, co odnosi się do budowy tego samego pakietu. Zadania lint i typecheck nie mają żadnych zależności i mogą być uruchamiane natychmiast równolegle.
{
"tasks": {
"build": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["build"]
},
"lint": {},
"typecheck": {}
}
}
Opisujesz, w jaki sposób zadania są od siebie zależne, a Turborepo tworzy plan wykonywania. W rzeczywistym projekcie należałoby również dodać element outputs do zadań nadających się do buforowania, jak opisano powyżej, oraz upewnić się, że nazwa każdego zadania odpowiada skryptowi w plikach package.json pakietów z przestrzeni roboczej.
pnpm i Turborepo pełnią różne funkcje
To połączenie występuje tak często, że może wyglądać, jakby była to jedna narzędzie:
Turborepo + pnpm
Są to oddzielne warstwy. pnpm zarządza pakietami i przestrzeniami roboczymi: instaluje zależności, łączy wewnętrzne pakiety oraz egzekwuje ścisły układ plików node_modules. Turborepo koordynuje zadania i przechowuje ich wyniki w tych przestrzeniach roboczych.
pnpm
│
Workspace Management
│
↓
┌─────────────┐
│ Monorepo │
└──────┬──────┘
│
↓
Turborepo
│
┌───────┼────────┐
↓ ↓ ↓
Build Test Lint
Aby lepiej przyjrzeć się samemu pnpm, sprawdź npm vs pnpm: przechowywanie, szybkość i rzeczywiste kompromisy.
Dlaczego nie użyć po prostu wbudowanego trybu monorepo w Nest?
Nest CLI posiada własny tryb monorepo z aplikacjami i bibliotekami, więc dodawanie Turborepo wymaga konkretnego powodu. Różnica leży w zakresie działania – przestrzeń robocza Nest rozumie projekty typu Nest, natomiast Turborepo funkcjonuje z dowolnymi pakietami JavaScript lub TypeScript w tej przestrzeni roboczej. Gdy repozytorium zawiera już więcej niż tylko usługi typu Nest, na przykład interfejsy użytkownika oraz zwykłe procesy Node:
apps/
├── web/ # Next.js
├── admin/ # Next.js
├── api-gateway/ # NestJS
├── order-service/ # NestJS
└── worker/ # Node.js
plus wspólne pakiety UI, typów i konfiguracji:
packages/
├── ui/
├── types/
├── config/
└── eslint-config/
nie zarządzasz już projektem typu Nest, lecz platformą TypeScript, i to właśnie w tym przypadku Turborepo staje się atrakcyjne.
Turborepo, Nx czy Nest CLI
Nie istnieje jedyna słuszna opcja. Jako ogólne wskazówki: tryb monorepo w Nest to najprostsza opcja, gdy wszystko jest zrealizowane przy użyciu Nest. Turborepo stanowi stosunkowo cienką warstwę obsługi zadań i cache’owania na wierzchu przestrzeni roboczych menedżera pakietów. Nx również oferuje graf projektu oraz mechanizm cache’owania, a także szerszy zestaw pluginów, generatorów kodu i konwencji – co jedne zespoły cenią, a inne uważają za nadmiarowe. Wybieraj na podstawie swoich rzeczywistych potrzeb, a nie tego, który narzędzie przyciąga najwięcej uwagi w mediach społecznościowych.
Kiedy Turborepo jest przesadą
Załóżmy, że cały system składa się z jednej API Nest obsługiwanej przez PostgreSQL i Redis:
NestJS API
↓
PostgreSQL
↓
Redis
przy małym zespole i jednym elementie do wdrożenia:
1 application
5 developers
1 deployment
Turborepo jest prawdopodobnie niepotrzebne. Zwykłe repozytorium Nest jest prostsze, a wprowadzanie całego tego zestawu narzędzi:
Turborepo
pnpm workspace
multiple packages
complex task graph
zwiększa obciążenie poznawcze bez żadnej korzyści. Narzędzia zasługują na swoje miejsce tylko wtedy, gdy problem wymusza ich użycie.
Kiedy zaczyna się to opłacać
Sytuacja zmienia się, gdy jednocześnie zachodzą kilka z tych warunków:
Multiple applications
+
Shared packages
+
Shared tooling
+
Repeated CI tasks
+
Large TypeScript codebase
Typowym przykładem jest platforma z interfejsami Frontend w Next.js, API w Nest oraz procesorami w tle, które wszystkie korzystają z wspólnych pakietów:
Platform
│
┌──────────────┼──────────────┐
↓ ↓ ↓
Next.js NestJS Workers
Web APIs Jobs
│ │ │
└──────────────┼──────────────┘
↓
Shared Packages
Zrozwinięta struktura z jasno określonymi obowiązkami
Zaawansowana platforma Nest może dodatkowo zawierać oddzielną sekcję dla infrastruktury obok aplikacji i pakietów:
platform/
│
├── apps/
│ ├── api-gateway/
│ ├── auth-service/
│ ├── user-service/
│ ├── order-service/
│ ├── inventory-service/
│ ├── payment-service/
│ └── notification-service/
│
├── packages/
│ ├── config/
│ ├── logger/
│ ├── types/
│ ├── database/
│ ├── messaging/
│ └── testing/
│
├── infrastructure/
│ ├── docker/
│ └── deployment/
│
├── turbo.json
├── pnpm-workspace.yaml
└── package.json
Każde narzędzie ma jeden konkretny zadanie. NestJS jest frameworkiem aplikacji:
NestJS
↓
Application framework
A pozostałe warstwy są podzielone w następujący sposób (w fragmencie źródłowym kilka pozycji zostało wymienionych razem; należy to rozumieć jako pnpm do zarządzania zależnościami i przestrzeniami roboczymi, Turborepo do organizacji zadań i buforowania danych, Docker do konteneryzacji, Kafka lub RabbitMQ do komunikacji asynchronicznej oraz PostgreSQL lub Redis do przechowywania danych i buforowania):
pnpm
↓
Dependency & workspace managementTurborepo
↓
Task orchestration + cachingDocker
↓
ContainerizationKafka / RabbitMQ
↓
Asynchronous communicationPostgreSQL / Redis
↓
Data & caching
Turborepo to nie twoja infrastruktura, broker wiadomości ani platforma wdrażania. Jest to warstwa budowania i zarządzania zadaniami otaczająca monorepo.
Jeden repozytorium nie oznacza jednego wdrożenia
Częstym obawą jest to, że umieszczenie wszystkiego w jednym repozytorium poświęca niezależność, którą miały zapewnić mikrosługi. Tak nie jest – miejsce przechowywania kodu i sposób jego wdrażania to odrębne decyzje. Jeden repozytorium może zawierać wiele usług:
One Git repository
│
├── auth-service
├── order-service
├── payment-service
└── notification-service
gdy każdy z nich jest nadal dostarczany jako osobny kontener:
auth-service → Container A
order-service → Container B
payment-service → Container C
notification → Container D
i skaluje się według własnych zasad:
order-service × 10
payment-service × 5
notification × 3
Monorepo to nie monolit, a polyrepo nie zapewnia automatycznie mikrosług. Są to niezależne kategorie. Potrzebny jest CI, który wdraża tylko te usługi, których kod lub zależności uległy zmianie – co umożliwia graf zależności.
Powiązane ryzyko: powszechny pakiet, który pochłania wszystko
Latwe dzielenie się informacjami ma dwa oblicza. Często zaczyna się niewinnie od jednego pakietu:
packages/common
który stopniowo gromadzi narzędzia pomocnicze dla każdej dziedziny:
packages/common
├── AuthUtils
├── OrderUtils
├── PaymentUtils
├── InventoryUtils
├── NotificationUtils
├── DatabaseUtils
└── EverythingUtils
aż każda usługa będzie od niego zależeć. W tym momencie stworzyłeś rozproszony monolit wewnątrz monorepo: zmiana w jednym narzędziu pomocniczym zmusza do ponownego skompilowania, przetestowania i wdrożenia wszystkiego. Katalogi najwyższego poziomu nadal wyglądają uporządkowanie:
apps/
packages/
ale architektura pod spodem jest ściśle powiązana. Trzymaj się ścisłej zasady: dziel się infrastrukturą, kontraktami oraz naprawdę uniwersalnymi narzędziami; logikę domeny umieszczaj w usłudze, która nią zarządza.
Warstwowy model mentalny
Pomaga wyobrazić sobie system jako warstwy – na górze znajdują się domeny biznesowe, poniżej wspólne kontrakty i infrastruktura, a u podstaw narzędzia monorepo:
Business Domains
│
┌───────────────┼───────────────┐
↓ ↓ ↓
Order Payment Inventory
│ │ │
└───────────────┼───────────────┘
↓
Shared Contracts
│
↓
Shared Infrastructure
│
↓
Monorepo Tooling
│
↓
Turborepo
Turborepo znajduje się blisko dołu. Powinno wspierać twoją architekturę, a nie ją kształtować.
Decyzja o przyjęciu
Rozważ kolejno cztery pytania.
Czy masz więcej niż jedną aplikację?
Jeśli nie, odpowiedź jest prosta:
Keep it simple.
Jeśli tak, przechodź dalej.
Czy aplikacje dzielą się istotnymi narzędziami lub kodem?
Jeśli nie:
Polyrepo may be perfectly reasonable.
Jeśli tak:
Monorepo becomes attractive.
Czy czas budowania, testowania i sprawdzania kodu staje się drogi?
Jeśli tak:
Turborepo's orchestration and caching become valuable.
Czy uruchamiasz razem Next.js, NestJS, procesy w tle oraz wspólne pakiety TypeScript?
Jeśli tak:
Turborepo becomes a strong candidate.
Predykowalność to prawdziwa korzyść
Szybkość to efekt uboczny. Prawdziwa wartość polega na tym, że duży repozytorium staje się przewidywalne. W miarę wzrostu liczby aplikacji ktoś musi na każdą zmianę odpowiedzieć:
What changed?
oraz na serię pytań dodatkowych (w oryginale są one umieszczone na jednej linii): co należy ponownie zbudować, co należy ponownie przetestować, co może działać równolegle, co można pobrać z pamięci cache oraz które pakiety zależą od tej zmiany:
What needs to rebuild?What needs to be tested?What can run in parallel?What can come from cache?Which packages depend on this change?
Bez narzędzi inżynierowie odpowiadają na te pytania ręcznie; dzięki systemowi budowania monorepo większość odpowiedzi pochodzi bezpośrednio z grafu zależności.
Główne wnioski
Turborepo nie naprawi słabych granic usług, kiepskiego projektu bazy danych, ścisłego powiązania komponentów, nieodpowiednich umów API, niejasnej przynależności domeny czy źle zaprojektowanych mikrosług. Jego zadaniem jest utrzymanie rosnącego repozytorium w TypeScript w zrozumiałej formie. Cała struktura mieści się w jednym obrazie:
Business Architecture
│
↓
NestJS Apps
│
↓
Shared Packages
│
↓
pnpm
│
↓
Turborepo
│
┌──────────┼──────────┐
↓ ↓ ↓
Build Test Lint
│ │ │
└──────────┼──────────┘
↓
CI/CD
- Monorepo decyduje o tym, gdzie znajduje się kod; pnpm zarządza przestrzenią roboczą; Turborepo koordynuje i cacheuje zadania; NestJS buduje usługi. Trzeba zachować te role oddzielnie.
- Dzielić się abstrakcjami typów, konfiguracji, logowania i komunikacji; logikę domeny należy zachować razem z odpowiadającą jej usługą, aby uniknąć rozproszonego monolitu.
- Deklarować zależności między zadaniami za pomocą
dependsOnoraz wyniki ich wykonywania w plikuturbo.json, aby cacheowanie było zarówno skuteczne, jak i poprawne. - Struktura repozytorium oraz proces wdrażania są niezależne: każdą usługę można nadal wdrażać i skalować osobno.