2269 từ
11 phút
Microservices (P1): Bản đồ thuật ngữ — đi theo một đơn hàng qua hệ thống

Lần đầu vào một dự án microservices, thứ làm bạn choáng thường không phải là code. Đó là một buổi họp kiểu: “Order service publish event qua outbox, payment consumer phải idempotent, nếu fail thì saga compensate, nhớ gắn correlation ID để trace.” Câu nào cũng có tiếng Việt, mà nghe không hiểu gì.

Glossary thì có rất nhiều. Vấn đề là học từng định nghĩa riêng lẻ thì mau quên. Lý do là bạn không thấy thuật ngữ đó sinh ra để chữa bệnh gì.

Bài này làm ngược lại: đi theo một request đặt hàng, từ lúc user bấm “Đặt hàng” đến khi đơn được xác nhận. Tới chỗ nào có vấn đề, thuật ngữ tương ứng sẽ xuất hiện. Đây là bài mở đầu của series Microservices từ A–Z. Mỗi nhóm thuật ngữ bên dưới sẽ có bài riêng để đi sâu, kèm code .NET.

Hệ thống ví dụ#

Một shop online nhỏ, tách thành 4 service:

  • Order Service: tạo và quản lý đơn hàng
  • Payment Service: trừ tiền
  • Inventory Service: giữ hàng trong kho
  • Notification Service: gửi email/SMS
flowchart LR
    C["Client (Web/Mobile)"] --> GW["API Gateway"]
    GW --> O["Order Service"]
    O --> ODB[("Order DB")]
    O -- "OrderCreated" --> MB{{"Message Broker"}}
    MB --> P["Payment Service"]
    MB --> I["Inventory Service"]
    MB --> N["Notification Service"]
    P --> PDB[("Payment DB")]
    I --> IDB[("Inventory DB")]

Cả series dùng lại đúng hệ thống này.

Chặng 0: Vì sao lại tách ra?#

Trước khi có 4 service, hệ thống thường bắt đầu là một khối.

Thuật ngữHiểu nhanh
MonolithMột ứng dụng, một lần deploy, thường dùng một database. Không xấu. Hầu hết sản phẩm nên bắt đầu như vậy.
Big Ball of MudMonolith để lâu không có ranh giới: module nào cũng gọi module nào, sửa một chỗ vỡ ba chỗ. Đây mới là thứ đáng sợ.
Modular MonolithVẫn deploy một khối, nhưng chia module có ranh giới rõ (mỗi module có API và dữ liệu riêng). Đây là bước đệm tự nhiên trước khi tách service.
MicroservicesMỗi service deploy độc lập, sở hữu dữ liệu riêng, giao tiếp qua mạng. Đổi lại, bạn phải gánh độ phức tạp của hệ phân tán.
Conway’s LawKiến trúc hệ thống sẽ phản chiếu cấu trúc giao tiếp của tổ chức. Có 4 team mà ép vào 1 service, hoặc 1 team ôm 12 service, đều sẽ đau.
WARNING

Microservices là giải pháp cho vấn đề tổ chức và scale team, trước khi là vấn đề kỹ thuật. Nếu team bạn có 3 người, gần như chắc chắn bạn chưa cần nó. Bài P2 sẽ nói kỹ chuyện này.

Chặng 1: Tách thế nào cho đúng — DDD#

Câu hỏi đầu tiên: ranh giới giữa Order và Payment nằm ở đâu? Tách sai thì sau này hai service phải gọi nhau liên tục, tức là bạn có một distributed monolith: tệ hơn cả monolith gốc.

Thuật ngữHiểu nhanh
Domain-Driven Design (DDD)Thiết kế phần mềm xoay quanh nghiệp vụ, dùng ngôn ngữ của nghiệp vụ.
Ubiquitous LanguageDev và business dùng chung một bộ từ. Nếu business nói “đơn hàng” mà code viết Cart, sớm muộn sẽ có bug hiểu nhầm.
Bounded ContextRanh giới mà trong đó một từ có đúng một nghĩa. “Product” trong Catalog (mô tả, hình ảnh) khác “Product” trong Inventory (số lượng, vị trí kho). Mỗi Bounded Context thường là ứng viên để thành một service.
AggregateCụm object phải luôn nhất quán với nhau, thay đổi qua một cửa duy nhất (aggregate root). Order + OrderLine là một aggregate.
Entity / Value ObjectEntity có định danh (OrderId). Value Object chỉ có giá trị (Money, Address): hai Money(100, "VND") là một.
Domain Event vs Integration EventDomain Event là chuyện xảy ra bên trong một service. Integration Event là chuyện được công bố ra ngoài cho service khác.
Anti-Corruption Layer (ACL)Lớp dịch giữa model của mình và model của hệ thống khác (legacy, bên thứ ba), để model “bẩn” không lan vào code của mình.
Strangler FigTách dần từng phần khỏi monolith thay vì viết lại từ đầu. Request được chuyển dần sang service mới cho đến khi phần cũ không còn ai dùng.

Chặng 2: Request đi vào — Giao tiếp & API#

User bấm “Đặt hàng”. Request từ mobile app đi vào hệ thống.

Thuật ngữHiểu nhanh.NET
API GatewayCửa ngõ duy nhất: routing, auth, rate limit. Client không cần biết có bao nhiêu service phía sau.YARP
Backend for Frontend (BFF)Mỗi loại client một gateway riêng: mobile cần payload gọn, web admin cần nhiều dữ liệu hơn.YARP / Minimal API
Rate LimitingGiới hạn số request trên một khoảng thời gian để bảo vệ hệ thống.Microsoft.AspNetCore.RateLimiting
REST vs gRPCREST (JSON/HTTP) cho client bên ngoài. gRPC (Protobuf/HTTP2) nhanh và có schema chặt, hợp cho service gọi nhau nội bộ.ASP.NET Core, Grpc.AspNetCore
API CompositionGateway hoặc một service gọi nhiều service rồi ghép kết quả, ví dụ màn “chi tiết đơn” cần Order + Payment + Shipping.
JWT / OAuth 2.0Gateway xác thực user một lần. Service phía sau chỉ cần verify token.Microsoft.AspNetCore.Authentication.JwtBearer

Order Service nhận request, lưu đơn trạng thái Pending. Bây giờ nó cần báo cho Payment và Inventory.

Chặng 3: Báo cho service khác — Messaging#

Order có thể gọi thẳng HTTP sang Payment. Nhưng nếu Payment đang chết, đơn hàng cũng chết theo. Cách phổ biến hơn là bắn event rồi đi tiếp.

Thuật ngữHiểu nhanh.NET
Synchronous vs AsynchronousSync: gọi rồi chờ kết quả (HTTP/gRPC). Async: gửi message rồi làm việc khác, bên nhận xử lý khi nó sẵn sàng.
Message Broker / Event BusTrung gian nhận và phân phối message: RabbitMQ, Kafka, Azure Service Bus.MassTransit, Wolverine
Queue vs TopicQueue là point-to-point: mỗi message có một người nhận. Topic là pub/sub: mọi subscriber đều nhận được. OrderCreated là topic, vì Payment, Inventory và Notification đều cần nghe.
Dead Letter Queue (DLQ)Message xử lý fail quá số lần retry sẽ bị đẩy vào đây, không làm tắc queue chính, chờ người xem.Có sẵn trong MassTransit/Wolverine
BackpressureKhi bên nhận xử lý không kịp, nó cần cách báo bên gửi chậm lại, thay vì để queue phình vô hạn.
WebhookHệ thống bên ngoài (ví dụ cổng thanh toán) gọi ngược lại HTTP endpoint của mình khi có sự kiện.

Chặng 4: Dữ liệu nằm rải rác — Nhất quán#

Đây là chặng khó nhất, và cũng là chỗ phỏng vấn system design hay đào sâu nhất.

Mỗi service giữ database riêng (Database per Service). Vậy “tạo đơn + trừ tiền + giữ hàng” không còn gói gọn trong một transaction nữa. Chuyện gì xảy ra nếu trừ tiền xong mà kho báo hết hàng?

Thuật ngữHiểu nhanh
Database per ServiceService khác không được đọc thẳng DB của mình. Muốn dữ liệu thì hỏi qua API hoặc nghe event.
Distributed Transaction / 2PCTransaction trải trên nhiều DB. Two-Phase Commit làm được, nhưng chậm, khóa tài nguyên, và hầu hết broker/cloud DB hiện đại không hỗ trợ. Thực tế ít ai dùng.
Eventual ConsistencyChấp nhận dữ liệu lệch nhau trong vài giây, miễn là cuối cùng sẽ khớp. Đơn hiện “Đang xử lý” trong lúc chờ Payment là ví dụ.
CAP TheoremKhi mạng bị chia cắt (Partition), bạn phải chọn giữa Consistency và Availability. Microservices thường nghiêng về A + eventual consistency.
SagaChuỗi transaction local, mỗi bước có một compensating transaction để hoàn tác. Kho hết hàng thì hoàn tiền, rồi hủy đơn.
Choreography vs OrchestrationChoreography: các service tự nghe event của nhau rồi phản ứng, không ai điều phối. Orchestration: một orchestrator (saga state machine) ra lệnh từng bước.
Dual WriteLỗi kinh điển: lưu DB xong rồi publish message. Nếu app crash ở giữa, DB có đơn mà không ai biết.
Transactional OutboxCách chữa Dual Write: ghi message vào bảng Outbox trong cùng transaction với dữ liệu, sau đó một background job đọc bảng này và publish.
Idempotent ConsumerBroker thường đảm bảo at-least-once, nên một message có thể đến hai lần. Consumer phải xử lý sao cho chạy hai lần cũng không trừ tiền hai lần.
CQRSTách model ghi (command) và model đọc (query). Phía đọc có thể là một Materialized View được dựng sẵn từ event, để khỏi phải join qua 4 service.
Event SourcingKhông lưu trạng thái hiện tại, mà lưu chuỗi event đã xảy ra, trạng thái được tính lại từ đó. Mạnh nhưng đắt. Chỉ dùng khi thật sự cần audit hoặc time-travel.

Outbox trong EF Core, ý tưởng cốt lõi chỉ có vậy:

await using var tx = await db.Database.BeginTransactionAsync(ct);

db.Orders.Add(order);
db.OutboxMessages.Add(OutboxMessage.From(new OrderCreated(order.Id, order.Total)));

await db.SaveChangesAsync(ct);   // đơn hàng và message commit cùng lúc
await tx.CommitAsync(ct);
// Background worker đọc OutboxMessages chưa gửi → publish lên broker → đánh dấu đã gửi

Và phía consumer, idempotent bằng cách nhớ message nào đã xử lý:

public async Task Consume(ConsumeContext<OrderCreated> context)
{
    var messageId = context.MessageId!.Value;
    if (await db.ProcessedMessages.AnyAsync(m => m.Id == messageId))
        return; // đã xử lý rồi, bỏ qua

    await paymentService.ChargeAsync(context.Message.OrderId, context.Message.Total);
    db.ProcessedMessages.Add(new ProcessedMessage(messageId));
    await db.SaveChangesAsync();
}
TIP

MassTransit và Wolverine đều có sẵn Outbox/Inbox. Trong dự án thật, bạn nên dùng sẵn thay vì tự viết. Các đoạn code trên chỉ để hiểu cơ chế. Đoạn consumer trên còn một lỗ: hai bản sao cùng message đến song song thì cả hai đều qua được bước AnyAsync. Bản thật cần unique constraint trên ProcessedMessages.Id và ghi nó cùng transaction với nghiệp vụ. Bài về Idempotent Consumer sẽ đi vào chi tiết.

Chặng 5: Có service bị chậm — Độ tin cậy#

Payment Service gọi sang cổng thanh toán bên ngoài, và cổng này thỉnh thoảng mất 30 giây mới trả lời. Nếu không làm gì, thread của Payment sẽ bị giữ hết, rồi Payment chết, rồi mọi thứ gọi Payment cũng chết theo. Đó là cascading failure.

Thuật ngữHiểu nhanh.NET
TimeoutKhông chờ vô hạn. Gọi ra ngoài lúc nào cũng phải có timeout.Polly, HttpClient.Timeout
RetryThử lại lỗi tạm thời, có exponential backoff + jitter để không dội bom service đang yếu. Chỉ retry thao tác idempotent.Polly
Circuit BreakerLỗi nhiều quá thì “ngắt cầu dao”: fail ngay trong một khoảng thời gian thay vì cứ gọi tiếp. Có 3 trạng thái: Closed → Open → Half-Open.Polly
BulkheadChia ngăn tài nguyên (connection pool, concurrency) để một dependency chậm không hút cạn tài nguyên của cả service.Polly
Health CheckEndpoint cho orchestrator biết service còn sống (liveness) và sẵn sàng nhận traffic chưa (readiness).AddHealthChecks()

Từ .NET 8, package Microsoft.Extensions.Http.Resilience gói sẵn mấy thứ này cho HttpClient:

builder.Services.AddHttpClient<PaymentGatewayClient>()
    .AddStandardResilienceHandler(); // timeout + retry + circuit breaker, dựng trên Polly v8

Chặng 6: Có bug trên production — Observability#

Khách báo: “Tôi bị trừ tiền mà đơn vẫn Pending”. Request đó đã đi qua 4 service và 1 broker. Log nằm ở 4 chỗ khác nhau. Bạn tìm bằng cách nào?

Thuật ngữHiểu nhanh.NET
Correlation IDMột ID gắn vào request từ gateway và truyền qua mọi service/message, để gom log của cùng một luồng.Activity.Current.TraceId
Centralized LoggingLog từ mọi service đổ về một chỗ (Seq, ELK, Loki), có thể search theo Correlation ID.Serilog
Distributed TracingVẽ lại toàn bộ đường đi của request thành các span, thấy được bước nào chậm hay lỗi.OpenTelemetry
MetricsSố liệu theo thời gian: request/giây, tỉ lệ lỗi, độ trễ p95/p99.OpenTelemetry + Prometheus
SLI / SLO / SLASLI là thứ đo được (p99 latency). SLO là mục tiêu nội bộ (p99 < 300ms). SLA là cam kết với khách, có phạt nếu vi phạm.
NOTE

Observability không phải “có log là đủ”. Câu hỏi đúng là: khi có sự cố chưa từng gặp, bạn có trả lời được “chuyện gì đang xảy ra” chỉ bằng dữ liệu hệ thống đang phát ra không.

Chặng 7: Deploy và vận hành#

Order Service vừa có bản mới. Làm sao deploy mà không làm gián đoạn 3 service còn lại?

Thuật ngữHiểu nhanh.NET / hạ tầng
ContainerizationĐóng gói service cùng runtime thành image chạy giống nhau ở mọi môi trường.Docker, dotnet publish /t:PublishContainer
Container OrchestrationTự động chạy, scale, restart container.Kubernetes
Service Discovery / RegistryService tìm địa chỉ của nhau bằng tên, không hardcode IP.K8s DNS, .NET Aspire
Externalized ConfigurationConfig nằm ngoài code, đổi theo môi trường, không phải build lại.IOptions<T>, Azure App Configuration
Sidecar / Service MeshMột container phụ chạy cạnh mỗi service lo mTLS, retry, metrics. Mesh là cả mạng lưới sidecar đó (Istio, Linkerd).
Blue-Green / CanaryBlue-Green: chạy song song bản cũ và mới, rồi chuyển toàn bộ traffic. Canary: chuyển dần 5% → 25% → 100%, theo dõi metrics từng bước.
Feature ToggleDeploy code nhưng tắt tính năng, bật lên khi sẵn sàng. Tách chuyện deploy khỏi chuyện release.Microsoft.FeatureManagement
Contract TestingTest để đảm bảo Order vẫn gửi đúng format mà Payment mong đợi, không cần dựng cả hệ thống.Pact
Backward CompatibilityBản mới của event/API không được làm vỡ consumer cũ: chỉ thêm field, không đổi nghĩa hay xóa field.

Mình đã viết về Feature Flags trong .NET ở một bài riêng, nếu bạn muốn đọc trước.

Nhìn lại cả hành trình của đơn hàng#

sequenceDiagram
    participant C as Client
    participant GW as API Gateway
    participant O as Order Service
    participant MB as Broker
    participant P as Payment
    participant I as Inventory

    C->>GW: POST /orders (JWT)
    GW->>O: route + Correlation ID
    O->>O: Lưu Order + Outbox (1 transaction)
    O-->>C: 202 Accepted (Pending)
    O->>MB: OrderCreated (outbox worker)
    MB->>P: OrderCreated
    P->>P: Idempotent check → charge (retry, circuit breaker)
    P->>MB: PaymentSucceeded
    MB->>I: PaymentSucceeded
    I-->>MB: InventoryFailed (hết hàng)
    MB->>P: InventoryFailed → Refund (compensate)
    MB->>O: InventoryFailed → Order Cancelled

Đọc lại buổi họp ở đầu bài: “Order service publish event qua outbox, payment consumer phải idempotent, nếu fail thì saga compensate, nhớ gắn correlation ID để trace.” Giờ mỗi cụm từ đều có một chỗ đứng trong hình trên.

Đọc tiếp trong series#

Series này có 22 bài, chia theo đúng các chặng ở trên:

  1. Nền tảng: bài này, và P2: Khi nào không nên dùng microservices
  2. Phân rã: DDD & Bounded Context, Database per Service, Strangler Fig & ACL
  3. Giao tiếp: API Gateway & BFF với YARP, Messaging, Kafka vs RabbitMQ
  4. Nhất quán dữ liệu: Saga, Outbox, Idempotent Consumer, CQRS, Event Sourcing
  5. Độ tin cậy: Retry, Timeout, Circuit Breaker, Bulkhead với Polly
  6. Observability: Logging, Tracing, Health check với OpenTelemetry
  7. Triển khai: Service Discovery, Service Mesh, Canary, Contract Testing
NOTE

Tham khảo khi viết series: microservices.vn (bộ thuật ngữ và pattern tiếng Việt rất đầy đủ), microservices.io của Chris Richardson, và Building Microservices (Sam Newman, 2nd edition).

Microservices (P1): Bản đồ thuật ngữ — đi theo một đơn hàng qua hệ thống
https://www.devwithxuan.com/vi/posts/microservices-terminology/
Tác giả
XuanPD
Ngày đăng
2025-09-23
Giấy phép
CC BY-NC-SA 4.0
Chia sẻ:

Đăng ký nhận bản tin

Nhận thông báo khi có bài viết mới. Không spam, hủy bất cứ lúc nào.

Bình luận