Khi chuyển đổi từ một nền tảng nguyên khối (monolith) sang một hệ thống microservice phân tán, câu hỏi khó nhất không phải là “Chúng ta viết code như thế nào?” — mà là “Làm sao để các mảnh ghép di động này nói chuyện với nhau một cách an toàn, và tại sao mỗi ranh giới lại được vẽ chính xác ở vị trí đó?”

Bài viết này là mỏ neo kiến trúc cho toàn bộ series về composable commerce. Nó trình bày bản vẽ hệ thống tổng thể và giải thích lý do đằng sau mỗi ranh giới domain. Để tìm hiểu sâu về từng tầng cụ thể, mỗi phần đều có link dẫn đến bài viết chuyên đề trong series.

6 Domain Nghiệp vụ (Business Domains)

Trước khi vẽ bất kỳ một đường thẳng nào lên biểu đồ, chúng tôi đã khoanh vùng hệ sinh thái dựa trên các nguyên tắc của Thiết kế Hướng Domain (Domain-Driven Design - DDD). Mỗi domain sở hữu cơ sở dữ liệu Postgres riêng. Tuyệt đối không có các câu query chéo giữa các domain. Mọi giao tiếp diễn ra độc quyền thông qua sự kiện (events) hoặc các hợp đồng gRPC rõ ràng.

6 domain và 21 service tương ứng:

DomainServicesSở hữu
Commerce FlowCheckout, Order, PaymentLuồng tiền bạc — độ quan trọng cao nhất
Product & ContentCatalog, Pricing, Promotion, SearchĐọc nhiều (Read-heavy), độ trễ < 50ms
LogisticsWarehouse, Fulfillment, ShippingTích hợp với thế giới vật lý
Post-PurchaseReturns, LoyaltyGiữ chân khách hàng sau khi giao hàng
Identity & AccessAuth, User, CustomerRanh giới bảo mật giữa nhân viên nội bộ và khách hàng
Platform OperationsGateway, Analytics, NotificationCác tiện ích hạ tầng dùng chung

Lý do đằng sau việc tách biệt User (Người dùng nội bộ)Customer (Khách hàng) đáng để nói rõ: nhân viên nội bộ và người mua bên ngoài có pattern truy cập, cấu trúc dữ liệu, và yêu cầu pháp lý hoàn toàn khác biệt. Gộp chung chúng lại sẽ tạo ra một schema không phục vụ tốt cho bên nào cả, và tạo ra một bề mặt rủi ro bảo mật nơi một lỗi RBAC nội bộ có thể làm lộ thông tin cá nhân (PII) của khách hàng.

Để xem chi tiết nhiệm vụ của từng service, xem Bóc tách Hệ sinh thái: Chi tiết Service theo Domain.

Kiến trúc Tổng quan (High-Level Architecture)

graph TD
    subgraph "🌐 Clients Bên Ngoài"
        FE["Website Khách Hàng (Next.js)"]
        ADMIN["Admin Dashboard (React)"]
    end

    subgraph "🚪 Tầng API Gateway"
        GW["Global Gateway<br>(Auth · Rate Limit · Circuit Breaker · BFF)"]
    end

    subgraph "🔐 Identity & Access"
        AUTH["Auth Service<br>(RS256 JWT · OAuth2 · MFA)"]
        USR["User Service<br>(Internal RBAC)"]
        CUST["Customer Profile<br>(LTV · Segmentation)"]
    end

    subgraph "📦 Product & Content"
        CAT["Catalog (PIM)<br>(EAV → Normalized)"]
        PRC["Pricing Rules<br>(Đa tiền tệ · Thuế)"]
        PROMO["Promotions<br>(BOGO · Coupons)"]
        SEARCH["Search (CQRS)<br>(Elasticsearch)"]
    end

    subgraph "🛒 Commerce Engine"
        CK["Checkout Orchestrator<br>(Khởi tạo Saga)"]
        ORD["Order Lifecycle<br>(8-state FSM)"]
        PAY["Payment Gateway<br>(Stripe · VNPay · MoMo)"]
    end

    subgraph "🚚 Logistics & Fulfillment"
        WH["Warehouse (WMS)<br>(OCC · Idempotency)"]
        FF["Fulfillment<br>(Nhặt · Đóng gói · Giao)"]
        SH["Shipping Hub<br>(Chuẩn hóa Carrier)"]
    end

    subgraph "🎁 Post-Purchase"
        RET["Returns (RMA)<br>(Nhập lại kho · Refund gRPC)"]
        LR["Loyalty & Rewards<br>(Outbox · Sổ điểm)"]
    end

    subgraph "📡 Platform Services"
        AN["Analytics Engine<br>(Quan sát thụ động)"]
        NOTIF["Notification Hub<br>(SendGrid · Twilio)"]
    end

    %% External traffic
    FE & ADMIN --> GW

    %% Synchronous paths (solid)
    GW --> AUTH & CUST & CAT & SEARCH & CK & ORD

    %% gRPC internal calls (dotted)
    CK -.->|"gRPC: CreateOrder"| ORD
    CK -.->|"gRPC: Authorize"| PAY
    RET -.->|"gRPC: Refund"| PAY

    %% Async event paths (double)
    ORD ==>|"order.confirmed"| WH
    ORD ==>|"order.paid"| FF
    FF ==>|"fulfillment.completed"| SH
    SH ==>|"shipping.delivered"| ORD
    ORD ==>|"order.cancelled"| LR & PROMO
    CAT ==>|"product.updated"| SEARCH & AN
    ORD ==>|"order.created"| AN & CUST & NOTIF

Nét liền = đồng bộ HTTP/gRPC. Nét đôi (==>) = sự kiện bất đồng bộ qua Dapr PubSub.

Giải phẫu Traffic: Ba Luồng Tách biệt

Luồng 1 — Tấm khiên Gateway (Đường Read)

Mọi traffic bên ngoài đều đi vào thông qua API Gateway. Gateway này ép buộc:

  • Xác thực JWT — giảm tải cho các service bên dưới; mọi request đều được xác thực ngay ở vùng biên.
  • Giới hạn tốc độ (Rate limiting) — dựa trên IP và user, bảo vệ hệ thống khỏi bot cào dữ liệu và các vụ tấn công checkout.
  • Circuit breaking (Cầu dao điện) — nếu Catalog service bị suy giảm hiệu năng, Gateway sẽ báo lỗi ngay lập tức (fail fast) thay vì để các request kẹt lại và xếp chồng lên nhau.

Các hành động đọc liên tục (danh sách sản phẩm, tìm kiếm, profile user) được phân giải ở đây với độ trễ dưới 50ms bởi vì Search service duy trì một Elasticsearch CQRS read model đã được index sẵn, và được cập nhật gần như realtime thông qua các sự kiện từ Catalog và Pricing.

Luồng 2 — Checkout Saga (Đường Write)

Đây là luồng quan trọng và phức tạp nhất. Khi khách hàng tiến hành checkout:

sequenceDiagram
    participant GW as API Gateway
    participant CK as Checkout
    participant WH as Warehouse
    participant PAY as Payment
    participant ORD as Order
    participant DAPR as Dapr PubSub

    GW->>CK: POST /checkout/confirm
    CK->>WH: gRPC: ReserveStock (Giữ hàng TTL 15min)
    WH-->>CK: ✅ Đã giữ hàng

    CK->>PAY: gRPC: Authorize payment
    PAY-->>CK: ✅ Cấp quyền

    CK->>ORD: gRPC: CreateOrder
    ORD-->>CK: ✅ Đơn #10482 đã tạo

    ORD->>DAPR: Publish order.confirmed
    Note over DAPR: Async bắt đầu từ đây — Checkout trả về 200 cho user

    DAPR-->>WH: Khấu trừ tồn kho vĩnh viễn
    DAPR-->>CK: Kích hoạt luồng fulfillment

Việc xử lý thất bại sử dụng cơ chế Compensating Transactions (Giao dịch Bù trừ): nếu Payment service từ chối thẻ ngân hàng sau khi tồn kho đã bị khóa, Checkout service sẽ kích hoạt sự kiện checkout.failed. Warehouse service sẽ lắng nghe sự kiện này và giải phóng hàng đang bị giữ. Không có các giao dịch database kéo dài lê thê, không bị cạn kiệt connection pool dưới tải nặng.

Để xem bản triển khai hoàn chỉnh — bao gồm SQL Optimistic Concurrency Control xử lý race conditions của kho hàng và pattern khóa lũy đẳng — hãy đọc Thiết kế Hệ sinh thái Thương mại điện tử 21 Microservices với Golang & DDD.

Luồng 3 — Lưới sự kiện Async (Post-Checkout)

Một khi sự kiện order.paid bắn vào lưới sự kiện Dapr, việc thực thi đồng bộ chấm dứt từ góc nhìn của khách hàng. Các service phía sau sẽ chạy song song:

  • Warehouse khấu trừ vĩnh viễn tồn kho đã giữ.
  • Fulfillment kích hoạt luồng nhặt-đóng gói-giao hàng.
  • Analytics tăng doanh thu trên dashboard.
  • Customer cập nhật LTV và lịch sử mua hàng.
  • Notification bắn email/SMS xác nhận đơn hàng.

Sự cố ở bất kỳ service nào trong nhóm này (Notification không gọi được, Analytics bị chậm) hoàn toàn không ảnh hưởng đến trải nghiệm checkout của khách hàng hay việc ghi nhận đơn hàng. Sự cách ly được ép buộc ở tầng hạ tầng — mỗi service sở hữu database riêng.

Để hiểu về các quy ước đặt tên sự kiện Dapr, Dead Letter Queue, và thiết kế lũy đẳng, hãy xem Làm chủ Kiến trúc Hướng Sự kiện với Dapr Pub/Sub.

Tính năng tìm kiếm sản phẩm xứng đáng có một mục riêng vì nó giải quyết vấn đề mà mô hình EAV của Magento gặp khó khi scale: việc JOIN 5+ bảng tại thời điểm query chỉ để hiển thị một danh sách sản phẩm là quá chậm.

Search service duy trì một mô hình đọc bằng Elasticsearch được build lại từ các sự kiện, chứ không query từ database gốc:

flowchart LR
    CAT["Catalog Service<br>(source of truth)"] -- "catalog.product.updated" --> DAPR[Dapr PubSub]
    PRC["Pricing Service"] -- "pricing.price.updated" --> DAPR
    WH["Warehouse Service"] -- "warehouse.stock.changed" --> DAPR
    DAPR --> SW["Search Worker<br>(subscriber)"]
    SW --> ES[("Elasticsearch<br>Flat documents")]
    ES -- "đọc < 50ms" --> GW["API Gateway"]

Khi team Catalog cập nhật một sản phẩm, họ ghi vào database Postgres của riêng họ và publish sự kiện catalog.product.updated. Search Worker nhận sự kiện đó và build lại document Elasticsearch cho SKU này — gộp cả giá mới nhất từ Pricing service và tồn kho mới nhất từ Warehouse service vào một document phẳng (flat document) duy nhất.

Không cần cron jobs. Không cần reindex toàn bộ. Không có khoảng trễ dữ liệu cũ. Catalog, Pricing, và Warehouse sở hữu dữ liệu domain của chúng; Search sở hữu bản đồ chiếu đọc (read projection).

Tầng Triển khai (Deployment Layer)

Toàn bộ nền tảng 21-service được deploy thông qua GitOps sử dụng ArgoCD và Kustomize overlays. Không có bất kỳ kỹ sư nào được phép chạm trực tiếp vào production cluster — mọi thay đổi phải chảy qua Git, và ArgoCD ép buộc việc chống trôi lệch cấu hình thông qua tính năng selfHeal: true trên mọi Application ở production.

Để xem toàn bộ thiết lập GitOps — bao gồm pattern App-of-Apps, cấu trúc Kustomize base/overlay, và sổ tay rollback bằng lệnh git revert — hãy xem GitOps at Scale: Điều phối 21 Microservices với Kubernetes & ArgoCD.

Tại sao lại không phải là Distributed Monolith?

Chế độ thất bại phổ biến nhất khi các team chuyển sang microservices là việc xây dựng ra một Distributed Monolith (Kiến trúc nguyên khối phân tán): các service được deploy riêng rẽ nhưng lại dính chặt vào nhau thông qua các chuỗi HTTP đồng bộ, chung database, hoặc chung pipeline deploy.

Kiến trúc phía trên tránh được điều này thông qua 3 quy tắc thép:

  1. Tuyệt đối không truy cập database chéo domain. Nếu Order service cần dữ liệu sản phẩm, nó hoặc là phải lưu một bản copy chuẩn hóa, hoặc là gọi gRPC API của Catalog service.
  2. Không có lệnh gọi đồng bộ nào nằm trong luồng event bất đồng bộ. Một khi sự kiện bước vào lưới Dapr, nó được xử lý độc lập. Các event consumers tuyệt đối không call-back (gọi ngược lại) producer.
  3. Không dùng chung pipeline deployment — mỗi service có ArgoCD Application riêng, đường dẫn registry riêng, và chu kỳ release riêng. Một lỗi ở Loyalty service không thể khóa chết việc release của Checkout.

Để xem toàn bộ lập luận về việc khi nào sự phức tạp này là xứng đáng — và khi nào thì không — hãy đọc Vì sao bạn nên Migrate từ Magento sang Microservices (Và khi nào thì không nên).

Điều hướng Series

Bài viếtNội dung bao trùm
Bài viết nàyBản vẽ toàn hệ thống, ranh giới domain, luồng traffic
Chi tiết Service theo DomainTrách nhiệm và quyền sở hữu của từng service
Golang DDD Deep-DiveKratos clean arch, triển khai Saga, OCC, lũy đẳng
Hướng sự kiện với DaprQuy ước đặt tên, Saga pattern, thiết kế DLQ
GitOps với ArgoCDApp-of-Apps, Kustomize overlays, sổ tay rollback
Magento sang Microservices: Tại saoKhung quyết định: khi nào nên migrate, khi nào không
Magento sang Microservices: Như thế nàoStrangler Fig 3 giai đoạn, Debezium CDC, đồng bộ 2 chiều

🤝 Kết nối với tôi

Bạn đang gặp phải những thách thức tương tự về kiến trúc hệ thống, mở rộng quy mô (scaling) hay dịch chuyển (migration)? Hãy kết nối với tôi trên LinkedIn, theo dõi GitHub của tôi, hoặc gửi một email để trao đổi nhé.