A Go microservices project demonstrating the two-phase commit (2PC) protocol for distributed transaction coordination across three services, each with its own PostgreSQL database.
| Service | Port | Database | Role |
|---|---|---|---|
| Order Service | 8081 | orderdb |
Coordinator — orchestrates the 2PC protocol |
| Delivery Service | 8082 | deliverydb |
Manages delivery agents (reserve/assign) |
| Store Service | 8083 | storedb |
Manages food packets (reserve/assign) |
When a user creates an order (POST /orders/create):
- Insert order with
PENDINGstatus POST /foods/reserve— Store Service locks an available food packet (is_reserved = true)POST /agents/reserve— Delivery Service locks an available delivery agent (is_reserved = true)
POST /foods/assign— Store Service assigns the reserved packet to the order (order_id = X)POST /agents/assign— Delivery Service assigns the reserved agent to the order (order_id = X)
User POST /orders/create {user_id: 1, food_id: 1}
│
├─ Insert into orders (status = PENDING)
│
├─ PHASE 1: PREPARE
│ ├─ POST /foods/reserve
│ │ └─ Store locks packet, sets is_reserved=true
│ └─ POST /agents/reserve
│ └─ Delivery locks agent, sets is_reserved=true
│
├─ All Phase 1 responses successful?
│ ├─ NO → Return error (automatic transaction rollback)
│ └─ YES → Proceed to Phase 2
│
├─ PHASE 2: COMMIT
│ ├─ POST /foods/assign
│ │ └─ Store sets packet.order_id = orderID
│ └─ POST /agents/assign
│ └─ Delivery sets delivery.order_id = orderID
│
└─ Return order_id to user
- Each service uses
SELECT ... FOR UPDATErow-level locks within database transactions to prevent race conditions. - This ensures that when two concurrent requests try to reserve the same resource, one blocks until the other completes.
- The current implementation lacks explicit rollback/abort calls.
- If a Phase 2 operation fails (e.g., food assign succeeds but delivery assign fails), there is no compensation logic to undo the partial commit.