LOCAL_DEV_SETUP — reservation-service
Sibling: DEPLOYMENT_TOPOLOGY · TESTING_STRATEGY · DATA_MODEL
Strategic anchors: 02 §16 Local Development
This guide brings up reservation-service on a developer workstation with all dependencies running locally — Postgres, Pub/Sub emulator, mock payment provider, mock lock adapter — and seed data sufficient to exercise the full booking saga end-to-end.
1. Prerequisites
| Tool | Version |
|---|---|
| Node.js | 20 LTS |
| pnpm | 9.x |
| Docker Desktop | 4.x (Linux containers) |
| Google Cloud SDK | latest (for the Pub/Sub emulator) |
| Java | 17+ (Pub/Sub emulator runtime) |
| GNU make | 4.x |
The platform monorepo bundles a make bootstrap that installs Node deps, builds shared packages (@ghasi/shared, @ghasi/domain-primitives, @ghasi/telemetry), and verifies tool versions.
2. Bring up the local stack
cd services/reservation-service
cp .env.example .env
make local-up
make local-up runs docker compose up -d against docker-compose.local.yml which starts:
| Service | Image | Port | Notes |
|---|---|---|---|
| Postgres 15 | postgres:15-alpine | 5432 | melmastoon_local database; default schema reservation |
| Redis 7 | redis:7-alpine | 6379 | hot-cache substitute for Memorystore |
| Pub/Sub emulator | gcr.io/google.com/cloudsdktool/cloud-sdk:emulators | 8085 | PUBSUB_EMULATOR_HOST=localhost:8085 |
| Mock payment provider | melmastoon-platform/mock-payment:dev | 7100 | matches payment-gateway-service webhook contract |
| Mock lock adapter | melmastoon-platform/mock-lock:dev | 7200 | matches lock-integration-service event contract |
| Mock notification | melmastoon-platform/mock-notification:dev | 7300 | sinks confirmation/cancel emails to a local file |
| OpenTelemetry collector | otel/opentelemetry-collector:latest | 4317 | optional; exports to local Jaeger if you also start it |
After containers start, run migrations and seed:
pnpm drizzle-kit push # apply migrations to local Postgres
pnpm seed:local # insert seed data (see §4)
pnpm dev # start NestJS in watch mode on :8080
The hold-expiry worker can be run separately:
pnpm dev:hold-expiry-worker
3. Environment variables (.env.example)
NODE_ENV=development
PORT=8080
LOG_LEVEL=debug
# Postgres
DATABASE_URL=postgres://melmastoon:melmastoon@localhost:5432/melmastoon_local
DATABASE_SCHEMA=reservation
DATABASE_POOL_SIZE=10
# Redis (Memorystore substitute)
REDIS_URL=redis://localhost:6379
# Pub/Sub emulator
PUBSUB_EMULATOR_HOST=localhost:8085
GOOGLE_CLOUD_PROJECT=melmastoon-local
PUBSUB_TOPIC_PREFIX=melmastoon
PUBSUB_DLQ_SUFFIX=-dlq
# Mock service URLs (called via the platform port adapters)
PRICING_BASE_URL=http://localhost:7050
INVENTORY_BASE_URL=http://localhost:7060
PAYMENT_BASE_URL=http://localhost:7100
LOCK_BASE_URL=http://localhost:7200
NOTIFICATION_BASE_URL=http://localhost:7300
AI_ORCHESTRATOR_BASE_URL=http://localhost:7400 # mock returns canned anomaly + parser responses
# Tenancy
DEFAULT_TENANT_HOLD_TTL_SECONDS=600
DEFAULT_TENANT_NO_SHOW_GRACE_HOURS=2
# OTel
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
OTEL_SERVICE_NAME=reservation-service
Never commit a real .env; only .env.example is in version control.
4. Seed data
pnpm seed:local inserts a reproducible scenario:
| Entity | Count | Notes |
|---|---|---|
| Tenants | 2 | tnt_local_af (Afghan tenant, AFN base, USD billing) and tnt_local_ir (Iran tenant, IRR base, IRR billing with FX snapshot) |
| Properties | 3 | ppt_local_kbl_grand (Kabul, 12 rooms), ppt_local_thr_lake (Tehran, 8 rooms), ppt_local_dyu_central (Dushanbe, 6 rooms) |
| Room types | 6 | Suite, Suite Plus, Twin, Family, Single, Apartment |
| Rooms | 26 | distributed across the three properties; each carries a vendor hint for the mock lock adapter (ttlock, salto, wiegand) |
| Rate plans | 4 | rate_local_BAR, rate_local_NREF, rate_local_CASH_FRIENDLY, rate_local_CORP_ACME |
| Sample guests | 6 | Mixed scripts: ps-AF, fa-IR, tg-TJ, en-US, ar-SA; one guest carries a national_id last4, others passport |
| Sample reservations | 5 | one per status to exercise UI: held (with active TTL), confirmed (future stay), checked_in (in-house), checked_out (last week), cancelled (last month) |
The seed script also publishes the corresponding events to the Pub/Sub emulator so projections downstream can be exercised.
5. Common commands
| Task | Command |
|---|---|
| Run all tests | pnpm test |
| Run unit tests only | pnpm test:unit |
| Run integration tests (Testcontainers) | pnpm test:integration |
| Run the mandatory three | pnpm test:integration -- tenant-isolation outbox inbox |
| Generate OpenAPI from controllers | pnpm openapi:gen (writes openapi.json) |
| Generate Drizzle migration from schema | pnpm db:gen "<name>" |
| Apply migrations to local DB | pnpm db:push |
| Tail mock notification sink | tail -f docker/mock-notification/outbox.log |
| Replay an event into the local emulator | pnpm pubsub:replay <subject> <fixture-file.json> |
6. Mock service behavior
- mock-payment (
localhost:7100): acceptsPOST /intents, immediately calls back withpayment.transaction.captured.v1500 ms later for cards. Cash-on-arrival returnspending_cashand only emitscapturedwhen the developer hitsPOST /sim/cash-paid/:intentId. Configurable failure modes via?failure=timeout|declined|webhook_lost. - mock-lock (
localhost:7200): emitslock.key.issued.v1200 ms after request; emitslock.key.failed.v1if the room id contains_fail. The sandbox runbook in 09 §6 describes the simulator UI. - mock-notification (
localhost:7300): writes every outbound message tooutbox.loginstead of sending; surfaces a small UI atlocalhost:7301for visual verification. - mock-ai-orchestrator (
localhost:7400): returns canned anomaly verdicts (band=low), tag arrays for special requests, and identity transliteration; supports?force=high_riskfor fraud-flow testing.
7. End-to-end booking dry run
# 1. Get a quote
curl -s -X POST http://localhost:8080/api/v1/reservations/quotes \
-H "Authorization: Bearer dev-staff-token" \
-H "X-Tenant-Id: tnt_local_af" \
-H "Idempotency-Key: 01J0000000000000000000QUOTE" \
-H "Content-Type: application/json" \
-d @fixtures/quote-request.json | jq .
# 2. Place a hold
curl -s -X POST http://localhost:8080/api/v1/reservations/holds \
-H "Authorization: Bearer dev-staff-token" \
-H "X-Tenant-Id: tnt_local_af" \
-H "Idempotency-Key: 01J0000000000000000000HOLD" \
-H "Content-Type: application/json" \
-d @fixtures/hold-request.json | jq .
# 3. Mock payment fires; reservation transitions to confirmed automatically.
# 4. Inspect:
curl -s http://localhost:8080/api/v1/reservations/<rsv_id> \
-H "Authorization: Bearer dev-staff-token" \
-H "X-Tenant-Id: tnt_local_af" | jq .
fixtures/ holds canonical request bodies (quote, hold, walk-in, modify date, cancel, check-in, check-out) ready to use.
8. Troubleshooting
PUBSUB_EMULATOR_HOST not honored— confirm the env var is exported in the shell wherepnpm devruns; the SDK reads it at module load.- Migrations fail with RLS error — connect as
melmastoon_admin(which hasBYPASSRLSfor migrations); the application role does not. - Hold expiry not firing — start the worker (
pnpm dev:hold-expiry-worker); it does not auto-start with the API. - Mock lock returns
failed— check theroomIdyou passed; remove_failsuffix to get the success path.
9. Cross-references
- Test commands & expectations: TESTING_STRATEGY
- Mock-lock simulator design: 09 §6
- Mock-payment sandbox: 10 §17