Chương đầu là một bài học về từ vựng, và nó quan trọng hơn vẻ ngoài của nó. Nhiều cuộc tranh luận về microservices thật ra là tranh luận giữa những định nghĩa khác nhau: người này hiểu là "nhỏ", người kia hiểu là "chạy trên Kubernetes", người thứ ba hiểu là "có repo riêng". Newman chốt thuật ngữ lại bằng vài thuộc tính, rồi dành phần còn lại của chương cho những ý tưởng thật sự quyết định việc tách có giúp ích hay không: information hiding (che giấu thông tin), cohesion (độ gắn kết), coupling (mức độ phụ thuộc) và ranh giới domain.

Đây cũng là chương ông bảo vệ monolith. Lập luận bảo vệ đó đến nay vẫn đứng vững. Ở năm 2026, khi modular monolith quay lại thành xu hướng, nó đọc lên không còn giống một lời rào đón mà giống ý chính.

Ứng với chương 1 trong sách.

Ý chính

Independent deployability là định nghĩa thật sự quan trọng

Newman định nghĩa microservices là các service có thể deploy độc lập, được mô hình hoá quanh một business domain, giao tiếp qua mạng và giấu dữ liệu của riêng mình. Trong các thuộc tính đó, ông chọn riêng independent deployability (khả năng deploy độc lập) là thứ cần mang theo nếu chỉ nhớ được một điều: bạn phải thường xuyên có khả năng sửa một service và release nó lên production mà không release thứ gì khác.

Yêu cầu duy nhất đó kéo theo hầu hết những yêu cầu còn lại. Để deploy một mình, service cần một contract rõ ràng và ổn định, để consumer không gãy khi phần bên trong thay đổi. Để giữ contract ổn định, service phải giấu cách triển khai, mà database là phần triển khai lớn nhất. Kích thước, ngôn ngữ và nền tảng hosting được cố ý để ra ngoài. Newman gọi kích thước là một trong những câu hỏi kém thú vị nhất và gợi ý hai câu hỏi tốt hơn: thực tế bạn vận hành được bao nhiêu service, và ranh giới đặt ở đâu để thay đổi luôn mang tính cục bộ?

Cắt theo năng lực nghiệp vụ, không theo tầng kỹ thuật

Hệ thống ba tầng kinh điển (team UI, team backend, team database) được tối ưu cho việc nhóm người theo kỹ năng, và định luật Conway phản chiếu điều đó vào kiến trúc. Cái giá lộ ra khi một tính năng đơn giản, ví dụ cho người dùng chọn danh mục yêu thích, cần thay đổi và release phối hợp qua cả ba tầng.

Cách Newman đề xuất là nhóm code theo chức năng nghiệp vụ, để một service sở hữu một lát cắt dọc mỏng: một ít UI, một ít logic, storage riêng. Cohesion về kỹ thuật được đổi lấy cohesion về nghiệp vụ. Ông nêu một điểm đáng nhấn mạnh: tách phía server mà vẫn để UI là một khối deploy lớn thường làm phí phần lớn lợi ích, vì tính năng nào cũng phải xếp hàng chờ một lần release frontend duy nhất.

Monolith có nhiều loại

Newman dùng chữ "monolith" để chỉ một đơn vị deploy, không phải để phán xét chất lượng code. Ông phân biệt:

Loại Là gì Quan điểm của ông
Single-process monolith Toàn bộ code deploy thành một process (có thể nhiều instance) Trường hợp phổ biến, và là lựa chọn hợp lệ
Modular monolith Một khối deploy được ghép từ các module tách bạch Thường là lựa chọn rất tốt; Shopify là ví dụ của ông
Distributed monolith Nhiều service nhưng phải deploy cùng nhau Lãnh đủ cái dở của cả hai bên
Hộp đen bên thứ ba Phần mềm của vendor hoặc SaaS bạn không sửa được Vẫn có thể tách dần quanh các cạnh của nó

Vấn đề monolith hay gây ra là thứ ông gọi là delivery contention: nhiều người cùng sửa một chỗ code, cùng chờ một lần release, không rõ ai sở hữu cái gì. Monolith không chắc chắn dẫn tới contention, microservices cũng không chữa khỏi nó, nhưng ranh giới service cho bạn những đường rõ hơn để phân chia quyền sở hữu. Trong khi đó monolith vẫn có những lợi thế thật: deploy, monitoring, testing và tái sử dụng code đơn giản hơn. Coi "monolith" là đồng nghĩa với "legacy", theo lời ông, chính là một vấn đề.

Bốn loại coupling

Cohesion là code thay đổi cùng nhau thì nằm cùng nhau; coupling là mức độ thay đổi một thứ buộc phải thay đổi chỗ khác. Newman đưa ra cách phân loại thực dụng của riêng ông cho hệ phân tán:

Coupling Ví dụ trong một nền tảng khoá học Cách giảm
Implementation Reporting đọc thẳng bảng của Enrollment service Giấu schema sau một API hoặc một tập dữ liệu đọc được thiết kế riêng
Temporal Checkout gọi Enrollment, Enrollment lại gọi đồng bộ sang Billing Cache, hoặc message bất đồng bộ
Deployment Mọi service cùng ship theo một release train cố định Release theo nhu cầu, khối deploy nhỏ hơn
Domain Certificate cần biết học viên đã hoàn thành khoá học Không tránh được, nhưng chỉ chia sẻ thứ bên nhận cần

Cách chữa implementation coupling là information hiding, ý tưởng của Parnas rằng ranh giới module nên giấu những gì dễ thay đổi. Newman bổ sung thiết kế outside-in: định hình interface của service bằng cách hỏi chính consumer họ cần gì, như khi thiết kế giao diện người dùng, thay vì phơi ra bất cứ thứ gì data model đang có.

Với domain coupling, mẹo của ông là gửi một message được thiết kế riêng thay vì cả một entity. Kho hàng không cần giá hay thông tin thẻ, chỉ cần biết lấy gì và giao đi đâu. Trong domain của chính mình, certificate service không cần cả bản ghi enrollment:

type Enrollment = {
  id: string;
  learnerId: string;
  courseId: string;
  pricePaidCents: number;
  couponCode: string | null;
  paymentMethodId: string;
  progress: Record<string, number>;
  completedAt: Date | null;
};

type CertificateRequested = {
  enrollmentId: string;
  learnerName: string;
  courseTitle: string;
  completedOn: string;
};

export function toCertificateRequest(
  enrollment: Enrollment,
  learnerName: string,
  courseTitle: string,
): CertificateRequested {
  if (!enrollment.completedAt) {
    throw new Error(`Enrollment ${enrollment.id} is not complete`);
  }
  return {
    enrollmentId: enrollment.id,
    learnerName,
    courseTitle,
    completedOn: enrollment.completedAt.toISOString().slice(0, 10),
  };
}

Message đó đi bằng lời gọi trực tiếp hay bằng một event mà certificate service subscribe là một quyết định riêng. Với event, chiều phụ thuộc bị đảo: phía enrollment không còn cần biết certificate service tồn tại.

DDD vừa đủ dùng

Newman mượn hai khái niệm từ DDD. Aggregate là một khái niệm domain có thật có vòng đời (đơn hàng, hoá đơn, lượt ghi danh), nên được đối xử như một state machine mà các bước chuyển trạng thái nằm cùng chỗ với trạng thái. Điểm then chốt: khi phần khác của hệ thống yêu cầu aggregate đổi trạng thái, aggregate có quyền từ chối. Bounded context là một ranh giới tổ chức lớn hơn, chứa một hoặc nhiều aggregate và giấu những mối quan tâm nội bộ khỏi phần còn lại của doanh nghiệp.

type EnrollmentState = "pending_payment" | "active" | "completed" | "refunded";

const allowedTransitions: Record<EnrollmentState, EnrollmentState[]> = {
  pending_payment: ["active"],
  active: ["completed", "refunded"],
  completed: [],
  refunded: [],
};

export class EnrollmentAggregate {
  constructor(
    readonly id: string,
    private state: EnrollmentState,
  ) {}

  get current(): EnrollmentState {
    return this.state;
  }

  transition(next: EnrollmentState): void {
    if (!allowedTransitions[this.state].includes(next)) {
      throw new Error(`Cannot move enrollment ${this.id} from ${this.state} to ${next}`);
    }
    this.state = next;
  }
}

Hướng dẫn của ông về cách ánh xạ: bắt đầu với service có kích thước bằng cả một bounded context, vì ít service thì dễ vận hành hơn trong lúc bạn còn đang học, rồi sau đó tách tiếp theo ranh giới aggregate. Kể cả khi đó, bạn vẫn có thể tiếp tục đưa ra một API thô (coarse) cho consumer, vì việc tách bên trong chỉ là chi tiết triển khai.

Đọc lại ở năm 2026

  • Modular monolith đã thành dòng chính. Newman đã gọi nó là lựa chọn rất tốt cho nhiều tổ chức từ năm 2019. Từ đó tới nay các team cởi mở hơn nhiều khi nói điều này công khai. Ví dụ hay được trích nhất là bài viết năm 2023 của Prime Video về việc gộp một service giám sát audio và video thành một process duy nhất, báo cáo tiết kiệm hơn 90 phần trăm chi phí hạ tầng. Hãy đọc kỹ: đó là một service trong một hệ thống lớn hơn, không phải cả công ty từ bỏ microservices.
  • Ranh giới module có thể được ép trong monolith. Với Java, Spring Modulith kiểm tra được việc các module chỉ dùng API public của nhau và phụ thuộc giữa các module không có vòng. Trong monorepo TypeScript, lint rule về module boundary của Nx hoặc dependency-cruiser làm việc tương tự. Điều này khiến con đường "modular monolith trước" thực tế hơn nhiều so với trước đây.
  • Cuốn sách anh em đã có bản mới. Building Microservices ra bản thứ hai vào tháng 8/2021, đi sâu hơn nhiều về kiểu giao tiếp, deployment và testing. Mình không tìm thấy phiên bản mới hơn của chính Monolith to Microservices.
  • Temporal coupling và tracing. Messaging bất đồng bộ giờ là lựa chọn mặc định chứ không còn lạ lẫm, và OpenTelemetry, đã tốt nghiệp CNCF vào tháng 5/2026 với traces, metrics và logs đều đã GA, là cách chuẩn để theo dõi một request đi qua nhiều service.
  • DDD vẫn là công cụ vẽ ranh giới. Bounded context và aggregate vẫn là từ vựng phần lớn team dùng; kỹ thuật để tìm ra chúng (nói ở phần sau) đã trưởng thành hơn, nhưng định nghĩa ở đây không đổi.

Áp dụng vào dự án

  1. Ghi ra những service nào hôm nay release một mình được. Nhóm nào luôn phải ship cùng nhau là distributed monolith; hãy coi nó là một khối cho tới khi sửa xong.
  2. Với mỗi service, liệt kê những ai đang đọc thẳng database của nó. Mỗi dòng là một implementation coupling cần gỡ.
  3. Thiết kế endpoint và event mới bằng cách hỏi team sử dụng họ cần gì, và gửi payload được thiết kế riêng thay vì cả entity.
  4. Mô hình hoá các đối tượng domain quan trọng thành aggregate với các bước chuyển tường minh, và từ chối bước chuyển không hợp lệ ngay trong code.
  5. Nếu đang dùng monolith, hãy làm nó modular trước: mỗi bounded context một thư mục hoặc package, cùng một lint rule cấm import xuyên vào phần nội bộ của nhau.
  6. Trước khi tách bất cứ thứ gì, hãy hỏi team bạn thực tế deploy, monitor và hỗ trợ được bao nhiêu service.

Tự kiểm tra

  1. Thuộc tính duy nhất Newman bảo hãy nhớ nếu không nhớ gì khác là gì? Independent deployability: thường xuyên sửa và release một service mà không phải deploy thứ gì khác.

  2. Vì sao kiến trúc ba tầng khiến tính năng đơn giản trở nên đắt đỏ? Chức năng nghiệp vụ trải qua mọi tầng, nên một tính năng cần thay đổi và release phối hợp từ nhiều team.

  3. Distributed monolith là gì? Nhiều service nhưng phải deploy cùng nhau, gánh chi phí của hệ phân tán mà không có lợi ích của tính độc lập.

  4. Cho một ví dụ về implementation coupling và cách sửa. Một service đọc thẳng bảng của service khác; giấu schema sau một API hoặc một tập dữ liệu đọc riêng, ổn định.

  5. Nên chọn kích thước cho những service đầu tiên thế nào? Theo cả một bounded context, rồi tách tiếp theo ranh giới aggregate khi đã xử lý được nhiều service hơn.

Đọc thêm