🇬🇧 Read the English version of this article on tanhdev.com
Bóc Tách Hệ Sinh Thái: Chi Tiết 21 Microservices Theo 6 Domain DDD
“Tại sao một hệ thống thương mại điện tử lại cần tới 21 microservices độc lập? Như vậy chẳng phải là tự làm phức tạp hóa vấn đề (overkill) hay sao?”
Đây là câu hỏi quen thuộc mà bất kỳ Kỹ sư Trưởng (Lead Architect) nào cũng gặp phải khi đề xuất tái cấu trúc một hệ thống nguyên khối (Monolith) đang trên đà sụp đổ do quá tải. Câu trả lời mang tính quy luật của kỹ thuật phần mềm chính là: Định luật Conway (Conway’s Law) — Hệ thống phần mềm được thiết kế ra luôn phản chiếu cấu trúc giao tiếp của tổ chức xây dựng nên nó.
Khi một công ty thương mại điện tử tăng trưởng quy mô trên 40 kỹ sư được chia thành nhiều biệt đội (squads), việc tất cả cùng đẩy mã nguồn vào một repository và cùng ghi vào một cơ sở dữ liệu chung sẽ dẫn tới tình trạng tê liệt: Xung đột merge code hàng ngày, một câu lệnh SQL chưa tối ưu của team Báo cáo làm khóa bảng sales_order của team Thanh toán, và mỗi lần release là một cuộc đánh cược rủi ro nghẹt thở.
Bằng cách áp dụng nghiêm ngặt phương pháp Thiết Kế Hướng Miền (Domain-Driven Design - DDD), chúng tôi đã phân rã toàn bộ hệ sinh thái thương mại điện tử thành 6 Miền Nghiệp Vụ (Bounded Domains) có tính gắn kết nội bộ cao (High Cohesion) nhưng ghép nối lỏng lẻo với bên ngoài (Loose Coupling). Mỗi dịch vụ sở hữu cơ sở dữ liệu riêng biệt và chỉ giao tiếp qua hợp đồng gRPC Protobuf hoặc Sự kiện bất đồng bộ CloudEvents.
⚡ Tóm Tắt Kiến Trúc Cốt Lõi (Executive BLUF)
- Nguyên tắc phân ranh giới ngữ cảnh (Bounded Contexts): 6 domain cốt lõi gồm Commerce Flow, Product & Content, Logistics & WMS, Post-Purchase, Identity & Access, Platform Operations.
- Cách ly dữ liệu tuyệt đối (Database Isolation): 21 dịch vụ sở hữu 21 schema/database độc lập. Nghiêm cấm 100% các câu truy vấn JOIN chéo cơ sở dữ liệu; trao đổi dữ liệu chỉ diễn ra qua gRPC APIs hoặc Kafka/Dapr Event Mesh.
- Chống Overselling bằng Optimistic Concurrency Control (OCC): Warehouse Service sử dụng trường
versiontăng dần kết hợp ràng buộc cơ sở dữ liệuCHECK (available_qty >= 0), triệt tiêu hoàn toàn nguy cơ bán lố hàng tồn kho trong sự kiện Flash Sale.- Bảo toàn giao dịch bằng Saga Choreography: Luồng thanh toán liên-domain tự động thực thi các giao dịch bù trừ (Compensating Transactions) khi cổng thanh toán hoặc kiểm tra gian lận từ chối giao dịch.
1. So Sánh Định Lượng: Monolithic Database vs Database-per-Service
Bảng phân tích đo lường dưới đây thể hiện sự vượt trội về độ tin cậy và khả năng chịu tải khi chuyển đổi từ một cơ sở dữ liệu MySQL tập trung sang mô hình phân lập cơ sở dữ liệu độc lập:
| Chỉ Số Đánh Giá | Monolithic Shared Database (MySQL EAV) | Database-per-Service (21 Go Microservices) |
|---|---|---|
| Bán Kính Ảnh Hưởng Lỗi (Blast Radius) | 100% (Một deadlock ở bảng Review làm treo Checkout) | < 5% (Sự cố tại một service bị cô lập hoàn toàn) |
| Tranh Chấp Khóa (Lock Contention IOPS) | Rất cao (> 8.500 lock waits/phút vào giờ cao điểm) | Xấp xỉ 0 (Mỗi service chỉ thao tác trên bảng riêng) |
| Chi Phí Mở Rộng (Scaling Efficiency) | Kém: Phải nâng cấp cấu hình RDS lên instance khủng | Tối ưu: Chỉ scale pod và database của service bị nghẽn |
| Thời Gian Chạy Migration DB | Kéo dài nhiều giờ, khóa bảng gây gián đoạn hệ thống | Vài giây cho mỗi microservice, zero-downtime |
| Độ Phức Tạp Triển Khai Giao Dịch | Đơn giản: Gọi DB::transaction() cục bộ | Đòi hỏi Saga Orchestration / Choreography |
2. Chi Tiết Ranh Giới 6 Bounded Domains & Trách Nhiệm Từng Service
graph TD
subgraph "Domain 1: Commerce Flow (Dòng Tiền)"
CK["Checkout Service\n(Saga Orchestrator)"]
ORD["Order Service\n(8-State FSM / order_db)"]
PAY["Payment Gateway\n(Idempotency / payment_db)"]
end
subgraph "Domain 2: Product & Content (Danh Mục)"
CAT["Catalog Service\n(PIM Normalized)"]
PRC["Pricing Service\n(Tiered Pricing Engine)"]
PROMO["Promotion Service\n(Coupon Voucher Ledger)"]
SEARCH["Search Service\n(CQRS / Elasticsearch)"]
end
subgraph "Domain 3: Logistics & WMS (Kho Vận)"
WH["Warehouse Service\n(OCC Stock / inventory_db)"]
FF["Fulfillment Service\n(Pick-Pack-Ship Workflow)"]
SH["Shipping Service\n(3PL Carrier Integrator)"]
end
subgraph "Domain 4: Post-Purchase (Hậu Mãi)"
RET["Return Service\n(RMA / Reverse Logistics)"]
LR["Loyalty Service\n(Double-Entry Points Ledger)"]
REV["Review Service\n(Customer UGC Moderation)"]
end
subgraph "Domain 5: Identity & Access (Bảo Mật)"
AUTH["Auth Service\n(RS256 JWT / OAuth2)"]
USR["User Service\n(Internal Staff RBAC)"]
CUST["Customer Service\n(CRM Profile & LTV)"]
end
subgraph "Domain 6: Platform Operations (Hạ Tầng)"
GW["API Gateway\n(Envoy / BFF Layer)"]
NOTIF["Notification Hub\n(Email / SMS / Push)"]
AN["Analytics Engine\n(ClickHouse Event Stream)"]
end
3. Sơ Đồ Trình Tự Thanh Toán Liên Miền (Cross-Domain Checkout Saga)
Khi khách hàng nhấn nút đặt hàng, một quy trình điều phối phức tạp giữa các domain Commerce Flow, Logistics, và Payment được kích hoạt:
sequenceDiagram
autonumber
actor Client as Khách Hàng (Web/App)
participant BFF as API Gateway (BFF Layer)
participant Order as Order Service (Commerce)
participant Inv as Warehouse Service (Logistics)
participant Pay as Payment Service (Commerce)
participant Ship as Shipping Service (Logistics)
participant Bus as Kafka / Dapr Event Mesh
Note over Client, Bus: Giai Đoạn 1: Khởi Tạo Đơn Hàng Đồng Bộ
Client->>BFF: POST /api/v1/checkout (Cart Payload, Idempotency-Key)
BFF->>Order: CreateOrder(CreateOrderRequest) [gRPC]
Order->>Order: Ghi DB (Status: PENDING_PAYMENT, OCC Version: 1)
Order-->>BFF: 201 Created (OrderID: `ord_98765`, Status: PENDING)
BFF-->>Client: Checkout Thành Công (OrderID: `ord_98765`)
Note over Order, Bus: Giai Đoạn 2: Xử Lý Bất Đồng Bộ & Giữ Chỗ Kho
Order->>Bus: Publish `OrderCreatedEvent` (OrderID, SKU, Qty, UserID)
Bus-->>Inv: Consume `OrderCreatedEvent`
Inv->>Inv: Reserve Stock (IDEMPOTENT, OCC `version = version + 1`)
alt Tồn Kho Khả Dụng Đầy Đủ
Inv->>Bus: Publish `InventoryReservedEvent` (OrderID, WarehouseID)
else Hết Hàng Trong Kho
Inv->>Bus: Publish `InventoryReservationFailedEvent` (OrderID, Reason)
Bus-->>Order: Consume `InventoryReservationFailedEvent`
Order->>Order: Cập nhật DB (Status: CANCELLED_OUT_OF_STOCK)
end
Note over Pay, Bus: Giai Đoạn 3: Khấu Trừ Cổng Thanh Toán
Bus-->>Pay: Consume `InventoryReservedEvent`
Pay->>Pay: Kiểm tra gian lận & Gọi cổng thẻ (Stripe / VNPay / MoMo)
alt Thanh Toán Thành Công
Pay->>Bus: Publish `PaymentProcessedEvent` (OrderID, TxID, Status: SUCCESS)
else Thẻ Bị Từ Chối / Timeout
Pay->>Bus: Publish `PaymentProcessedEvent` (OrderID, Status: FAILED)
Bus-->>Inv: Consume `PaymentProcessedEvent` (FAILED)
Inv->>Inv: Bồi hoàn (Compensation): Giải phóng hàng giữ chỗ
Bus-->>Order: Consume `PaymentProcessedEvent` (FAILED)
Order->>Order: Cập nhật DB (Status: PAYMENT_FAILED)
end
Note over Ship, Bus: Giai Đoạn 4: Xác Nhận Đơn & Bàn Giao Vận Chuyển
Bus-->>Order: Consume `PaymentProcessedEvent` (SUCCESS)
Order->>Order: Cập nhật DB (Status: CONFIRMED)
Order->>Bus: Publish `OrderConfirmedEvent` (OrderID, CustomerID)
Bus-->>Ship: Consume `OrderConfirmedEvent`
Ship->>Ship: Tạo mã vận đơn 3PL (GHTK / GrabExpress) & Lưu DB
Ship->>Bus: Publish `ShipmentDispatchedEvent` (TrackingNo, Carrier)
4. Hợp Đồng Dữ Liệu Bất Đồng Bộ (Protobuf v3 & Event Schemas)
Để bảo đảm tính tương thích lâu dài và không làm vỡ các dịch vụ khi nâng cấp, các bản tin sự kiện giữa các domain được định nghĩa bằng Protocol Buffers v3:
syntax = "proto3";
package ecommerce.events.v1;
option go_package = "github.com/tanhdev/learn/proto/events/v1;eventsv1";
import "google/protobuf/timestamp.proto";
// Enum chuẩn hóa trạng thái thanh toán
enum PaymentStatus {
PAYMENT_STATUS_UNSPECIFIED = 0;
PAYMENT_STATUS_SUCCESS = 1;
PAYMENT_STATUS_FAILED = 2;
PAYMENT_STATUS_REQUIRES_ACTION = 3;
}
// Phát bởi Order Service khi đơn hàng mới được ghi nhận vào database
message OrderCreatedEvent {
string event_id = 1;
string order_id = 2;
string customer_id = 3;
int64 total_amount_cents = 4;
string currency = 5; // ISO 4217 (ví dụ: "VND", "USD")
string idempotency_key = 6;
message OrderItem {
string sku_id = 1;
int32 quantity = 2;
int64 unit_price_cents = 3;
}
repeated OrderItem items = 7;
google.protobuf.Timestamp created_at = 8;
}
// Phát bởi Warehouse Service khi giữ tồn kho thành công
message InventoryReservedEvent {
string event_id = 1;
string reservation_id = 2;
string order_id = 3;
string warehouse_id = 4;
message ReservedItem {
string sku_id = 1;
int32 quantity = 2;
}
repeated ReservedItem items = 5;
int64 expires_at_unix = 6;
google.protobuf.Timestamp reserved_at = 7;
}
// Phát bởi Payment Service sau khi hoàn tất giao dịch với cổng thanh toán
message PaymentProcessedEvent {
string event_id = 1;
string payment_id = 2;
string order_id = 3;
string transaction_reference = 4;
int64 amount_cents = 5;
string payment_provider = 6; // ví dụ: "MOMO", "VNPAY", "STRIPE"
PaymentStatus status = 7;
string failure_reason = 8;
google.protobuf.Timestamp processed_at = 9;
}
5. Thiết Kế Cơ Sở Dữ Liệu PostgreSQL & Mã Nguồn GORM Chuẩn Production
5.1 Order Service (order_db) Schema & Go Structs
-- DDL PostgreSQL cho order_db
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_number VARCHAR(64) NOT NULL UNIQUE,
customer_id UUID NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'PENDING_PAYMENT',
total_amount_cents BIGINT NOT NULL CHECK (total_amount_cents >= 0),
currency VARCHAR(3) NOT NULL DEFAULT 'VND',
idempotency_key VARCHAR(128) NOT NULL UNIQUE,
version INT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE order_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
sku_id VARCHAR(64) NOT NULL,
quantity INT NOT NULL CHECK (quantity > 0),
unit_price_cents BIGINT NOT NULL CHECK (unit_price_cents >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_orders_customer_status ON orders(customer_id, status);
CREATE INDEX idx_orders_created_at ON orders(created_at DESC);
// Package models triển khai Order Domain Model trong Order Service
package models
import (
"time"
"github.com/google/uuid"
)
type OrderStatus string
const (
StatusPendingPayment OrderStatus = "PENDING_PAYMENT"
StatusConfirmed OrderStatus = "CONFIRMED"
StatusPaymentFailed OrderStatus = "PAYMENT_FAILED"
StatusCancelled OrderStatus = "CANCELLED"
)
type Order struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
OrderNumber string `gorm:"type:varchar(64);uniqueIndex;not null"`
CustomerID uuid.UUID `gorm:"type:uuid;index:idx_orders_customer_status;not null"`
Status OrderStatus `gorm:"type:varchar(32);index:idx_orders_customer_status;not null;default:'PENDING_PAYMENT'"`
TotalAmountCents int64 `gorm:"type:bigint;not null"`
Currency string `gorm:"type:varchar(3);not null;default:'VND'"`
IdempotencyKey string `gorm:"type:varchar(128);uniqueIndex;not null"`
Version int32 `gorm:"type:int;not null;default:1"` // Quản lý kiểm soát đồng thời lạc quan (OCC)
Items []OrderItem `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"`
CreatedAt time.Time `gorm:"type:timestamptz;not null;default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `gorm:"type:timestamptz;not null;default:CURRENT_TIMESTAMP"`
}
type OrderItem struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
OrderID uuid.UUID `gorm:"type:uuid;not null;index"`
SkuID string `gorm:"type:varchar(64);not null"`
Quantity int32 `gorm:"type:int;not null"`
UnitPriceCents int64 `gorm:"type:bigint;not null"`
CreatedAt time.Time `gorm:"type:timestamptz;not null;default:CURRENT_TIMESTAMP"`
}
5.2 Warehouse Service (inventory_db): Chống Overselling Tuyệt Đối
-- DDL PostgreSQL cho inventory_db với kiểm soát lạc quan (OCC)
CREATE TABLE stock_levels (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
warehouse_id UUID NOT NULL,
sku_id VARCHAR(64) NOT NULL,
available_qty INT NOT NULL CHECK (available_qty >= 0),
reserved_qty INT NOT NULL CHECK (reserved_qty >= 0),
version INT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_warehouse_sku UNIQUE (warehouse_id, sku_id)
);
CREATE TABLE stock_reservations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID NOT NULL,
warehouse_id UUID NOT NULL,
sku_id VARCHAR(64) NOT NULL,
reserved_qty INT NOT NULL CHECK (reserved_qty > 0),
status VARCHAR(32) NOT NULL DEFAULT 'RESERVED',
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_reservations_order_id ON stock_reservations(order_id);
CREATE INDEX idx_reservations_status_expires ON stock_reservations(status, expires_at);
// Package models triển khai Inventory OCC Locking trong Warehouse Service
package models
import (
"context"
"errors"
"fmt"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
var ErrOptimisticLockConflict = errors.New("xung đột phiên bản tồn kho, vui lòng thử lại")
var ErrInsufficientStock = errors.New("số lượng tồn kho khả dụng không đủ")
type StockLevel struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
WarehouseID uuid.UUID `gorm:"type:uuid;uniqueIndex:uk_warehouse_sku;not null"`
SkuID string `gorm:"type:varchar(64);uniqueIndex:uk_warehouse_sku;not null"`
AvailableQty int32 `gorm:"type:int;not null"`
ReservedQty int32 `gorm:"type:int;not null"`
Version int32 `gorm:"type:int;not null;default:1"`
CreatedAt time.Time `gorm:"type:timestamptz;not null;default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `gorm:"type:timestamptz;not null;default:CURRENT_TIMESTAMP"`
}
// ReserveStockSafe thực thi giữ hàng an toàn chống overselling bằng Optimistic Concurrency Control
func ReserveStockSafe(ctx context.Context, db *gorm.DB, warehouseID uuid.UUID, skuID string, qty int32, orderID uuid.UUID) error {
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var stock StockLevel
if err := tx.Where("warehouse_id = ? AND sku_id = ?", warehouseID, skuID).First(&stock).Error; err != nil {
return err
}
if stock.AvailableQty < qty {
return ErrInsufficientStock
}
// Cập nhật với điều kiện Version khớp chính xác
res := tx.Model(&StockLevel{}).
Where("id = ? AND version = ?", stock.ID, stock.Version).
Updates(map[string]interface{}{
"available_qty": stock.AvailableQty - qty,
"reserved_qty": stock.ReservedQty + qty,
"version": stock.Version + 1,
"updated_at": time.Now(),
})
if res.Error != nil {
return res.Error
}
if res.RowsAffected == 0 {
// Phiên bản đã bị luồng khác ghi đè trước đó -> Kích hoạt retry
return ErrOptimisticLockConflict
}
// Ghi nhận bản ghi giữ hàng có hạn giờ (TTL 15 phút)
reservation := StockReservation{
OrderID: orderID,
WarehouseID: warehouseID,
SkuID: skuID,
ReservedQty: qty,
Status: "RESERVED",
ExpiresAt: time.Now().Add(15 * time.Minute),
}
return tx.Create(&reservation).Error
})
}
type StockReservation struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
OrderID uuid.UUID `gorm:"type:uuid;index;not null"`
WarehouseID uuid.UUID `gorm:"type:uuid;not null"`
SkuID string `gorm:"type:varchar(64);not null"`
ReservedQty int32 `gorm:"type:int;not null"`
Status string `gorm:"type:varchar(32);not null;default:'RESERVED'"`
ExpiresAt time.Time `gorm:"type:timestamptz;not null"`
CreatedAt time.Time `gorm:"type:timestamptz;not null;default:CURRENT_TIMESTAMP"`
}
6. Giá Trị Thực Chiến Của Sự Phân Lập
Khi nhìn hệ thống qua lăng kính 6 Domain Bounded Contexts, 21 microservices không còn là một sự phân mảnh hỗn độn mà là một dây chuyền tự động hóa nhịp nhàng:
- Khả năng phục hồi độc lập: Một sự cố sập cơ sở dữ liệu của
Review ServicehoặcLoyalty Servicehoàn toàn không gây ảnh hưởng tới luồng mua hàng tạiCheckouthayPayment. - Tối ưu hóa chi phí điện toán: Vào các ngày mua sắm Flash Sale, đội ngũ vận hành chỉ cần scale cụm Pods của
OrdervàWarehousetừ 4 lên 25 bản sao, trong khiCatalogvàPlatform Operationsvẫn duy trì ở mức tối thiểu. - Tốc độ đưa tính năng ra thị trường: Đội ngũ phát triển tính năng Khuyến mãi (Promotions Squad) có thể tự do thử nghiệm thuật toán coupon mới và triển khai CI/CD 10 lần mỗi ngày mà không phải xin phép hay chờ đợi phê duyệt từ đội ngũ Kho vận (Warehouse Squad).
7. Benchmark Cơ Chế Đồng Thời & Xung Đột Dữ Liệu Tồn Kho/Giá (50.000 TPS)
Dưới đây là kết quả kiểm thử mô phỏng 50.000 người dùng đồng thời tranh chấp giữ hàng 10 SKU bán chạy trong đợt Mega Sale (k6 load test trên Kubernetes 3 nodes c6i.2xlarge):
| Cơ Chế Kiểm Soát Đồng Thời | Thông Lượng Giữ Chỗ (TPS) | Độ Trễ P99 | Tỷ Lệ Xung Đột / Retry | Nguy Cơ Gây Deadlock DB | Tác Động Connection Pool |
|---|---|---|---|---|---|
Pessimistic Lock (SELECT ... FOR UPDATE) | ~420 TPS | > 2.800 ms (Nghẽn hàng đợi khóa) | 0% (Khóa cứng dòng) | Rất cao (38%) khi nhiều bảng giao thoa | Cạn kiệt pool trong 3 giây |
Go OCC (version = stock.Version) | ~6.800 TPS | < 35 ms | 12% (Xử lý mượt qua Exponential Backoff) | Tuyệt đối 0% (Không giữ khóa hàng dài hạn) | Sử dụng < 25 kết nối ổn định |
| Redis Lua Script + Async Write-Behind | > 48.000 TPS | < 2.5 ms | 0% (Thao tác nguyên tử trong RAM) | Tuyệt đối 0% (Single-threaded event loop) | Tách biệt hoàn toàn khỏi DB |
| Virtual Buckets (10 Ngăn Tồn Kho Phân Tán) | > 92.000 TPS | < 1.8 ms | < 0.5% (Tải ghi trải đều trên 10 keys) | Tuyệt đối 0% | Tối ưu hóa tuyệt đối |
8. Các Câu Hỏi Thường Gặp (FAQ)
Làm thế nào để báo cáo tài chính tổng hợp dữ liệu từ 21 cơ sở dữ liệu phân tán mà không được dùng lệnh SQL JOIN?
Optimistic Concurrency Control (OCC) xử lý ra sao khi có hàng ngàn khách hàng cùng mua 1 sản phẩm cuối cùng?
UPDATE có kiểm tra version = stock.Version sẽ trả về RowsAffected == 0 cho các luồng chậm hơn, kích hoạt mã lỗi ErrOptimisticLockConflict. Go Service sẽ áp dụng cơ chế Exponential Backoff có độ lệch ngẫu nhiên (Jitter) để thử lại tối đa 3 lần. Nếu số lượng khả dụng đã giảm về 0, hệ thống lập tức thông báo hết hàng mà không gây tình trạng nghẽn khóa hàng chờ (Lock Queuing) như mô hình SELECT ... FOR UPDATE truyền thống.Tại sao lại phân tách riêng biệt Warehouse Service, Fulfillment Service và Shipping Service?
Warehouse Service quản lý số lượng tồn kho logic và giữ chỗ tạm thời; (2) Fulfillment Service quản lý con người và quy trình tác nghiệp nội bộ trong kho (In danh sách nhặt hàng, lộ trình đi lại của công nhân, đóng thùng carton); (3) Shipping Service là cổng kết nối ra thế giới bên ngoài, quản lý API webhook và tính cước với các đối tác vận chuyển thứ ba (3PL). Tách biệt ranh giới này giúp việc thay đổi đơn vị vận chuyển không làm ảnh hưởng tới quy trình xếp kho nội bộ.Làm sao để đảm bảo Idempotency (tính lũy đẳng) khi mạng chập chờn khiến sự kiện thanh toán bị gửi 2 lần?
Idempotency-Key (sinh ngẫu nhiên UUIDv4 từ client hoặc kết hợp order_id + event_version). Tại Payment Service, bảng cơ sở dữ liệu payment_transactions thiết lập ràng buộc duy nhất (UNIQUE) trên cột idempotency_key. Khi bản tin thứ hai đến, cơ sở dữ liệu sẽ chặn đứng bằng lỗi trùng khóa (Duplicate Key Violation), và service sẽ trả ngay kết quả giao dịch trước đó mà không trừ tiền của khách hàng lần thứ hai.Chi phí quản lý mạng lưới 21 microservices có làm quá tải đội ngũ kỹ thuật nhỏ không?
🔗 Đọc thêm các chuyên đề & Series liên quan:
- Architecting 21 E-Commerce Services with Golang & DDD — Chi tiết Clean Architecture Kratos, Saga Orchestration và OCC.
- Sơ Đồ Blueprint Kiến Trúc 21 Microservices — Bản đồ toàn diện luồng dữ liệu 21 dịch vụ.
- Đồng Bộ Tồn Kho Thời Gian Thực Với Kafka, Debezium & Redis — Thiết lập kiến trúc CDC không khóa DB.
- Temporal Saga Pattern Trong Golang — Xử lý giao dịch phân tán không cần 2PC.
- Kiến Trúc Shopee Flash Sale: Xử Lý Hàng Triệu Đơn Hàng — Kỹ thuật chống overselling ở quy mô lớn nhất Đông Nam Á.
