+9

🧩🧠 Database per Service: nguyên tắc bị phá nhiều nhất trong Microservices - Microservice Architecture P6

Database per Service: nguyên tắc bị phá nhiều nhất trong Microservices

Hãy tưởng tượng bạn bước vào phòng họp dự án sau một đêm trực sự cố "rụng tóc". Hệ thống thương mại điện tử của công ty đột ngột trả về hàng loạt lỗi 500 ở luồng checkout ngay khi chương trình Flash Sale bắt đầu. Khi tra cứu log từ Order Service, bạn liên tục bắt gặp thông báo lỗi quen thuộc: Connection pool exhausted.

Sau hai tiếng phân tích chuyên sâu, đội ngũ phát triển phát hiện ra thủ phạm: Một kỹ sư dữ liệu ở bộ phận Business Intelligence (BI) vừa kích hoạt một câu lệnh SQL quét qua toàn bộ bảng tồn kho để tạo báo cáo doanh thu tháng. Sự cố này đáng lẽ đã không ảnh hưởng đến luồng mua hàng nếu hệ thống được cô lập tốt. Tuy nhiên, trên thực tế, cả Order ServiceInventory Service đều đang kết nối trực tiếp vào một cơ sở dữ liệu PostgreSQL dùng chung. Câu lệnh query báo cáo quá nặng đã khóa chặt (lock) các bảng dữ liệu dùng chung, kéo sụp toàn bộ hệ thống giao dịch của doanh nghiệp.

Đây là một thực tế kinh điển trong thiết kế hệ thống. Nguyên tắc Database per Service (Mỗi dịch vụ sở hữu một cơ sở dữ liệu riêng) luôn được nhấn mạnh như một điều luật tối thượng trong mọi giáo trình Microservices. Nhưng khi đi vào thực chiến tại các doanh nghiệp, đây lại là nguyên tắc bị phá vỡ nhiều nhất.

Tại sao các đội ngũ kỹ sư lại dễ dàng từ bỏ nguyên tắc này để quay về với mô hình Shared Database? Tác hại thực sự của nó lớn đến mức nào ở môi trường production? Và làm thế nào để chúng ta giải quyết bài toán truy vấn chéo dữ liệu một cách tối ưu nhất? Bài viết này sẽ phân tích chi tiết từ góc độ thiết kế hệ thống thực chiến.


Câu chuyện từ Production: Lối tắt dẫn đến "Monolith phân tán"

Khi bắt đầu chuyển đổi hệ thống từ Monolith sang Microservices để chuẩn bị cho đợt tăng trưởng người dùng mới, ban quản trị quyết định chia nhỏ hệ thống thành hai dịch vụ chính: Order Service (quản lý đơn hàng) và Inventory Service (quản lý kho). Mục tiêu là để hai nhóm phát triển độc lập có thể nâng cấp và scale hệ thống một cách linh hoạt.

Tuy nhiên, trong quá trình thiết kế, các nhà phát triển nhanh chóng vấp phải một bài toán nghiệp vụ cơ bản: Khi khách hàng tạo đơn hàng, hệ thống cần kiểm tra xem sản phẩm còn hàng trong kho hay không. Đồng thời, khi hiển thị chi tiết đơn hàng cho người dùng, hệ thống cần đính kèm thông tin tên sản phẩm, mã SKU và số lượng còn lại trong kho.

Để kịp tiến độ bàn giao (Go-live) và tiết kiệm thời gian thiết kế, đội ngũ kỹ thuật quyết định chọn một "lối tắt":

+------------------+         +---------------------+
|  Order Service   |         |  Inventory Service  |
+--------+---------+         +----------+----------+
         |                              |
         | (Direct SQL Joins)           | (Direct Read/Write)
         +---------------+--------------+
                         |
                         v
             +----------------------+
             |   Shared Postgres    |
             |  - table: orders     |
             |  - table: inventory  |
             +----------------------+

Họ giữ nguyên một cơ sở dữ liệu PostgreSQL dùng chung. Order ServiceInventory Service chạy độc lập ở tầng ứng dụng, nhưng kết nối trực tiếp vào cùng một database instance. Nhờ đó, việc kiểm tra tồn kho và join thông tin sản phẩm được thực hiện vô cùng mượt mà thông qua một câu lệnh SQL duy nhất:

SELECT o.id, o.total_amount, i.sku_code, i.stock_quantity 
FROM orders o 
INNER JOIN inventory i ON o.product_id = i.product_id 
WHERE o.user_id = ?;

Giải pháp này hoạt động rất trơn tru trong vài tháng đầu. Cho đến một ngày, để chuẩn bị cho mô hình phân phối đa kho hàng (Multi-warehouse), đội ngũ phát triển Inventory Service quyết định tái cấu trúc cơ sở dữ liệu. Họ thực hiện thay đổi schema bảng inventory, đổi tên trường sku_code thành supplier_sku và chuyển thông tin tồn kho sang một bảng mới là warehouse_stocks để tối ưu hóa việc quản lý.

Một kịch bản di chuyển dữ liệu (migration) được thực hiện thành công trên Inventory Service. Nhưng ngay khi deploy, toàn bộ hệ thống đặt hàng sập hoàn toàn. Order Service liên tục ném ra ngoại lệ do không tìm thấy trường sku_code trong bảng inventory. Đội ngũ phát triển Order Service hoàn toàn không được cảnh báo trước về thay đổi này vì họ nghĩ rằng schema của kho hàng thuộc quyền quản lý riêng của dịch vụ Inventory.

Hệ thống lúc này đã biến thành một Distributed Monolith (Monolith phân tán) - mô hình tồi tệ nhất trong kiến trúc phần mềm. Bạn phải gánh chịu toàn bộ sự phức tạp của hệ thống phân tán (độ trễ mạng, cấu hình CI/CD phức tạp, khó khăn trong việc debug) nhưng lại mất đi lợi ích lớn nhất của Microservices: Khả năng deploy độc lập.


Niềm tin phổ biến: Tại sao "Shared Database" luôn nghe có vẻ đúng?

Trước khi chỉ trích quyết định dùng chung database, chúng ta cần hiểu tại sao lựa chọn này lại cực kỳ hấp dẫn đối với các lập trình viên khi mới bắt đầu dự án.

1. Sự tiện lợi của SQL Join và tính nhất quán tức thời (ACID)

Trong thế giới của cơ sở dữ liệu quan hệ (RDBMS), việc liên kết dữ liệu giữa các bảng bằng các phép toán JOIN là cực kỳ tự nhiên và có hiệu năng rất cao. Hơn thế nữa, khi dùng chung một database, các giao dịch tài chính hoặc cập nhật trạng thái kho/đơn hàng được đảm bảo bởi tính chất ACID cục bộ. Nếu việc trừ kho thất bại, giao dịch tạo đơn hàng sẽ tự động rollback ngay lập tức mà không cần bất kỳ cơ chế bù trừ (compensating transaction) phức tạp nào.

2. Định kiến về hạ tầng vật lý

Nhiều kỹ sư có một quan niệm sai lầm rằng: Database per Service có nghĩa là mỗi microservice bắt buộc phải chạy trên một cụm cơ sở dữ liệu vật lý riêng biệt (chẳng hạn như mỗi service có một cụm AWS RDS Postgres độc lập).

Suy nghĩ này dẫn đến việc ước tính chi phí hạ tầng tăng vọt và làm tăng độ phức tạp cho đội ngũ vận hành (DevOps) khi phải quản lý hàng chục kết nối, cơ chế backup và giám sát cho hàng chục database server khác nhau. Nhìn vào bài toán chi phí đó, việc gom tất cả vào một database server dùng chung dường như là lựa chọn thực tế nhất.

3. Nỗi sợ về hiệu năng truy vấn liên dịch vụ (API Join)

Khi phân tách database, lập trình viên thường lo lắng rằng để hiển thị một trang danh sách đơn hàng kèm thông tin sản phẩm, họ sẽ phải gọi API HTTP sang dịch vụ sản phẩm cho từng dòng đơn hàng. Điều này tạo ra lỗi thiết kế kinh điển mang tên N+1 Query Problem ở tầng ứng dụng, khiến độ trễ (latency) của hệ thống tăng lên theo cấp số nhân do overhead của các lượt gọi mạng (network round-trip).


Tại sao Shared Database lại đổ vỡ khi scale trong Production?

Sự tiện lợi ban đầu của Shared Database nhanh chóng bị lu mờ bởi những rủi ro vận hành nghiêm trọng khi hệ thống bắt đầu tăng trưởng.

1. Phá vỡ tính Đóng gói và Ràng buộc Lược đồ (Schema Coupling)

Khi hai dịch vụ đọc và ghi trực tiếp vào cùng một database, cơ sở dữ liệu vô tình trở thành một API ẩn (implicit contract) nối liền hai dịch vụ. Trong nguyên lý thiết kế microservices, mọi tương tác giữa các service phải được thực hiện thông qua các giao diện lập trình ứng dụng tường minh (Explicit APIs như REST, gRPC hoặc Message Queue).

Việc chia sẻ database khiến ranh giới domain bị xóa nhòa. Bạn không còn khả năng thay đổi cấu trúc bảng dữ liệu của mình mà không cần hỏi ý kiến các đội phát triển khác, biến mọi quy trình refactor code trở thành một cơn ác mộng phối hợp giữa nhiều phòng ban.

2. Tranh chấp tài nguyên (Resource Contention) và Điểm nghẽn duy nhất (Single Point of Failure)

Dù bạn có bao nhiêu microservice chạy ở tầng ứng dụng đi chăng nữa, nếu chúng cùng kết nối vào một database instance, thì database đó chính là điểm nghẽn cổ chai (bottleneck) lớn nhất của hệ thống.

Một truy vấn báo cáo nặng từ một dịch vụ nội bộ có thể chiếm dụng toàn bộ tài nguyên CPU, Disk I/O hoặc làm cạn kiệt Connection Pool của database. Khi đó, dịch vụ cốt lõi xử lý các giao dịch trực tiếp từ khách hàng cũng sẽ bị ảnh hưởng nghiêm trọng và ngừng hoạt động.

3. Sự khác biệt về đặc tính dữ liệu và khả năng scale độc lập

Mỗi loại dữ liệu trong hệ thống có tính chất rất khác nhau:

  • Dữ liệu đơn hàng (Order) cần tính nhất quán cao và lưu trữ dạng quan hệ để phục vụ đối soát tài chính.
  • Dữ liệu giỏ hàng (Cart) cần tốc độ ghi cực nhanh nhưng có thể chấp nhận mất mát nhẹ (thích hợp với Redis hoặc MongoDB).
  • Dữ liệu mô tả sản phẩm (Product) có tần suất đọc rất cao nhưng rất ít khi thay đổi (thích hợp với in-memory cache hoặc tài liệu phi cấu trúc).

Nếu gom tất cả vào một cơ sở dữ liệu duy nhất, bạn sẽ bị bó buộc vào một công nghệ lưu trữ không tối ưu cho tất cả các tác vụ và không thể scale riêng lẻ các thành phần có lưu lượng truy cập đột biến.


Góc nhìn mới: Phân biệt Độc lập Logical (Schema) và Độc lập Vật lý

Để áp dụng đúng nguyên tắc "Database per Service" mà không làm tăng chi phí hạ tầng một cách phi lý, chúng ta cần thay đổi tư duy thiết kế: Độc lập Logical quan trọng hơn độc lập vật lý.

Khi bắt đầu xây dựng hệ thống microservices hoặc chuyển đổi từ monolith, bạn không cần phải mua ngay lập tức các server cơ sở dữ liệu riêng biệt. Thay vào đó, hãy thiết lập sự cô lập nghiêm ngặt ở tầng Logical (lược đồ cơ sở dữ liệu).

+------------------+         +---------------------+
|  Order Service   |         |  Inventory Service  |
+--------+---------+         +----------+----------+
         |                              |
         | (User: order_user)           | (User: inventory_user)
         |                              |
  +------v-------+               +------v-------+
  | order_schema |               | invent_schema|
  +--------------+               +--------------+
  +---------------------------------------------+
  |              PostgreSQL Cluster             |
  +---------------------------------------------+

Phương án triển khai Logical Isolation:

  1. Phân vùng Schema riêng biệt: Trên cùng một PostgreSQL cluster, hãy tạo các schema riêng biệt (ví dụ: order_schemainventory_schema).
  2. Phân quyền truy cập nghiêm ngặt: Cấu hình user kết nối cho Order Service chỉ có toàn quyền trên order_schema và hoàn toàn không có quyền đọc/ghi trên inventory_schema. Ngược lại, user của Inventory Service chỉ được phép tương tác với inventory_schema.
  3. Cấm tuyệt đối các câu lệnh Join chéo schema: Bất kỳ truy vấn nào cố gắng thực hiện JOIN giữa hai schema này sẽ bị cơ chế phân quyền của database chặn đứng ngay lập tức.

Với cách tiếp cận này, về mặt vật lý, bạn vẫn chỉ quản lý một cụm database duy nhất để tiết kiệm chi phí và nguồn lực vận hành. Nhưng về mặt kiến trúc phần mềm, hai service đã hoàn toàn tách biệt về mặt dữ liệu. Khi hệ thống phát triển đến quy mô lớn hơn và một schema trở thành nút thắt cổ chai về hiệu năng, việc tách schema đó ra một database server vật lý độc lập trở nên vô cùng dễ dàng vì không có bất kỳ dòng code nào thực hiện join chéo giữa chúng.


Giải pháp thực chiến: Xử lý bài toán Join dữ liệu phân tán

Khi dữ liệu đã được cô lập (dù là ở dạng logical hay vật lý), chúng ta không thể sử dụng câu lệnh SQL JOIN truyền thống nữa. Để giải quyết bài toán truy vấn thông tin chéo giữa các dịch vụ, kỹ sư hệ thống thường áp dụng ba mô hình kiến trúc sau đây:

1. API Composition (Gom dữ liệu ở API Gateway hoặc Aggregator Service)

Đây là cách tiếp cận trực quan nhất. Khi người dùng yêu cầu xem chi tiết đơn hàng, một dịch vụ trung gian (thường là API Gateway hoặc một Backend-for-Frontend - BFF service) sẽ thực hiện hai bước:

  1. Gọi API đến Order Service để lấy thông tin đơn hàng gốc (ví dụ: ngày đặt, tổng tiền, danh sách ID sản phẩm).
  2. Lấy danh sách ID sản phẩm từ kết quả trên, gọi một API gộp (Bulk Read API) sang Product/Inventory Service để lấy thông tin chi tiết của các sản phẩm đó, sau đó tiến hành map dữ liệu ở tầng ứng dụng trước khi trả về cho client.
 Client
   |
   | 1. Get Order Details
   v
+------------------+     2. Get Order Info      +-----------------+
|   API Gateway    | -------------------------> |  Order Service  |
|   (Composer)     |                            +-----------------+
|                  |     3. Get Product Details +-----------------+
|                  | -------------------------> | Product Service |
+------------------+                            +-----------------+
  • Ưu điểm: Đơn giản, dễ hiện thực hóa, dữ liệu trả về luôn là dữ liệu mới nhất (real-time).
  • Nhược điểm: Tăng độ trễ của hệ thống do phải gọi nhiều API qua mạng. Nếu không thiết kế cẩn thận, nó có thể dẫn đến lỗi N+1 API calls.
  • Khi nào nên dùng: Phù hợp cho các trang hiển thị chi tiết của một bản ghi duy nhất, hoặc khi kích thước tập dữ liệu nhỏ và không yêu cầu hiệu năng đọc cực cao.

2. Data Replication (Sao chép dữ liệu cần thiết thông qua Event)

Thay vì truy vấn dữ liệu từ dịch vụ khác vào thời điểm cần thiết, dịch vụ của bạn sẽ chủ động lưu trữ một bản sao (replica) của những thông tin cần dùng ngay trong database nội bộ của mình.

Hãy xem xét luồng hoạt động sau:

  • Khi một sản phẩm được tạo mới hoặc thay đổi thông tin (ví dụ: cập nhật tên sản phẩm hoặc giá bán), Product Service sẽ phát đi một sự kiện (Event) như ProductPriceUpdated vào Message Broker (như Kafka hoặc RabbitMQ).
  • Order Service đăng ký lắng nghe sự kiện này. Khi nhận được event, nó sẽ cập nhật thông tin tên sản phẩm và giá bán tương ứng vào một bảng chứa dữ liệu sao chép (product_cache hoặc bảng products nội bộ của schema order_db).
  • Khi người dùng truy vấn danh sách đơn hàng, Order Service chỉ cần thực hiện SQL JOIN cục bộ với bảng dữ liệu sao chép đó ngay trong cơ sở dữ liệu của mình mà không cần gọi bất kỳ API nào ra bên ngoài.
+-----------------+   Publish Event    +---------------+
| Product Service | -----------------> | Message Broker|
+-----------------+                    +-------+-------+
                                               |
                                               | Listen Event
                                               v
+-----------------+                    +---------------+
|    Order DB     | <----------------- | Order Service |
| (Local Replica) |  Update local copy +---------------+
+-----------------+
  • Ưu điểm: Hiệu năng truy vấn cực nhanh (local read), loại bỏ hoàn toàn sự phụ thuộc runtime vào các service khác khi đọc dữ liệu.
  • Nhược điểm: Chấp nhận Eventual Consistency (nhất quán gián tiếp). Dữ liệu ở Order Service có thể bị lệch một vài mili giây hoặc vài giây so với nguồn dữ liệu gốc của Product Service trước khi sự kiện được xử lý xong. Ngoài ra, việc lưu trữ trùng lặp dữ liệu làm tăng dung lượng ổ đĩa.
  • Khi nào nên dùng: Cực kỳ hiệu quả cho các màn hình danh sách, tìm kiếm, hoặc các thông tin mang tính chất lịch sử (ví dụ: giá sản phẩm tại thời điểm mua hàng cần được lưu giữ nguyên trạng để đối soát, không thay đổi theo giá hiện tại của cửa hàng).

3. CQRS (Command Query Responsibility Segregation)

CQRS là một mô hình nâng cao, tách biệt hoàn toàn kiến trúc của luồng ghi dữ liệu (Command) và luồng đọc dữ liệu (Query).

Trong mô hình này:

  • Các service như Order Service, Inventory Service chỉ chịu trách nhiệm tiếp nhận và xử lý các thao tác ghi (tạo đơn hàng, trừ kho) vào cơ sở dữ liệu ghi (Write DB) được tối ưu hóa cho các giao dịch nhanh.
  • Mọi thay đổi dữ liệu từ các Write DB này đều được gửi dưới dạng event stream về một cơ sở dữ liệu đọc chuyên biệt (Read DB) dùng chung (ví dụ: Elasticsearch phục vụ tìm kiếm toàn văn, hoặc MongoDB phục vụ truy vấn tài liệu phi cấu trúc).
  • Tầng ứng dụng khi cần hiển thị giao diện tổng hợp phức tạp chỉ việc truy vấn trực tiếp vào Read DB này.
  User Write                       User Read
      |                                |
      v                                v
+-------------+                  +------------+
|  Write DB   |                  |  Read DB   |
|  (Postgres) |                  | (ES/Mongo) |
+------+------+                  +-----+------+
       |                               ^
       | Sync via Events               |
       +-------------------------------+
  • Ưu điểm: Khả năng scale luồng đọc và luồng ghi độc lập một cách vượt trội. Cho phép xây dựng các câu truy vấn phức tạp kết hợp dữ liệu từ nhiều nguồn khác nhau với tốc độ phản hồi cực nhanh.
  • Nhược điểm: Độ phức tạp kiến trúc rất cao. Cần duy trì cơ chế đồng bộ dữ liệu liên tục và xử lý các kịch bản mất mát sự kiện hoặc đồng bộ trễ.
  • Khi nào nên dùng: Dành cho các hệ thống lớn, có tỷ lệ đọc/ghi chênh lệch rất cao (ví dụ: các trang thương mại điện tử lớn với hàng triệu lượt xem sản phẩm nhưng số lượng đơn đặt hàng nhỏ hơn nhiều lần).

Phân tích Trade-offs: Cái giá của sự tự chủ

Mọi quyết định kiến trúc hệ thống đều là sự đánh đổi. Khi quyết định tách rời cơ sở dữ liệu để tuân thủ nguyên tắc Database per Service, chúng ta đang đánh đổi điều gì?

Tiêu chí Shared Database (Dùng chung) Database per Service (Tách biệt)
Tính tự chủ (Autonomy) Thấp: Bị ràng buộc chặt chẽ bởi schema của nhau. Cao: Mỗi đội tự do thay đổi cấu trúc DB và deploy độc lập.
Tính nhất quán dữ liệu Tức thời (ACID): Rất dễ dàng rollback giao dịch khi có lỗi. Gián tiếp (Eventual Consistency): Cần thiết kế các cơ chế bù trừ.
Hiệu năng truy vấn Rất nhanh (SQL Join): Tận dụng sức mạnh tối ưu của RDBMS. Phụ thuộc vào pattern: API Composition (Chậm) vs Replication/CQRS (Nhanh).
Độ phức tạp vận hành Thấp: Quản lý và backup một cụm database duy nhất. Cao: Quản lý nhiều DB, giám sát hạ tầng và luồng dữ liệu đồng bộ.

Nhìn vào bảng so sánh trên, rõ ràng Database per Service không phải là một "bữa trưa miễn phí". Lợi ích lớn nhất mà nó mang lại là khả năng mở rộng quy mô hệ thống về mặt con người (Team scaling)khả năng chịu lỗi cô lập (Fault isolation). Nếu dự án của bạn chỉ có 1-2 nhóm phát triển nhỏ và lượng truy cập không quá lớn, việc vội vàng phân tách cơ sở dữ liệu vật lý có thể tạo ra gánh nặng vận hành vượt quá năng lực của đội ngũ.


Những kịch bản đổ vỡ thực tế khi đồng bộ dữ liệu

Khi đã quyết định áp dụng các mô hình đồng bộ dữ liệu phân tán như Replication hay CQRS, bạn cần chuẩn bị sẵn sàng cho những tình huống lỗi luôn có thể xảy ra trong môi trường mạng không tin cậy.

1. Độ trễ đồng bộ (Consistency Lag) và trải nghiệm người dùng

Một lỗi phổ biến là hệ thống gặp độ trễ lớn trong việc xử lý hàng đợi sự kiện (message queue backup). Ví dụ: Người bán cập nhật giá của một sản phẩm từ $100 xuống $80. Sự kiện ProductPriceUpdated được gửi vào Kafka nhưng do hệ thống đang bị quá tải, sự kiện này bị trễ 5 phút mới đến được Order Service. Trong khoảng thời gian 5 phút đó, khách hàng mua sản phẩm vẫn nhìn thấy giá cũ ($100) trên giao diện thanh toán.

Giải pháp: Tầng nghiệp vụ phải được thiết kế để xử lý bất nhất quán. Khi khách hàng bấm thanh toán, hệ thống có thể thực hiện một lệnh kiểm tra nhanh (double-check) bằng cách gọi API trực tiếp đến dịch vụ giá để xác thực mức giá cuối cùng trước khi trừ tiền, chấp nhận đánh đổi thêm một chút latency ở bước thanh toán để đảm bảo an toàn tài chính.

2. Sự kiện đến sai thứ tự (Out-of-order Events)

Trong môi trường phân tán, các thông điệp truyền tải qua mạng có thể đến đích không theo thứ tự gửi ban đầu. Ví dụ:

  1. Sự kiện A: Cập nhật tên sản phẩm thành "Bàn phím cơ A" (gửi lúc 10:00:00).
  2. Sự kiện B: Cập nhật tên sản phẩm thành "Bàn phím cơ B" (gửi lúc 10:00:05).

Nếu do sự cố mạng, Sự kiện B lại đến đầu tiên và được lưu vào database của Order Service, sau đó Sự kiện A mới đến. Nếu không có cơ chế kiểm tra, dữ liệu mới ("Bàn phím cơ B") sẽ bị ghi đè bởi dữ liệu cũ ("Bàn phím cơ A").

Giải pháp: Mỗi event cần đính kèm một số phiên bản tăng dần (monotonic version) hoặc nhãn thời gian (timestamp) tạo ra từ nguồn phát. Dịch vụ tiêu thụ sự kiện chỉ thực hiện cập nhật cơ sở dữ liệu nếu phiên bản của sự kiện nhận được lớn hơn phiên bản hiện tại đang lưu trữ trong database cục bộ.


Lời kết

Database per Service không đơn thuần là một quyết định kỹ thuật hay việc lựa chọn công nghệ lưu trữ. Đó là triết lý thiết kế hướng tới sự tự chủ độc lập của các thành phần trong hệ thống và ranh giới phân định trách nhiệm của các đội ngũ phát triển.

Việc lười biếng áp dụng Shared Database để tiết kiệm thời gian ban đầu thường là một khoản nợ kỹ thuật có lãi suất rất cao mà hệ thống sẽ phải trả giá đắt khi scale. Tuy nhiên, việc áp dụng máy móc nguyên tắc này bằng cách dựng lên hàng loạt database server vật lý độc lập ngay từ ngày đầu tiên khi chưa hiểu rõ domain nghiệp vụ cũng là một sai lầm phổ biến khác. Bắt đầu từ sự cô lập chặt chẽ ở tầng Logical (Schema) chính là bước đệm thông minh và thực tế nhất cho hành trình tiến lên kiến trúc Microservices của mọi doanh nghiệp.

Khi cơ sở dữ liệu của bạn đã được phân tách thành công thành các phân vùng độc lập, một thách thức lớn hơn sẽ xuất hiện: Làm thế nào để thực hiện một giao dịch nghiệp vụ phức tạp đi qua nhiều dịch vụ (như tạo đơn hàng, trừ tiền ví điện tử, trừ kho sản phẩm) mà vẫn đảm bảo tính toàn vẹn dữ liệu khi có sự cố xảy ra giữa chừng? Chúng ta không thể dùng rollback của database được nữa. Tập tiếp theo trong series sẽ giúp bạn giải quyết bài toán giao dịch phân tán này.


Góc nhìn thêm: Database per Service không phải nghi thức tôn giáo, mà là ranh giới ownership dữ liệu

Rất nhiều bài nói về nguyên tắc này như thể chỉ cần "mỗi service một database" là kiến trúc sẽ sạch. Thực tế, giá trị thật của nguyên tắc này không nằm ở số lượng database, mà ở chỗ ownership trở nên rõ ràng.

Khi một service sở hữu dữ liệu của mình, nó có quyền:

  • đổi schema theo nhịp riêng
  • tối ưu truy vấn cho use case riêng
  • áp dụng consistency model phù hợp với miền riêng
  • chịu trách nhiệm đầy đủ cho logic đọc/ghi của miền đó

Ngược lại, nếu nhiều service còn lén đọc bảng của nhau, ownership chỉ còn là lời nói. Lúc đó mọi thay đổi schema đều trở thành change management across teams, và rốt cuộc bạn quay lại kiểu phụ thuộc chéo y như shared database trong monolith.

Nhìn thêm: Điều đắt nhất của nguyên tắc này là query không còn "tiện"

Khi không được join trực tiếp giữa các miền, team phải học cách nghĩ khác:

  • dùng read model riêng
  • chấp nhận duplicate dữ liệu có chủ đích
  • tạo projection phục vụ màn hình đọc
  • thiết kế event flow để đồng bộ state ở mức chấp nhận được

Đây là cái giá mà nhiều đội ngũ không ước lượng trước. Họ thích ý tưởng ownership, nhưng chưa chuẩn bị cho việc reporting, search, dashboard hay admin screen sẽ khó hơn nhiều khi dữ liệu không còn nằm chung một chỗ.

🧭 Học theo lộ trình

Nếu bạn muốn tiếp tục hành trình nâng cấp tư duy thiết kế hệ thống và làm chủ các kỹ thuật thiết kế cơ sở dữ liệu phân tán nâng cao, hãy tiếp tục với:

🚀 Dev Insider https://www.patreon.com/techcraft_official/posts/vi-sao-dev-ra-161163881?collection=2220113

📘 Facebook https://www.facebook.com/techcraft.official

🎥 YouTube https://www.youtube.com/@techcraft.official

🎵 TikTok https://www.tiktok.com/@techcraft.official

Hiểu trade-off. Thiết kế tốt hơn.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí