3.12 Kiến trúc hướng sự kiện và nhắn tin
Tổng quan và động lực
Kiến trúc hướng sự kiện (EDA) là một phong cách trong đó các thành phần giao tiếp bằng cách tạo ra và phản ứng với các sự kiện thay vì gọi trực tiếp lẫn nhau. Một sự kiện là một sự thật: điều gì đó đã xảy ra, như “OrderPlaced” (đơn hàng đã được đặt) hay “PaymentCaptured” (thanh toán đã được ghi nhận). Bên sản xuất thông báo sự thật rồi đi tiếp, và bất kỳ số lượng bên tiêu thụ nào phản ứng theo lịch của riêng họ mà bên sản xuất không biết ai đang lắng nghe. Đây là một tư thế khác với các lệnh gọi yêu cầu-và-trả lời ở chương 2.3, nơi một bên gọi yêu cầu một dịch vụ cụ thể làm điều gì đó và chờ câu trả lời.
Với một tổ chức lớn, sức hấp dẫn là sự tách rời ở quy mô lớn. Khi bạn có hàng chục đội và hàng trăm dịch vụ, nối mọi thứ bằng các lệnh gọi điểm-nối-điểm trực tiếp tạo ra một mạng lưới giòn nơi thay đổi của đội này làm hỏng đội khác và không ai truy được vì sao. Sự kiện cho phép các đội tích hợp qua một luồng sự thật dùng chung thay vì qua phần bên trong của nhau, và một bên tiêu thụ mới tham gia bằng cách đăng ký, mà bên sản xuất không đổi một dòng mã. Thuộc tính đó, hơn là thông lượng thô, là lý do các cách tiếp cận hướng sự kiện tiếp tục lan rộng trong doanh nghiệp thay thế các tích hợp rối rắm và trong chính phủ kết nối các cơ quan mà mỗi cái sở hữu hệ thống riêng.
Khu vực công nhận được lợi ích thứ hai dễ bị đánh giá thấp: một bản ghi bền vững, có thứ tự về những gì đã xảy ra là một tài sản kiểm toán và minh bạch. Khi một công dân hỏi vì sao một quyết định trợ cấp ra như vậy, một nhật ký bất biến của các sự kiện dẫn đến nó trả lời trực tiếp. Nhưng thiết kế hướng sự kiện không miễn phí và không phải lúc nào cũng đúng: các luồng bất đồng bộ khó truy vết hơn, khó suy luận hơn, và dễ bị áp dụng quá mức. Chương này có chính kiến về việc khi nào sự tách rời và quy mô xứng đáng với độ phức tạp thêm vào, và khi nào một lệnh gọi đồng bộ đơn giản đã phục vụ bạn tốt hơn. Nó xây trên thực tế hệ thống phân tán của chương 3.3, vì vậy hãy đọc chương đó trước nếu bạn chưa đọc.
Các nguyên tắc chính
- Sự kiện là sự thật, không phải chỉ thị. Một sự kiện nói điều gì đã xảy ra; một lệnh yêu cầu điều gì đó xảy ra. Giữ chúng riêng biệt, và đặt tên sự kiện ở thì quá khứ.
- Tách rời là mục đích. Bên sản xuất không nên biết hay quan tâm ai tiêu thụ sự kiện của họ. Nếu họ làm vậy, bạn có liên kết khoác bộ đồ nhắn tin.
- Thiết kế cho giao nhận ít-nhất-một-lần. Giao nhận đúng-một-lần là huyền thoại. Làm mọi bên tiêu thụ lũy đẳng để trùng lặp vô hại.
- Thứ tự là một bảo đảm bạn phải trả tiền. Bạn có thứ tự trong một phân vùng, không phải xuyên một chủ đề. Hãy chọn khóa phân vùng có chủ đích.
- Lược đồ là hợp đồng. Hình dạng của một sự kiện là một giao diện công khai; hãy tiến hóa nó với sự cẩn thận như bạn dành cho một API đã công bố.
- Bất đồng bộ không có nghĩa là không quan sát được. Nếu bạn không thể theo dõi một thông điệp từ đầu đến cuối, bạn không thể vận hành hệ thống.
- Độ phức tạp phải được giành lấy. Event sourcing, CQRS và saga mạnh và tốn kém; hãy với tới chúng khi vấn đề đòi hỏi, không phải theo mặc định.
Khuyến nghị
Phân biệt sự kiện, lệnh và thông điệp trước khi xây bất cứ thứ gì
Ba từ này bị dùng thay thế lẫn nhau, và sự nhầm lẫn gây ra những sai lầm thiết kế thực sự. Một lệnh là một yêu cầu làm điều gì đó (“CapturePayment”), hướng đến một trình xử lý, và nó có thể bị từ chối. Một sự kiện là một thông báo rằng điều gì đó đã xảy ra (“PaymentCaptured”), phát cho bất cứ ai quan tâm, và nó không thể bị từ chối vì sự thật đã đúng rồi. Một thông điệp là phong bì trung tính mang một trong hai qua đường truyền. Sự phân biệt định hình liên kết: lệnh ghép bên gửi với một bên nhận và kết quả cụ thể, trong khi sự kiện từ bỏ quyền kiểm soát điều gì xảy ra tiếp theo. Hãy đặt tên sự kiện ở thì quá khứ, và khi bạn bắt gặp mình đang công bố một “sự kiện” mà thực ra có nghĩa “làm ơn đi làm việc cụ thể này”, bạn đã viết một lệnh trá hình.
Chọn hàng đợi, nhật ký và xuất bản/đăng ký có chủ đích
Không phải mọi nhắn tin đều có cùng hình dạng, và chọn sai là một sai lầm sớm phổ biến. Một hàng đợi thông điệp giao mỗi thông điệp cho một bên tiêu thụ và thường xóa nó sau khi xử lý, phù hợp với phân phối công việc: nhiều công nhân kéo tác vụ, mỗi tác vụ làm một lần. Một nhật ký sự kiện bền vững (một luồng) giữ sự kiện theo thứ tự và cho phép nhiều bên tiêu thụ độc lập đọc theo tốc độ riêng, phát lại lịch sử từ bất kỳ điểm nào, phù hợp với phân phối sự kiện và kiểm toán. Xuất bản/đăng ký để bên sản xuất xuất bản vào một chủ đề và nhiều bên đăng ký mỗi bên nhận bản sao riêng. Quy tắc thực tế: nếu thông điệp là một tác vụ mà một công nhân nên hoàn thành, hãy với tới hàng đợi; nếu nó là một sự thật nhiều bên có thể quan tâm bây giờ hoặc sau này, hãy với tới nhật ký bền vững, vốn cũng cho bạn khả năng phát lại để khôi phục và đưa bên tiêu thụ mới vào. Xem chương 3.4 để biết những lựa chọn này tương tác thế nào với chiến lược lưu trữ dữ liệu của bạn.
Ưu tiên biên đạo cho quyền tự chủ, điều phối cho kiểm soát
Khi một quy trình kinh doanh trải qua nhiều dịch vụ, bạn phối hợp nó theo một trong hai cách. Trong biên đạo, mỗi dịch vụ phản ứng với sự kiện và phát sự kiện của riêng nó, không có bộ não trung tâm: tách rời tối đa và tốt cho quyền tự chủ của đội, nhưng quy trình tổng thể chỉ tồn tại như hành vi nổi lên mà không nơi đơn lẻ nào mô tả. Trong điều phối, một bộ điều phối trung tâm dẫn dắt các bước và biết toàn bộ luồng: dễ giám sát và sửa đổi hơn, với cái giá là một thành phần mà mọi bước phụ thuộc. Một mặc định tốt là biên đạo cho các phản ứng liên quan lỏng (“khi một đơn hàng được giao, dịch vụ khách hàng thân thiết thưởng điểm”) và điều phối cho một giao dịch xác định với điều kiện thành công rõ ràng và nhu cầu báo cáo trạng thái. Đừng để một quy trình quan trọng chỉ sống như kiến thức truyền miệng rải rác qua mười trình xử lý sự kiện.
Chỉ với tới event sourcing và CQRS khi chúng xứng đáng với chỗ đứng
Event sourcing lưu trạng thái dưới dạng chuỗi sự kiện chỉ-thêm thay vì một ảnh chụp hiện tại bạn ghi đè, và bạn dựng lại trạng thái hiện tại bằng cách phát lại chúng. Mặt lợi là một dấu vết kiểm toán hoàn hảo, khả năng dựng lại bất kỳ trạng thái quá khứ nào, và các truy vấn theo thời gian; cái giá là bạn đánh phiên bản lược đồ sự kiện mãi mãi, xử lý phát lại và ảnh chụp, và mang một mô hình tinh thần mà hầu hết lập trình viên chưa bao giờ dùng. CQRS (Phân tách Trách nhiệm Lệnh và Truy vấn) tách mô hình ghi khỏi một hoặc nhiều mô hình đọc để đọc và ghi mở rộng và tiến hóa độc lập; nó kết hợp tự nhiên với event sourcing nhưng không đòi hỏi nó. Cả hai tỏa sáng với các miền có nhu cầu kiểm toán, tuân thủ hoặc truy vấn phức tạp thực sự, đó là lý do tài chính chịu quy định và chính phủ thấy chúng đáng công. Với một dịch vụ tạo-đọc-cập nhật-xóa đơn giản chúng là độ phức tạp ngẫu nhiên bạn sẽ hối tiếc, vì vậy hãy áp dụng chúng cho lát của miền cần chúng, không phải cả hệ thống theo phản xạ.
Quản lý giao dịch phân tán bằng saga, không phải cam kết hai pha
Bạn thường không thể bọc một giao dịch nguyên tử quanh nhiều dịch vụ và cơ sở dữ liệu. Cam kết hai pha phân tán giữ khóa qua mạng, cắt giảm tính sẵn sàng, và mở rộng kém, nên hiếm khi phù hợp với một hệ thống hướng sự kiện. Mẫu saga thay thế nó: mô hình hóa giao dịch như một chuỗi các giao dịch cục bộ, mỗi giao dịch phát một sự kiện kích hoạt giao dịch kế tiếp, và cho mỗi bước một hành động bù trừ hoàn tác nó nếu một bước sau thất bại. Nếu “đặt trước hàng tồn kho” thành công nhưng “tính phí thẻ” thất bại, một bù trừ giải phóng hàng tồn kho. Saga có thể được biên đạo hoặc điều phối, và điều phối thường thắng với bất cứ thứ gì bạn phải giám sát. Vì saga chấp nhận nhất quán cuối cùng, hệ thống đi qua các trạng thái trung gian (“đã đặt trước nhưng chưa trả tiền”) trước khi hội tụ, nên hãy thiết kế trải nghiệm người dùng và dấu vết kiểm toán để hiển thị các trạng thái “đang tiến hành” và “đã bù trừ” một cách trung thực. Chương 3.3 bao quát cùng mảng này từ góc nhìn hệ thống phân tán.
Thiết kế cho giao nhận ít-nhất-một-lần và làm cho bên tiêu thụ lũy đẳng
Hệ thống thông điệp không thể thực sự giao đúng-một-lần qua các thất bại, vì xác nhận nói “tôi đã xử lý cái này” bản thân nó có thể bị mất, buộc phải giao lại. Những gì bạn có thể đạt được là giao nhận ít-nhất-một-lần với xử lý lũy đẳng, tạo ra các hiệu ứng đúng-một-lần. Tính lũy đẳng nghĩa là xử lý cùng một sự kiện hai lần để lại cùng kết quả như xử lý một lần; hãy đạt được nó bằng một khóa lũy đẳng trên mỗi sự kiện và một bản ghi những gì bạn đã xử lý, để một bản trùng lặp được nhận ra và bỏ đi. Giao nhận nhiều-nhất-một-lần (bắn rồi quên, không giao lại) đơn giản hơn nhưng âm thầm mất thông điệp, nên hãy dành nó cho dữ liệu bạn có thể chịu mất. Hãy nhìn nhãn “đúng-một-lần” mà một số nhà cung cấp quảng cáo với sự nghi ngờ: nó thường có nghĩa đúng-một-lần trong ranh giới của một hệ thống dưới các điều kiện cụ thể, không phải bảo đảm từ đầu đến cuối mà cụm từ ngụ ý.
Kiểm soát thứ tự bằng phân vùng, và biết các nhóm người tiêu thụ của bạn
Thứ tự không phải toàn cục và miễn phí; nó là cục bộ và phải trả tiền. Một luồng được chia thành các phân vùng, và bạn có thứ tự trong một phân vùng, không phải xuyên chủ đề. Sự kiện được định tuyến đến một phân vùng bằng một khóa phân vùng, nên chọn khóa đó là cách bạn kiểm soát cái gì giữ thứ tự: đặt khóa theo ID khách hàng và các sự kiện của một khách hàng giữ thứ tự với nhau, trong khi các khách hàng khác nhau xử lý song song. Nhóm người tiêu thụ cho phép một tập công nhân chia sẻ các phân vùng của một chủ đề, mỗi phân vùng do một công nhân xử lý, đó là cách bạn mở rộng thông lượng trong khi bảo toàn thứ tự theo phân vùng; đây là nơi khả năng mở rộng gặp tính đúng đắn, liên hệ với chương 3.5. Hãy chọn một khóa phân vùng phản ánh yêu cầu thứ tự thực của bạn và phân bố tải đều, vì một khóa dồn phần lớn lưu lượng vào một phân vùng tạo ra một điểm nóng mà không số lượng công nhân nào giải tỏa được.
Coi lược đồ là hợp đồng với sổ đăng ký và quy tắc tiến hóa
Cấu trúc của một sự kiện là một giao diện đã công bố được tiêu thụ bởi các đội bạn có thể không bao giờ gặp, nên thay đổi nó bất cẩn làm hỏng họ từ xa. Hãy đặt lược đồ sự kiện của bạn vào một sổ đăng ký lược đồ, một danh mục dùng chung lưu từng lược đồ và thực thi quy tắc tương thích khi một bên sản xuất cố đổi một cái. Hãy áp dụng một chính sách tường minh: thay đổi tương thích ngược (thêm một trường tùy chọn) được phép; thay đổi phá vỡ (xóa trường, đổi kiểu, đổi tên) đòi hỏi một phiên bản lược đồ mới và một kế hoạch di chuyển. Điều này cho phép bên sản xuất tiến hóa mà không cần triển khai đồng bộ qua mọi bên tiêu thụ, đúng là lý do bạn chọn sự kiện. Cùng kỷ luật đánh phiên bản giao diện từ chương 2.3 áp dụng, vì một lược đồ sự kiện là một API dưới tên khác.
Bảo đảm giao nhận bằng hộp thư đi giao dịch, và xử lý thất bại tường minh
Một lỗi kinh điển: dịch vụ của bạn ghi vào cơ sở dữ liệu rồi xuất bản một sự kiện, và nó sập giữa hai bước, nên cơ sở dữ liệu đã đổi nhưng sự kiện không bao giờ được gửi đi. Hộp thư đi giao dịch sửa điều này bằng cách ghi sự kiện vào một bảng hộp thư đi trong cùng giao dịch cơ sở dữ liệu với thay đổi trạng thái, để chúng cùng cam kết hoặc cùng thất bại; một bộ chuyển tiếp riêng sau đó đọc hộp thư đi và xuất bản lên bộ môi giới, thường dùng thu thập thay đổi dữ liệu để theo đuôi nhật ký cơ sở dữ liệu. Với thất bại khi tiêu thụ, một hàng đợi thư chết giữ các thông điệp liên tục thất bại để một thông điệp độc (cái sẽ không bao giờ thành công, có lẽ vì dị dạng) không chặn hàng đợi phía sau nó mãi mãi. Thêm áp suất ngược để một bên sản xuất nhanh không áp đảo một bên tiêu thụ chậm: giới hạn hàng đợi của bạn, và làm chậm hoặc bỏ tải khi chúng đầy thay vì cạn bộ nhớ. Bốn cơ chế này phân biệt một bản trình diễn với một hệ thống bạn có thể vận hành lúc 3 giờ sáng.
Làm các luồng bất đồng bộ quan sát được từ đầu đến cuối
Chi phí khó nhất của việc đi hướng sự kiện là một hành động kinh doanh đơn lẻ nay rải khắp bên sản xuất, bộ môi giới và bên tiêu thụ mà không có ngăn xếp gọi nào nối chúng. Hãy lan truyền một ID tương quan qua mọi sự kiện để bạn có thể theo một luồng logic qua mọi bước nhảy, cùng kỷ luật mà chương 3.3 quy định cho các lệnh gọi đồng bộ. Theo dõi độ trễ người tiêu thụ (mỗi bên tiêu thụ đang đọc chậm hơn thời gian thực bao nhiêu) như một chỉ số hạng nhất, vì độ trễ tăng là cảnh báo sớm nhất của bạn về rắc rối, và giám sát độ sâu hàng đợi thư chết, độ trễ xử lý và tỷ lệ giao lại. Không có điều này, một sự kiện lặng lẽ không được tiêu thụ trở thành một lỗi vô hình xuất hiện nhiều ngày sau dưới dạng dữ liệu bị thiếu.
Đánh đổi: ưu và nhược điểm
| Cách tiếp cận | Ưu điểm | Nhược điểm / chi phí |
|---|---|---|
| Yêu cầu/trả lời đồng bộ | Đơn giản để suy luận, kết quả tức thì, truy vết dễ | Liên kết thời gian chặt, thất bại lan truyền, mở rộng hạn chế |
| Hướng sự kiện (pub/sub trên nhật ký) | Tách rời, mở rộng độc lập, phát lại, dấu vết kiểm toán | Nhất quán cuối cùng, truy vết khó hơn, nhiều bộ phận chuyển động hơn |
| Hàng đợi thông điệp (phân phối công việc) | San tải, đệm, thân thiện với áp suất ngược | Một bên tiêu thụ cho mỗi thông điệp, kém phù hợp với phát sóng |
| Event sourcing + CQRS | Lịch sử đầy đủ, truy vấn theo thời gian, đọc/ghi mở rộng độc lập | Đánh phiên bản lược đồ mãi mãi, độ phức tạp phát lại, đường cong học dốc |
| Saga (so với cam kết hai pha) | Mở rộng được, sẵn sàng, không khóa phân tán | Nhất quán cuối cùng, logic bù trừ, khó suy luận hơn |
Căng thẳng trung tâm là giữa sự tách rời và khả năng thấu hiểu. Mỗi sự kiện bạn thêm làm lỏng liên kết giữa bên sản xuất và bên tiêu thụ, mua quyền tự chủ của đội và mở rộng độc lập, và đồng thời nó xóa một dòng của câu chuyện mà một lệnh gọi đồng bộ sẽ kể rõ ràng: luồng trở thành nổi lên, sống trong các tương tác thay vì trong bất kỳ tệp nào. Hãy giải quyết điều này bằng cách chọn lọc: dùng sự kiện ở nơi tách rời thực sự hoàn vốn, như tích hợp xuyên ranh giới đội, phân phối ra nhiều bên tiêu thụ, đệm các đợt tăng tải, và kiểm toán. Giữ các lệnh gọi đồng bộ ở nơi bạn cần câu trả lời tức thì và một mô hình tinh thần đơn giản, như đọc dữ liệu để hiển thị một trang. Chế độ thất bại cần tránh là biến mọi lệnh gọi hàm nội bộ thành một sự kiện rồi gọi đó là kiến trúc, cùng phán đoán kiến trúc mà chương 3.2 đòi hỏi ở mọi mẫu bạn áp dụng.
Câu hỏi để thảo luận với đội của bạn
Với tương tác cụ thể này, chúng ta có thực sự cần một sự kiện, hay một lệnh gọi đồng bộ sẽ rõ ràng và an toàn hơn? Bỏ qua câu hỏi này là cách một hệ thống tích tụ độ phức tạp ngẫu nhiên. Phép thử trung thực là liệu bên sản xuất cần một kết quả trả về ngay bây giờ (một lệnh gọi) hay đang thông báo một sự thật mà người khác có thể phản ứng theo thời gian của họ (một sự kiện). Hãy mang tương tác cụ thể, không phải một sở thích chung, và hỏi bạn được gì từ sự tách rời và mất gì về độ rõ ràng truy vết. Nếu bên gọi bị chặn chờ “sự kiện” được xử lý, bạn đã xây một lệnh gọi đồng bộ chậm, khó gỡ lỗi và trả thêm tiền cho đặc quyền đó. Mặc định cho các tương tác nội bộ, cùng đội, cần-câu-trả-lời-ngay nên là lệnh gọi trực tiếp; dành sự kiện cho nơi liên kết lỏng xứng đáng với chi phí của nó.
Điều gì xảy ra khi một bên tiêu thụ nhận cùng một sự kiện hai lần, và chúng ta đã thực sự kiểm thử chưa? Giao nhận ít-nhất-một-lần bảo đảm bản trùng lặp sẽ xảy ra, nên mọi bên tiêu thụ phải lũy đẳng, nhưng tính lũy đẳng dễ tuyên bố và dễ làm sai. Hãy đi qua một bên tiêu thụ thật và truy chính xác một lần giao thứ hai được nhận ra và vô hiệu hóa thế nào, dù bằng khóa lũy đẳng, nhật ký sự kiện đã xử lý, hay một thao tác vốn lũy đẳng. Hãy mang kết quả của một bài kiểm thử thực nơi bạn giao lại một lô và xác nhận không có khoản phí nhân đôi, bản ghi trùng hay thông báo lặp lại. Hãy đặc biệt chú ý đến các tác dụng phụ rời khỏi cơ sở dữ liệu của bạn, như email, thanh toán và lệnh gọi bên thứ ba, vì đó là nơi các lỗi không lũy đẳng làm hại khách hàng trực tiếp. Nếu đội của bạn không chỉ ra được một bài kiểm thử chứng minh an toàn trước bản trùng lặp, hãy giả định bạn không an toàn.
Khi một luồng sự kiện hỏng trên môi trường thực, mất bao lâu để chúng ta nhận ra, và chúng ta có thể truy vết một thông điệp từ đầu đến cuối không? Thất bại bất đồng bộ thì lặng lẽ, nên một bên tiêu thụ âm thầm ngừng xử lý có thể không được nhận ra cho đến khi dữ liệu thiếu thành lời phàn nàn của khách hàng hay khoảng trống kiểm toán. Hãy hỏi tín hiệu sớm nhất của bạn là gì, và liệu bạn có giám sát độ trễ người tiêu thụ và độ sâu hàng đợi thư chết như chỉ số cảnh báo thay vì bảng điều khiển không ai xem không. Hãy mang một sự cố thật hoặc một bài tập ngày diễn tập và đo mất bao lâu để theo một ID tương quan duy nhất qua bên sản xuất, bộ môi giới và mọi bên tiêu thụ. Nếu câu trả lời là “chúng tôi grep nhiều dịch vụ và đoán”, khả năng quan sát của bạn chưa sẵn sàng cho độ phức tạp bạn đã nhận. Trong các lĩnh vực chịu quy định, dựng lại chính xác một thông điệp đã di chuyển thế nào thường là yêu cầu tuân thủ, không phải điều dễ chịu.
Chúng ta sẽ tiến hóa một lược đồ sự kiện mà nhiều đội đã tiêu thụ thế nào mà không làm hỏng đội nào? Hình dạng của một sự kiện là hợp đồng công khai, và một khi hàng chục bên tiêu thụ phụ thuộc vào nó, một thay đổi trông vô hại có thể làm hỏng các hệ thống bạn chưa từng nghe tới, từ xa, không có trình biên dịch nào cảnh báo. Căng thẳng là thật: bên sản xuất muốn di chuyển nhanh và dọn dẹp sự kiện của họ, trong khi mọi bên tiêu thụ muốn hình dạng đóng băng mãi mãi, vì vậy hãy thống nhất trước thay đổi nào an toàn (thêm trường tùy chọn) và thay đổi nào đòi hỏi phiên bản mới cùng cửa sổ di chuyển (xóa trường, đổi kiểu, đổi tên). Hãy mang danh sách thực các bên tiêu thụ cho mỗi chủ đề, liệu một sổ đăng ký lược đồ có thực thi quy tắc tương thích hôm nay hay hình dạng đổi bằng thỏa thuận không chính thức, và hai phiên bản có thể chạy song song bao lâu trong một cuộc di chuyển. Trong một doanh nghiệp lớn hoặc một thỏa thuận chia sẻ dữ liệu chính phủ giữa các cơ quan, một lược đồ âm thầm bị phá vỡ có thể làm hỏng bản ghi trong các hệ thống bạn không sở hữu và hiện ra sau như một thất bại kiểm toán, vì vậy hãy coi việc thực thi tương thích là quản trị, không phải phép lịch sự.
Với giao dịch nhiều dịch vụ quan trọng nhất của chúng ta, mỗi hành động bù trừ thực sự hoàn tác điều gì, và người dùng cùng kiểm toán viên sẽ thấy những trạng thái trung gian nào? Saga đổi ảo tưởng an ủi về một giao dịch nguyên tử lấy chuỗi các bước cục bộ mà mỗi bước đều có thể thất bại, nên hệ thống thực sự đi qua các trạng thái như “đã đặt trước nhưng chưa trả tiền” và “đã tính phí nhưng chưa giao” trước khi hội tụ, và giả vờ khác đi là cách bạn phát hành một saga rò rỉ tiền hoặc bỏ rơi bản ghi. Hãy đi qua quy trình thật từ đầu đến cuối, gọi tên bù trừ cho mọi bước (cái gì giải phóng hàng tồn kho, cái gì hoàn tiền thẻ), và quyết định liệu một saga điều phối bạn giám sát được có thắng một biên đạo nổi lên mà không nơi đơn lẻ nào mô tả. Hãy mang các trường hợp thất bại bạn đã thực sự kiểm thử, không phải đường hạnh phúc, và xác nhận trải nghiệm người dùng cùng dấu vết kiểm toán hiển thị các trạng thái “đang tiến hành” và “đã bù trừ” một cách trung thực thay vì giấu chúng. Trong hệ thống tài chính, trợ cấp hay thuế, cơ quan quản lý sẽ hỏi bản ghi trông thế nào ở mỗi khoảnh khắc trung gian và ai chịu trách nhiệm về bù trừ, nên các trạng thái của saga bản thân chúng là một sản phẩm tuân thủ.
Ai vận hành bộ môi giới hay nhật ký sự kiện, và chúng ta đã tính chi phí thực của việc vận hành nó so với một phương án được quản lý chưa? Xương sống nhắn tin không phải hạ tầng miễn phí xuất hiện một khi bạn vẽ nó lên sơ đồ: ai đó vá nó, mở rộng phân vùng, điều chỉnh lưu giữ, phản hồi khi nó báo động lúc 3 giờ sáng, và sở hữu năng lực cùng các chế độ thất bại của nó. Hãy quyết định có chủ đích giữa việc tự lưu trữ một bộ môi giới mã nguồn mở và mua một dịch vụ được quản lý, cân nhắc kiểm soát và nơi lưu trữ dữ liệu so với gánh nặng vận hành và chi phí giấy phép, và trung thực về việc đội bạn có đủ chiều sâu để chạy tốt một nhật ký phân tán không. Hãy mang tổng chi phí sở hữu: tải trực ca, kỹ năng chuyên môn cần thiết, hóa đơn lưu giữ và lưu trữ, và việc bộ môi giới ngừng hoạt động làm gì với mọi luồng phụ thuộc. Với một doanh nghiệp câu trả lời định hình nhiệm vụ của đội nền tảng, và với một cơ quan chính phủ, quy tắc mua sắm, yêu cầu chủ quyền dữ liệu và nhu cầu cứng nhắc tránh khóa chặt nhà cung cấp có thể áp đảo lựa chọn rẻ nhất, vì vậy hãy phơi bày những ràng buộc đó trước khi bạn cam kết với một công nghệ bạn sẽ chạy một thập kỷ.
Góc nhìn theo lĩnh vực
Công ty khởi nghiệp. Nguồn lực khan hiếm nhất của bạn là sự chú ý kỹ thuật, nên hãy ở lại đồng bộ cho đến khi việc phân phối ra nhiều bên thực sự gây đau. Khi ba thứ cần phản ứng với một hành động, hãy xuất bản một sự kiện duy nhất (như “OrderPlaced”) vào một hàng đợi hoặc nhật ký được quản lý thay vì dựng cụm bộ môi giới riêng, và giữ thanh toán cùng bất cứ thứ gì bạn cần câu trả lời tức thì dưới dạng lệnh gọi trực tiếp. Đừng áp dụng event sourcing, CQRS hay saga để trông tinh vi; độ phức tạp đó sẽ vượt khỏi một đội nhỏ xíu và làm chậm chính sự lặp mà bạn đang cạnh tranh.
Doanh nghiệp nhỏ. Bạn không có chuyên gia nhắn tin và không có hứng thú chạy Kafka, vì vậy hãy coi tích hợp bất đồng bộ là thứ bạn mua, không vận hành. Dựa vào các sự kiện mà công cụ hiện có của bạn đã phát (webhook, các hàng đợi tích hợp sẵn trong nhà cung cấp đám mây hay nền tảng SaaS) và để một dịch vụ được quản lý sở hữu giao nhận, lưu giữ và hệ thống ống nước lũy đẳng. Hãy đóng khung lựa chọn mua-so-với-xây một cách trung thực: một nhúm trình xử lý webhook đáng tin cậy thắng một bộ môi giới làm riêng mà bạn không thể bố trí nhân sự hay gỡ lỗi lúc 3 giờ sáng.
Doanh nghiệp lớn. Vấn đề của bạn là chi phí tích hợp qua nhiều đội, nên phần thưởng là một nền tảng dùng chung được quản trị: một nhật ký sự kiện bền vững, một sổ đăng ký lược đồ với chính sách tương thích được thực thi, quyền sở hữu chủ đề rõ ràng, và các mẫu lũy đẳng cùng hộp thư đi chuẩn để mỗi đội không phát minh lại. Đây là bối cảnh nơi thay thế một ESB giòn hoặc một mạng lưới liên kết điểm-nối-điểm cộng dồn thành khoản tiết kiệm thực, và nơi các saga điều phối với trạng thái hiển thị cho phép nhân viên vận hành quản lý các quy trình nhiều bước. Hãy ngân sách tường minh khoản đầu tư khả năng quan sát và quản trị lược đồ, vì ở quy mô này một thất bại bên tiêu thụ âm thầm trở thành dữ liệu thiếu qua hàng chục hệ thống.
Chính phủ. Một nhật ký sự kiện bền vững, có thứ tự là tài sản kiểm toán và minh bạch: nó trả lời “vì sao quyết định này ra như vậy” bằng một chuỗi sự thật bất biến, nên hãy nghiêng về event sourcing ở nơi trách nhiệm giải trình đòi hỏi. Trao đổi dữ liệu liên cơ quan cần giao nhận ít-nhất-một-lần với khử trùng lặp trên một ID sự kiện ổn định để một bản ghi được giao lại không bao giờ tạo hồ sơ trùng lặp, và mua sắm nên cân nhắc chủ quyền dữ liệu, công khai các hạn chế của bộ môi giới, và tính di động so với khóa chặt. Hãy công bố, ở nơi phù hợp, luồng hoạt động thế nào và một công dân có thể phản đối kết quả tự động ra sao, và giữ nhật ký sự kiện làm bản ghi có thể bảo vệ mà các cơ quan giám sát sẽ yêu cầu kiểm tra.
Ví dụ
Công ty khởi nghiệp. Một công ty khởi nghiệp thương mại điện tử nhỏ bắt đầu với một luồng đồng bộ: thanh toán gọi dịch vụ thanh toán và chờ. Khi lớn lên, họ muốn email xác nhận đơn hàng, cập nhật kho hàng và chương trình khách hàng thân thiết phản ứng với các giao dịch mua, và nối mỗi cái thành một lệnh gọi đồng bộ khác trong thanh toán làm thanh toán chậm và mong manh. Đội xuất bản một sự kiện “OrderPlaced” duy nhất vào một nhật ký bền vững và để ba bên tiêu thụ độc lập phản ứng, nên thanh toán lại nhanh và thêm phản ứng thứ tư sau này không cần đổi gì ở nó. Họ giữ việc ghi nhận thanh toán đồng bộ, vì họ cần câu trả lời có-hay-không trước khi xác nhận đơn hàng, đúng là lằn ranh đúng để vạch ở quy mô của họ.
Doanh nghiệp lớn. Một công ty bảo hiểm toàn cầu đang chìm trong các tích hợp điểm-nối-điểm và một bus dịch vụ doanh nghiệp (ESB) lão hóa, một trung tâm mà mọi hệ thống định tuyến qua và đã trở thành nút thắt cùng điểm lỗi đơn. Họ chuyển sang một nhật ký sự kiện bền vững nơi mỗi miền xuất bản các sự thật của nó (hợp đồng đã phát hành, yêu cầu bồi thường đã nộp, thanh toán đã thực hiện) và các đội tiêu thụ đăng ký những gì họ cần. Một saga yêu cầu bồi thường, được điều phối để nhân viên vận hành thấy trạng thái từng yêu cầu, phối hợp việc thanh toán nhiều bước với các bù trừ cho các bước thất bại. Một sổ đăng ký lược đồ cho phép đội hợp đồng tiến hóa sự kiện của họ mà không cần triển khai đồng bộ qua bốn mươi hệ thống tiêu thụ, chính xác là sự giòn mà ESB cũ áp đặt.
Chính phủ. Một cơ quan thuế quốc gia phải cho công dân và kiểm toán viên một câu trả lời có thể bảo vệ cho “vì sao đánh giá của tôi ra như thế này.” Họ mô hình hóa miền đánh giá bằng event sourcing, nên mọi thay đổi là một sự kiện bất biến trong một nhật ký có thứ tự và đánh giá hiện tại là một lần phát lại các sự kiện đó; khi một công dân tranh chấp một con số, một cán bộ xử lý dựng lại trạng thái chính xác tại bất kỳ ngày quá khứ nào và chỉ ra chuỗi sự thật đã tạo ra nó. Trao đổi dữ liệu liên cơ quan chạy qua các chủ đề bền vững với giao nhận ít-nhất-một-lần, và bên tiêu thụ của mỗi cơ quan khử trùng lặp trên một ID sự kiện để một bản ghi được giao lại không bao giờ tạo hồ sơ trùng lặp. Nhật ký sự kiện kiêm dấu vết kiểm toán mà các cơ quan giám sát đòi hỏi, biến một nghĩa vụ tuân thủ thành sản phẩm phụ của thiết kế.
Luận chứng kinh doanh: động lực, ROI và TCO
Lợi tức của kiến trúc hướng sự kiện bị chi phối bởi một thứ: chi phí tích hợp theo thời gian. Tích hợp điểm-nối-điểm làm chi phí đó tăng theo số kết nối, vốn tăng nhanh hơn số hệ thống, nên tích hợp trở thành loại thuế ăn mất năng lực phân phối của bạn. Sự kiện làm phẳng điều này, vì các đội tích hợp qua một luồng sự thật dùng chung, một bên tiêu thụ mới tham gia bằng cách đăng ký, và bên sản xuất tiến hóa sau một lược đồ có phiên bản, nên chi phí biên của tích hợp kế tiếp giảm mạnh. Đó là câu chuyện ROI cốt lõi để kể với ban lãnh đạo: không phải hiệu năng thô, mà là sự giảm cộng dồn của chi phí thay đổi qua nhiều đội.
Hãy gọi tên tổng chi phí sở hữu một cách trung thực để bạn được tin. Bạn nhận hạ tầng bộ môi giới để chạy, quản trị lược đồ để duy trì, và một đường cong học vận hành dốc hơn, vì các hệ thống bất đồng bộ thực sự khó gỡ lỗi hơn, nên hãy ngân sách cho khoản đầu tư khả năng quan sát ngay từ đầu. Hãy cân nhắc những điều này so với chi phí của việc không áp dụng sự kiện ở nơi chúng phù hợp: một tầng tích hợp hóa thạch nơi mọi thay đổi là một dự án phối hợp nhiều đội, một ESB cũ đã thành nút thắt không ai dám chạm, và không thể thêm năng lực mà không làm xáo trộn cái cũ. Với doanh nghiệp thay thế các dây điểm-nối-điểm giòn và chính phủ xây dựng dấu vết kiểm toán bền vững, phần thưởng mạnh nhất ở nơi nhu cầu kiểm toán và tách rời là thật. Ở nơi những nhu cầu đó vắng mặt, câu trả lời trung thực là một thiết kế đồng bộ đơn giản hơn có TCO thấp hơn, và bạn nên nói vậy.
Phản mẫu và cạm bẫy
- Khối đơn phân tán trá hình. Các dịch vụ phải triển khai cùng nhau và phụ thuộc vào sự kiện nội bộ của nhau. Bạn đã thêm một bộ môi giới nhưng giữ liên kết, nên bạn có nhược điểm của cả hai phong cách.
- Sự kiện như lệnh. Công bố các “sự kiện” thực chất có nghĩa “làm ơn làm việc cụ thể này cho tôi”, tái tạo liên kết chặt với độ trễ thêm và khả năng truy vết tệ hơn.
- Giả định giao nhận đúng-một-lần. Các bên tiêu thụ hỏng, tính phí đôi, hoặc nhân đôi bản ghi khi bộ môi giới chắc chắn giao lại một thông điệp.
- Event sourcing ở mọi nơi. Áp dụng event sourcing và CQRS cho các miền tạo-đọc-cập nhật-xóa đơn giản chưa bao giờ cần lịch sử, mua độ phức tạp dốc mà không có lợi ích.
- Không quản trị lược đồ. Bên sản xuất đổi hình dạng sự kiện tự do, làm hỏng bên tiêu thụ hạ nguồn từ xa mà không có kiểm tra tương thích.
- Bỏ qua hộp thư đi. Ghi vào cơ sở dữ liệu và xuất bản sự kiện thành hai bước riêng, nên một lần sập giữa chúng âm thầm mất sự kiện hoặc phát ra sự kiện ma.
- Không xử lý thư chết. Một thông điệp độc duy nhất chặn một phân vùng, hoặc các thông điệp thất bại biến mất mà không hàng đợi nào bắt và kiểm tra.
- Luồng vô hình. Xử lý bất đồng bộ không có ID tương quan, không cảnh báo độ trễ người tiêu thụ và không truy vết, nên thất bại im lặng cho đến khi trở thành dữ liệu thiếu.
Mô hình trưởng thành
- Mức 1, Khởi đầu: Tích hợp là các lệnh gọi điểm-nối-điểm tùy nghi, hoặc một bộ môi giới tồn tại nhưng được dùng như yêu cầu/trả lời đồng bộ. Bản trùng lặp làm hỏng bên tiêu thụ, không có kỷ luật lược đồ hay truy vết xuyên bước nhảy, và thông điệp thất bại biến mất trong im lặng.
- Mức 2, Phát triển: Một bộ môi giới hoặc nhật ký được dùng thực cho một số luồng, và vài đội có thực hành cơ bản: các bên tiêu thụ đang trở nên lũy đẳng và hàng đợi thư chết bắt một số thất bại. Nhưng cách tiếp cận không nhất quán giữa các đội, sự kiện và lệnh vẫn lẫn lộn, lược đồ đổi bằng thỏa thuận không chính thức, và khả năng quan sát mỏng.
- Mức 3, Chuẩn hóa: Sự kiện, lệnh và thông điệp được phân biệt có chủ đích trong toàn tổ chức. Các bên tiêu thụ lũy đẳng theo tiêu chuẩn, lược đồ sống trong sổ đăng ký với chính sách tương thích được lập tài liệu và thực thi, và hộp thư đi giao dịch bảo đảm giao nhận. Saga với bù trừ xử lý giao dịch nhiều dịch vụ, ID tương quan lan truyền qua mọi bước nhảy, và các mẫu này được viết ra và áp dụng nhất quán thay vì để mỗi đội tự lo.
- Mức 4, Quản lý: Nền tảng sự kiện được đo và kiểm soát bằng dữ liệu theo các đường cơ sở. Độ trễ người tiêu thụ, độ sâu hàng đợi thư chết, tỷ lệ giao lại và độ trễ xử lý được theo dõi như chỉ số cảnh báo với ngưỡng đã thống nhất, không phải bảng điều khiển không ai xem; tính tương thích lược đồ được xác minh tự động trước khi một bên sản xuất có thể phát hành thay đổi; phát lại, xử lý thất bại và tính lũy đẳng được kiểm thử theo nhịp cố định thay vì hy vọng. Một tương tác nhất định nên là sự kiện hay lệnh gọi đồng bộ được quyết định dựa trên bằng chứng, và năng lực, chi phí lưu giữ và độ tin cậy của mỗi bộ môi giới được rà soát theo mục tiêu.
- Mức 5, Điều phối: Phong cách hướng sự kiện và đồng bộ được chọn theo từng tương tác như bản năng thứ hai, và event sourcing cùng CQRS được áp dụng chính xác ở nơi nhu cầu kiểm toán và truy vấn biện minh và không nơi nào khác. Các luồng bất đồng bộ quan sát được như các luồng đồng bộ, nền tảng liên tục cải thiện khi nhu cầu dịch chuyển (loại bỏ chủ đề chết, tiến hóa quản trị lược đồ, cân bằng lại phân vùng), và nhắn tin được tích hợp với kiến trúc rộng hơn cùng chiến lược kiểm toán để tổ chức điều chỉnh thiết kế sự kiện khi doanh nghiệp và nghĩa vụ của nó thay đổi.
Ý tưởng để thảo luận
- “Sự kiện” nào hiện tại của bạn thực ra là lệnh trá hình, và bạn sẽ gỡ bỏ liên kết nào bằng cách mô hình hóa chúng trung thực?
- Nếu bạn phát lại cả một ngày sự kiện qua các bên tiêu thụ vào ngày mai, cái gì sẽ hỏng, và điều đó nói gì với bạn về tính lũy đẳng và an toàn phát lại?
- Phần nào của miền bạn thực sự cần dấu vết kiểm toán của event sourcing, và phần nào là trạng thái đơn giản mà bạn chỉ làm phức tạp thêm nếu dùng sourcing?
- Bạn sẽ di chuyển khỏi một bus dịch vụ doanh nghiệp cũ hoặc một mạng lưới tích hợp điểm-nối-điểm thế nào mà không có cuộc chuyển đổi nổ lớn rủi ro?
- Với quy trình nhiều dịch vụ quan trọng nhất của bạn, nó là một saga điều phối thật với bù trừ, hay một biên đạo nổi lên mà không nơi đơn lẻ nào mô tả?
Những điểm cần ghi nhớ
- Kiến trúc hướng sự kiện mua sự tách rời, mở rộng độc lập, phát lại và dấu vết kiểm toán, và tốn khả năng thấu hiểu cùng độ phức tạp vận hành, nên hãy chọn nó theo từng tương tác nơi lợi ích là thật.
- Giữ sự kiện (sự thật đã xảy ra), lệnh (yêu cầu hành động) và thông điệp (phong bì) riêng biệt, vì sự nhầm lẫn gây ra các sai lầm liên kết thực sự.
- Thiết kế cho giao nhận ít-nhất-một-lần và làm mọi bên tiêu thụ lũy đẳng; giao nhận đúng-một-lần là huyền thoại, và hiệu ứng đúng-một-lần là một thành tựu kỹ thuật.
- Thứ tự là theo phân vùng, lược đồ là hợp đồng thuộc về một sổ đăng ký, và hộp thư đi, hàng đợi thư chết và áp suất ngược là hệ thống ống nước làm nhắn tin sẵn sàng cho môi trường thực.
- Dùng saga với bù trừ thay vì cam kết hai pha, và chỉ với tới event sourcing cùng CQRS ở nơi nhu cầu kiểm toán và truy vết biện minh cho chi phí dốc của chúng.
- Các luồng bất đồng bộ im lặng khi thất bại, nên ID tương quan, giám sát độ trễ người tiêu thụ và truy vết từ đầu đến cuối là sự khác biệt giữa một hệ thống vận hành được và một hệ thống vô hình.
Tài liệu tham khảo và đọc thêm
- Martin Kleppmann, Designing Data-Intensive Applications
- Gregor Hohpe và Bobby Woolf, Enterprise Integration Patterns
- Chris Richardson, Microservices Patterns (saga, hộp thư đi giao dịch, CQRS)
- Sam Newman, Building Microservices
- Ben Stopford, Designing Event-Driven Systems
- Adam Bellemare, Building Event-Driven Microservices
- Vaughn Vernon, Implementing Domain-Driven Design (event sourcing và CQRS)
- Martin Fowler, “Event Sourcing” và “CQRS” (các bài viết trên martinfowler.com)
- Hector Garcia-Molina và Kenneth Salem, “Sagas” (1987)