Skip to content

feat: Implement order service (cart + checkout) - #5

Merged
rezadrian01 merged 4 commits into
mainfrom
feature/order-service
May 28, 2026
Merged

feat: Implement order service (cart + checkout)#5
rezadrian01 merged 4 commits into
mainfrom
feature/order-service

Conversation

@rezadrian01

Copy link
Copy Markdown
Owner

Summary

  • Implement complete order service (port 8083) with cart management and order checkout flow
  • Fix Docker Compose DATABASE_URL ports for all services (host port → container port 5432)
  • Add PRODUCT_SERVICE_URL env var to order-service; provision Kafka topics for order events

What's included

Domain layer (internal/domain/)

  • Cart, CartItem, Order, OrderItem entities with GORM tags and ToResponse() helpers
  • OrderStatus type with Cancellable() helper (blocks cancel on shipped/delivered)
  • Full set of interfaces: CartRepository, OrderRepository, CartService, OrderService, CartCache, OrderCache, EventPublisher, ProductClient
  • ProductSnapshot struct — price and name are snapshotted at cart-add time so historical orders are unaffected by product edits

DB migrations (db/)

  • 001_create_carts.up.sqlcarts (UNIQUE on user_id) + cart_items with CHECK constraint on quantity
  • 002_create_orders.up.sqlorders with CHECK constraint on status enum + order_items

Repository layer (internal/repository/)

  • CartRepository — full CRUD for carts and items; ClearCart deletes all items by cart_id
  • OrderRepositoryCreateOrder uses an explicit transaction (inserts order then sets FK on each item); UpdateOrderStatus does a targeted column update then re-fetches

Cache layer (internal/cache/)

  • CartCache — key cart:<userID>, 24 h TTL
  • OrderCache — order detail order:<id> (1 h TTL), order list orders:user:<uid>:page:<n>:limit:<l> (5 min TTL); InvalidateOrderList scans by user prefix

Events (internal/events/)

  • Kafka publisher for order.created, order.updated, order.cancelled; all publishes are fire-and-forget goroutines so Kafka unavailability never blocks HTTP responses

Product HTTP client (internal/client/)

  • Calls GET {PRODUCT_SERVICE_URL}/products/{id} with 5 s timeout
  • Returns ErrProductInactive immediately if is_active == false — prevents snapshotting a dead product into the cart

Service layer (internal/service/)

  • CartService — auto-creates cart on first access; AddItem merges quantity if product already in cart and refreshes price snapshot; UpdateItem/RemoveItem verify item ownership (item.CartID == cart.ID) before touching anything
  • OrderServiceCreateOrder builds order items from cart snapshots in one transaction, clears cart after checkout; CancelOrder checks Status.Cancellable() before proceeding

Handler + Route layers (internal/handler/, internal/route/)

  • 8 endpoints matching gateway routes exactly (4 cart, 4 order)
  • User identity read from X-User-ID header injected by the gateway proxy
  • Typed error mapping to HTTP status codes

Bootstrap (cmd/, main.go, Dockerfile)

  • Same pattern as product-service: config, dotenv loader, GORM AutoMigrate, Redis ping-on-startup, Kafka writer setup with ensureTopics, full dependency wiring, graceful shutdown

docker-compose fix

  • products-db, orders-db, payments-db, inventory-service were all using the host-mapped port in their DATABASE_URL — inside Docker containers communicate on the container port (5432), not the host port. Fixed across all four affected services.

Test plan

  • go build ./... passes in services/order-service/
  • go vet ./... passes with zero warnings
  • docker compose up orders-db redis kafka order-service starts and /health returns 200
  • POST /api/cart/items with a valid product UUID adds item and returns cart with correct price snapshot
  • POST /api/orders with shipping details creates order, clears cart, returns pending status
  • PUT /api/orders/:id/cancel on a pending order returns cancelled; on a shipped order returns 409
  • Kafka topic order.created receives a message after checkout

🤖 Generated with Claude Code

rezadrian01 and others added 4 commits May 28, 2026 10:59
- Domain: Cart, CartItem, Order, OrderItem entities with GORM tags and ToResponse helpers
- Domain interfaces: CartRepository, OrderRepository, CartService, OrderService, CartCache, OrderCache, EventPublisher, ProductClient
- DB migrations: 001_create_carts.up.sql, 002_create_orders.up.sql with indexes and CHECK constraints
- Repository: CartRepository and OrderRepository backed by GORM; CreateOrder uses explicit transaction to insert order then items atomically

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dler layers

- Cache: CartCache (24h TTL) and OrderCache (1h detail / 5min list TTL) backed by Redis; InvalidateOrderList scans by user prefix
- Events: Kafka publisher for order.created, order.updated, order.cancelled topics
- Client: HTTP ProductClient fetches product snapshot from product-service; returns ErrProductInactive if product is disabled
- Service: CartService auto-creates cart on first access, merges quantities on duplicate add, enforces item ownership on update/remove; OrderService builds order from cart snapshots in a single transaction, clears cart after checkout, publishes events asynchronously
- Handler + Route: 8 endpoints (GET/POST/PUT/DELETE cart, GET/POST/GET/PUT orders); user identity read from X-User-ID header injected by gateway

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… docker-compose wiring

- cmd: config (PORT/DATABASE_URL/REDIS_URL/KAFKA_BROKERS/PRODUCT_SERVICE_URL), dotenv loader, GORM+Redis infrastructure setup, Kafka publisher bootstrap with ensureTopics, dependency wiring in Run(), graceful shutdown (DB/Redis/Kafka)
- main.go: single-line entry point calling cmd.Run()
- Dockerfile: multi-stage build (golang:1.25-alpine → alpine:3.18), port 8083
- .env / .env.example: local dev defaults
- docker-compose: added REDIS_URL, KAFKA_BROKERS, PRODUCT_SERVICE_URL to order-service environment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… networking

In Docker Compose, service-to-service communication uses the container's internal
port (5432 for PostgreSQL), not the host-side mapped port. The ports directive
(e.g. "5433:5432") only affects host access — all services in the same network
must connect on 5432.

Affected services: product-service, order-service, payment-service, inventory-service

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 28, 2026 04:17
@rezadrian01
rezadrian01 merged commit 96936d2 into main May 28, 2026
1 of 2 checks passed
@rezadrian01
rezadrian01 removed the request for review from Copilot May 28, 2026 04:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant