An enterprise-grade distributive system built with Domain-Driven Design (DDD) & Clean Architecture
Engineered for Scability, Resilience, and Technical Excellence using Go (Golang)
Hotel Booking Microservices is a production-ready, backend-only ecosystem designed to handle the complex lifecycles of hotel reservations. This project serves as a showcase of Senior-level Backend Engineering in Go, demonstrating deep proficiency in:
- Domain-Driven Design (DDD): Managing complex business logic through Aggregates, Value Objects, and Specifications.
- Microservices Orchestration: 6 independent services communicating via a resilient API Gateway.
- Clean Architecture (Hexagonal): Strict separation of concerns to ensure maintainability and testability.
- Event-Driven Patterns: Leveraging domain events for cross-context consistency.
In a monolithic world, a failure in the notification system or a payment lag can paralyze the whole booking flow. This platform solves that through decoupled services. Each service (Auth, Hotel, Booking, Payment, Notification) is ownable, scalable, and resilient.
| Document | Description |
|---|---|
| ๐๏ธ Project Structure | Detailed breakdown of folders, layers, and key files. |
| ๐ Architecture Review | Design decisions, patterns, and architectural principles. |
| ๐บ๏ธ Entity Analysis | Database schema, entities, and business rules. |
| ๐ ER Diagram | Visual representation of database relationships. |
| ๐ Glossary | Ubiquitous Language definitions. |
| ๐ API Swagger | OpenAPI 3.0 specification. |
The system follows a Microservices Architecture with a Clean Architecture internal structure for each service.
graph TD
Client["Client (Web/Mobile)"] --> Gateway["API Gateway :8088"]
subgraph "Microservices Mesh"
Gateway --> Auth["Auth Service :8080"]
Gateway --> Hotel["Hotel Service :8081"]
Gateway --> Booking["Booking Service :8082"]
Gateway --> Payment["Payment Service :8083"]
Booking --> Hotel
Booking --> Payment
Booking --> Notification["Notification Service :8085"]
Payment --> Notification
end
Auth --> DB[(PostgreSQL)]
Hotel --> DB
Booking --> DB
Payment --> DB
Bounded contexts & responsibilities
| Service | Port | Responsibilities |
|---|---|---|
| API Gateway | 8088 | JWT verification, rate limiting, reverse proxy, booking+payment aggregation |
| Auth Service | 8080 | Register/login, password hashing (bcrypt), JWT issuing, /register /login /me |
| Hotel Service | 8081 | CRUD hotels, room types, rooms, public listing with room type summaries |
| Booking Service | 8082 | Booking lifecycle (create โ pending โ confirmed โ checked_in โ completed), cancellations, check-ins |
| Payment Service | 8083 | PaymentProvider abstraction (mock Xendit), initiation, webhook verification, refunds, booking sync |
| Notification | 8085 | Simple dispatcher (zap logger), triggered on booking creation or payment events |
The following sequence diagram illustrates the core Booking & Payment Workflow, demonstrating how services interact to complete a reservation.
sequenceDiagram
actor User
participant Gateway as API Gateway
participant Auth as Auth Service
participant Hotel as Hotel Service
participant Booking as Booking Service
participant Payment as Payment Service
participant Notif as Notification Service
%% Authentication
User->>Gateway: POST /auth/login
Gateway->>Auth: Forward Request
Auth-->>Gateway: Return JWT Token
Gateway-->>User: Access Token
%% Browsing
User->>Gateway: GET /hotels (with Token)
Gateway->>Hotel: Fetch Inventory
Hotel-->>Gateway: Hotel List
Gateway-->>User: Display Hotels
%% Booking Process
User->>Gateway: POST /bookings (RoomID, Dates)
Gateway->>Booking: Create Booking
Booking->>Hotel: Check Availability
Booking->>Booking: Calculate Price & Create Record (Pending)
Booking->>Payment: Initiate Payment
Payment-->>Booking: Payment URL/ID
Booking-->>Gateway: Booking Created (Pending)
Gateway-->>User: Payment Instructions
%% Payment Webhook (Async)
Note over Payment, Booking: User pays via Payment Gateway
Payment->>Payment: Webhook: Status = PAID
Payment->>Booking: Update Booking Status (Confirmed)
Booking->>Notif: Emit Event: BookingConfirmed
Notif-->>User: Send Email Confirmation
Flow summary
- User authenticates (JWT), then browses hotels/rooms via the gateway.
- Booking service validates availability and creates a pending booking; payment service issues an invoice URL.
- User pays through the provider; webhook validates signature, marks payment paid, and auto-confirms the booking.
- Booking emits a notification; notification service emails/logs the confirmation to the user.
- Language: Go 1.23
- Frameworks: chi router, sqlx/gorm, jwt v5, zap logger
- Database: PostgreSQL 15 (with
uuid-ossp) - Containerization: Docker + docker-compose (multi-stage builds)
- Docs: Swagger generated from handler annotations (
docs/swagger/swagger.yaml)
- Domain Events: Full event sourcing capability (
pkg/domain/events.go). - Rich Domain Models: Business logic encapsulated in Aggregates (
Booking.Confirm(),Booking.GuestCheckIn()). - Value Objects: Powerful
MoneyandDateRangetypes with validation and arithmetic. - CQRS Interfaces: Split
BookingReaderandBookingWriterrepositories. - Specification Pattern: Complex filtering logic (
pkg/domain/specification.go). - Domain Services:
PricingServicefor complex calculation logic. - Repository Factory: Abstracted repository creation.
- JWT-based authentication & role checks
- Rate limiting middleware on gateway
- Structured logging + context-aware logging helpers
- Config via environment variables (
.env.example) - Graceful shutdown using context cancellation & signal handling
- Payment provider abstraction + mock Xendit signature validation
- Consistent API error contract (DTO-based)
- Thin Handlers: DTO โ inbound assembler โ usecase (domain) โ outbound assembler โ DTO envelope.
- Domain-Centric: Usecases return domain models; mapping to DTO only happens at the handler boundary.
- Pagination: List endpoints accept
limit/offset(default limit 50) in hotel, booking, auth, notification. - Validation: Booking/Payment/Notification requests are validated in assemblers (ID/date/money/webhook signature).
Mock payment webhook signature generation:
PAYLOAD='{"payment_id":"<uuid>","status":"paid"}'
SIG=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "$PAYMENT_PROVIDER_KEY" -hex | awk '{print $2}')
curl -X POST http://localhost:8088/api/v1/payments/webhook \
-H "Content-Type: application/json" \
-d '{"payment_id":"<uuid>","status":"paid","signature":"'"$SIG"'"}'http://localhost:8088/api/v1
POST /auth/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecurePass123!",
"role": "customer" // or "admin"
}POST /auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecurePass123!"
}
Response:
{
"data": {
"access_token": "eyJhbGc...",
"refresh_token": "eyJhbGc..."
}
}GET /auth/me/{user_id}
Authorization: Bearer {token}GET /auth/users
Authorization: Bearer {admin_token}GET /auth/users/{id}
Authorization: Bearer {admin_token}GET /hotels?limit=10&offset=0GET /hotels/{hotel_id}POST /hotels
Authorization: Bearer {admin_token}
Content-Type: application/json
{
"name": "Grand Hotel",
"description": "Luxury hotel in city center",
"address": "123 Main St, Jakarta"
}PUT /hotels/{hotel_id}
Authorization: Bearer {admin_token}
Content-Type: application/json
{
"name": "Updated Grand Hotel",
"description": "Updated description",
"address": "Updated address"
}DELETE /hotels/{hotel_id}
Authorization: Bearer {admin_token}GET /room-types?limit=10&offset=0POST /room-types
Authorization: Bearer {admin_token}
Content-Type: application/json
{
"hotel_id": "{hotel_id}",
"name": "Deluxe Suite",
"capacity": 2,
"base_price": 1500000,
"amenities": "WiFi, TV, AC, Minibar"
}GET /rooms?limit=10&offset=0GET /rooms/{room_id}POST /rooms
Authorization: Bearer {admin_token}
Content-Type: application/json
{
"room_type_id": "{room_type_id}",
"number": "101",
"status": "available" // available, maintenance, occupied
}PUT /rooms/{room_id}
Authorization: Bearer {admin_token}
Content-Type: application/json
{
"number": "102",
"status": "maintenance"
}DELETE /rooms/{room_id}
Authorization: Bearer {admin_token}POST /bookings
Authorization: Bearer {token}
Content-Type: application/json
{
"room_type_id": "{room_type_id}",
"check_in": "2025-12-01",
"check_out": "2025-12-05"
}GET /bookings?limit=10&offset=0
Authorization: Bearer {token}GET /bookings/{booking_id}
Authorization: Bearer {token}POST /bookings/{booking_id}/cancel
Authorization: Bearer {token}GET /bookings/{booking_id}/status
Authorization: Bearer {token}POST /bookings/{booking_id}/status
Authorization: Bearer {admin_token}
Content-Type: application/json
{
"status": "confirmed"
}POST /bookings/{booking_id}/checkpoint
Authorization: Bearer {token}
Content-Type: application/json
{
"action": "check_in" // or other supported actions
}GET /payments/{payment_id}
Authorization: Bearer {admin_token}GET /payments/by-booking/{booking_id}
Authorization: Bearer {token}POST /payments/webhook
Content-Type: application/json
{
"payment_id": "{payment_id}",
"status": "paid",
"signature": "{hmac_signature}"
}POST /payments/refund
Authorization: Bearer {admin_token}
Content-Type: application/json
{
"payment_id": "{payment_id}",
"reason": "Customer request"
}POST /notifications
Authorization: Bearer {token}
Content-Type: application/json
{
"type": "booking_confirmed",
"target": "user@example.com",
"message": "Your booking has been confirmed"
}GET /notifications?limit=10&offset=0
Authorization: Bearer {token}GET /notifications/{notification_id}
Authorization: Bearer {token}GET /gateway/aggregate/bookings/{booking_id}
Authorization: Bearer {token}
Response: Combined data from booking, payment, and hotel services- Requires Authentication = JWT Bearer Token
- ๐ Admin Only = Requires
role: "admin"in JWT claims
- Trigger: Automatic CronJob (daily at 10:00 AM)
- Process: Bookings with
checkout_date = todayANDstatus = checked_inare automatically transitioned tocompleted - No API Call Required: Fully automated background process
cmd/<service>/ # each service entry point (auth-service, booking-service, etc.)
internal/
domain/<bounded> # entities & repository interfaces
usecase/<bounded> # core business logic
infrastructure/ # http handlers, repositories, provider clients, gateway logic
usecase/<bounded>/assembler # inbound/outbound mapping DTO <-> domain
pkg/ # shared libs (config, dto, middleware, logger, etc.)
migrations/001_init.sql # SQL schema seed
build/<service>/Dockerfile
docs/ # Swagger + ERD
- Docker Desktop / Docker Engine 24+
- Docker Compose V2
- Go 1.23+ (only needed for local dev/tests)
swagCLI (optional):go install github.com/swaggo/swag/cmd/swag@latest
Copy .env.example to .env:
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgres://... |
Shared Postgres DSN |
JWT_SECRET |
super-secret |
JWT signing secret |
PAYMENT_PROVIDER_KEY |
sandbox-key |
HMAC key for mock Xendit |
RATE_LIMIT_PER_MINUTE |
120 |
Gateway rate limiter |
-
Spin up services
make run # OR docker-compose up --build- API Gateway:
http://localhost:8088/gateway - Auth:
http://localhost:8080 - Hotel:
http://localhost:8081 - Booking:
http://localhost:8082 - Payment:
http://localhost:8083 - Notification:
http://localhost:8085 - PostgreSQL:
localhost:5432 - Adminer UI:
http://localhost:8089
- API Gateway:
-
Shutdown & cleanup
make down # OR docker-compose down -v
- Schema:
migrations/001_init.sqlcreates tables (users,hotels,room_types,rooms,bookings,payments,refunds,checkins). - UUIDs: All keys use UUID (requires
CREATE EXTENSION "uuid-ossp"). - Seeding: Run
migrations/002_seed_data.sqlmanually via Adminer to populate initial data.
-
Generate docs
make swagger
Produces
docs/swagger/swagger.yaml&docs/swagger/swagger.json. -
Serve via swagger-ui (optional)
docker run --rm -p 8087:8080 \ -e SWAGGER_JSON=/app/swagger.yaml \ -v ${PWD}/docs/swagger/swagger.yaml:/app/swagger.yaml \ swaggerapi/swagger-uiVisit
http://localhost:8087.
Swagger covers:
- Auth
/register /login /me/{id} - Hotel
/hotels /room-types /rooms(including full CRUD operations)GET /hotels- List all hotelsPOST /hotels- Create hotel (admin)GET /hotels/{id}- Get hotel by IDPUT /hotels/{id}- Update hotel (admin)DELETE /hotels/{id}- Delete hotel (admin)GET /rooms- List all roomsPOST /rooms- Create room (admin)GET /rooms/{id}- Get room by IDPUT /rooms/{id}- Update room (admin)DELETE /rooms/{id}- Delete room (admin)
- Booking
/bookings, cancellation, checkpoint- Auto-checkout via CronJob (daily at 10:00 AM)
- Payment
/payments,/payments/webhook - Notification
/notifications - Gateway
/gateway/aggregate/bookings/{id}
- Scheduler: Runs daily at 10:00 AM (configurable via cron expression)
- Process:
- Finds all bookings with
checkout_date = todayANDstatus = checked_in - Automatically transitions them to
completedstatus - Publishes domain events for notification
- Finds all bookings with
- Configuration: Implemented in
booking-serviceusingrobfig/cron/v3 - Graceful Shutdown: Scheduler stops cleanly when service terminates
make test # go test ./... -cover
make lint # go vet ./...Covered scenarios:
- Booking creation: happy path, invalid dates, room type not found.
- Payment webhook: valid/invalid signature propagation.
- Refund flows: provider success/fail.
- Booking repository insert via sqlmock.
POST /auth/register: Email normalized, password hashed (bcrypt), role assigned.POST /auth/login: Returnsaccess_token+refresh_token.- Protected requests: Gateway checks
Authorization: Bearer <token>.
- Admin Operations (requires JWT with admin role):
- Create, update, and delete hotels
- Create, update, and delete rooms
- Manage room types
- Public Operations (no auth required):
- List hotels and room types
- Get hotel details by ID
- Get room details by ID
- Soft Delete: Delete operations use soft delete (data retained with deleted_at timestamp)
- Availability: Booking service calls Hotel service for stock validation.
POST /bookings: Validates dates/availability, calculates price, sets statuspending_payment.PATCH /bookings/{id}/cancel: Allowed only while pending; blocked after confirmed/checked_in/completed.POST /bookings/{id}/checkin: Transitions tochecked_in.
POST /payments: Initiates payment via mock provider, returns URL.POST /payments/webhook: Validates HMAC, updates payment statuspaid, auto-confirms booking.POST /payments/refund: Records refund, updates status.
- Triggered by Booking Confirmed or Payment Paid events.
- Logs payload (placeholder for email/SMS).
Only used to simulate payment webhook (other endpoints are covered in the tester reference above).
Bash
PAYLOAD='{"payment_id":"<payment_uuid>","status":"paid"}'
SIG=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "$PAYMENT_PROVIDER_KEY" -hex | awk '{print $2}')
curl -X POST http://localhost:8088/api/v1/payments/webhook \
-H "Content-Type: application/json" \
-d "{\"payment_id\":\"<payment_uuid>\",\"status\":\"paid\",\"signature\":\"$SIG\"}"PowerShell
$payload = '{"payment_id":"<payment_uuid>","status":"paid"}'
$key = $env:PAYMENT_PROVIDER_KEY
$hmac = [System.Security.Cryptography.HMACSHA256]::new([Text.Encoding]::UTF8.GetBytes($key))
$bytes = $hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($payload))
$sig = -join ($bytes | ForEach-Object { $_.ToString('x2') })
curl -X POST http://localhost:8088/api/v1/payments/webhook `
-H "Content-Type: application/json" `
-d "{`"payment_id`":`"<payment_uuid>`",`"status`":`"paid`",`"signature`":`"$sig`"}"Note: use the same
PAYMENT_PROVIDER_KEYas the service (defaultsandbox-key).
| Command | Description |
|---|---|
make run |
docker-compose up --build |
make down |
docker-compose down -v |
make test |
go test ./... -cover |
make lint |
go vet ./... |
make swagger |
Generate Swagger docs under docs/swagger/ |
- Whitelist vs Proxy All: Toggle with
GATEWAY_MODE=whitelist|proxy_all(defaultwhitelist). - Route Map: Loaded from
config/routes.yml. - Features:
- Path rewrite/strip-prefix.
- Auth forwarding/validation.
- Circuit breaker & Health checks.
- Observability (
/metrics,/debug/routes).
- Payment Provider: Implement real Midtrans/Xendit in
domain.Provider. - Notifications: Replace logger with SMTP/Twilio in
domain.Dispatcher. - Caching: Add Redis for hotel search.
- CI/CD: Setup GitHub Actions for
make test. - New Contexts: Add Inventory or Pricing microservices.
Fitry Yuliani