Membangun fondasi projek tokokita: membandingkan monorepo dan polyrepo, menyusun struktur folder untuk tujuh layanan plus shared packages, lalu menjalankan environment dev PostgreSQL, Redis, dan Kafka via Docker Compose

Setelah di episode 2 kita menetapkan empat pilar arsitektur — bounded context, database per service, API gateway, dan dua gaya komunikasi — pada episode ini kita mulai membangun tokokita secara nyata. Kali ini kita akan menentukan bagaimana projek diorganisasi: satu repository (monorepo) atau banyak repository (polyrepo), lalu menyusun struktur foldernya dan menghidupkan infrastruktur dev.
Mengapa keputusan struktur projek penting? Karena di microservices, cara kode diorganisasi secara langsung memengaruhi kecepatan kolaborasi tim dan kemudahan berbagi kode antar layanan. Pilihan yang salah akan membuat berbagi schema validasi atau helper database menjadi mimpi buruk.
Semua layanan dan package ditaruh dalam satu repository dengan workspace manager (pnpm/Bun). Keunggulannya:
shared-types langsung dipakai semua service tanpa publish registry.lint, tsconfig, dan tooling seragam.Kekurangannya: repo menjadi besar, dan akses/permission per-service tidak bisa dipisah (buntu di GitHub).
Setiap layanan punya repository dan CI-nya sendiri. Keunggulan: tim benar-benar independen, permission terisolasi. Kekurangan: berbagi kode harus via package registry, perubahan menyebar ke banyak repo, dan konsistensi tooling sulit dipertahankan.
Untuk series belajar ini kita memilih monorepo dengan Bun workspaces — kemudahan berbagi shared library jauh lebih berharga daripada isolasi tim. Di produksi, tim 3+ boleh mempertimbangkan polyrepo per tim; topik ini kembali di episode 23.
# daftarkan packages/* dan services/* sebagai workspace
[workspaces]
packages = ["packages/*", "services/*", "apps/*"]Berikut struktur tokokita yang akan kita bangun:
tokokita/
apps/
web-app/ # React + Vite (frontend)
api-gateway/ # BFF API
services/
auth-service/
product-service/
cart-service/
order-service/
payment-service/
notification-service/
packages/
shared-types/ # DTO & zod schemas
client-db/ # Drizzle schema helpers
infra/
compose.yaml
k8s/Setiap service dan app memiliki package.json sendiri dengan "name": "auth-service" dst., sehingga Bun workspace otomatis mengenali semuanya dan mengelola dependensi bersama.
shared-types: Zod Schema untuk ValidasiSemua kontrak API (request body, response, event payload) didefinisikan sekali pakai zod schema sehingga klien dan server memvalidasi dengan skema yang sama.
import { z } from 'zod'
export const registerRequestSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(1),
})
export const userCreatedEventSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string(),
occurredAt: z.string().datetime(),
})
export type RegisterRequest = z.infer<typeof registerRequestSchema>
export type UserCreatedEvent = z.infer<typeof userCreatedEventSchema>client-db: Helper Koneksi DatabaseHelper koneksi PostgreSQL (pakai drizzle-orm atau postgres.js) agar semua service memakai cara koneksi dan retry yang konsisten.
import postgres from 'postgres'
export function createDb(url: string) {
return postgres(url, {
max: 10,
idle_timeout: 20,
connect_timeout: 10,
})
}Kedua package dipakai oleh service lewat import biasa: import { registerRequestSchema } from '@tokokita/shared-types'.
Kita jalankan tiga dependency utama lewat compose — PostgreSQL, Redis, dan Redpanda (drop-in Kafka).
services:
postgres:
image: postgres:16
environment:
POSTGRES_USER: tokokita
POSTGRES_PASSWORD: tokokita
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U tokokita"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
redpanda:
image: redpandadata/redpanda:latest
command: redpanda start \
--overprovisioned \
--smp 1 \
--memory 1G \
--node-id 0 \
--kafka-addr internal://0.0.0.0:9092 \
--advertise-kafka-addr internal://redpanda:9092
ports:
- "9092:9092"
- "9644:9644"
volumes:
pgdata:Jalankan dan verifikasi:
docker compose -f infra/compose.yaml up -d
docker compose ps
pg_isready -h localhost -p 5432Catatan: redpanda console (UI broker) bisa ditambahkan di port 8080 — kita pakai port 9644 untuk admin API. Detail jaringan antar container dan best practice compose dibahas penuh di episode 16.
Setiap service kemudian dijalankan terpisah di terminalnya sendiri (atau pakai bun --cwd services/auth-service dev):
bun --cwd services/auth-service run dev
bun --cwd services/product-service run dev
bun --cwd services/api-gateway run devVersi produksi memakai bun run dengan bun build untuk bundling, tapi untuk development mode watch cukup.
Note
Jangan tulis database, Redis, dan Kafka dalam satu container semua service. Dependency infrastruktur boleh berbagi di local dev, tetapi tidak ada layanan yang berbagi database. Di episode 4, auth-service akan memiliki schema users sendiri yang tidak di-sentuh service lain.
Episode 3 membangun fondasi projek tokokita:
apps/ (web-app, api-gateway), services/ (enam layanan), packages/ (shared-types, client-db), infra/.shared-types) dan helper DB (client-db).bun run dev.Di episode 4 selanjutnya, kita akan membangun auth-service — register, login, JWT access token + refresh token rotating, logout, dan ganti password, lengkap dengan integrasi Redis untuk blacklist serta emit event user.registered ke event bus. Sampai jumpa di episode 4!