Tại sao các lệnh và sự kiện được biểu diễn riêng biệt?


78

Sự khác biệt giữa lệnh và sự kiện trong kiến ​​trúc nhấn mạnh sự kiện là gì? Điểm khác biệt duy nhất mà tôi có thể thấy là các lệnh thường được lấy / gọi bởi các tác nhân bên ngoài hệ thống, trong khi các sự kiện dường như được lấy từ các trình xử lý và mã khác trong hệ thống. Tuy nhiên, trong nhiều ứng dụng ví dụ mà tôi đã thấy, chúng có giao diện khác nhau (nhưng giống nhau về chức năng).


Nó phụ thuộc rất nhiều vào những gì bạn chính xác trong đầu khi sử dụng các từ "lệnh" và "sự kiện".
Doc Brown

Lấy dự án CQRS / DDD điển hình của bạn làm ví dụ.
alphadogg

4
Sự hiểu biết của tôi: Các lệnh phải được xử lý bởi chính xác một bộ thu (có thể từ chối lệnh) và Sự kiện có thể được xử lý bởi 0 ... n bộ nhận.
Levi Fuller

Câu trả lời:


157

Các lệnh có thể bị từ chối.

Sự kiện đã xảy ra.

Đây có lẽ là lý do quan trọng nhất. Trong kiến ​​trúc hướng sự kiện, không thể nghi ngờ rằng một sự kiện được nêu ra đại diện cho một điều gì đó đã xảy ra .

Bây giờ, vì Lệnh là thứ chúng ta muốn xảy ra và Sự kiện là thứ đã xảy ra, chúng ta nên sử dụng các động từ khác nhau khi đặt tên cho những thứ này. Điều này thúc đẩy các đại diện riêng biệt.

Tôi có thể thấy rằng các lệnh thường được lấy / gọi bởi các tác nhân bên ngoài hệ thống, trong khi các sự kiện dường như được lấy từ các trình xử lý và mã khác trong hệ thống

Đây là một lý do khác khiến chúng được đại diện riêng biệt. Khái niệm rõ ràng.

Lệnh và Sự kiện đều là Thông báo. Nhưng thực tế chúng là những khái niệm riêng biệt và các khái niệm nên được mô hình hóa một cách rõ ràng.


Được rồi, có sự khác biệt thực tế ở mức độ triển khai giữa chúng không? Ví dụ, một giao diện khác nhau?
alphadogg

9
Vâng, tôi sẽ nói chủ yếu trong việc điều động. Các lệnh được gửi đến một trình xử lý duy nhất, nhưng các sự kiện được gửi đến nhiều trình nghe. Đúng là, sự khác biệt về triển khai là ở các bus, nhưng tôi vẫn sử dụng các giao diện sự kiện và lệnh riêng biệt, để mỗi bus chỉ nhận các thông báo mà nó có thể.
quentin-starin

8
Với các lệnh, bạn sử dụng từ "Gửi" (bạn quan tâm đến mục tiêu của thao tác này), trong khi với các sự kiện bạn sử dụng "Xuất bản" (bạn không quan tâm ai ở đầu bên kia).
Yves Reynhout

4
@ quentin-starin Tôi biết câu này đã cũ, nhưng tôi ước tôi có thể ủng hộ câu trả lời của bạn 10 lần ... - ví dụ: "SomethingRequested" và sau đó là "SomethingHappened"
Michael Wasser

1
Xem xét một thực thể đang tồn tại trong mysql và nâng cao thực thể sự kiệnIndexed. Sự kiện tương tự được lắng nghe bởi một dịch vụ chỉ mục mà khi nhận sự kiện sẽ tìm nạp thực thể và lập chỉ mục nó trong tìm kiếm đàn hồi. Bạn vẫn sẽ gọi nó là một sự kiện hay một lệnh từ quan điểm chỉ mục-dịch vụ?
Technoshaft

8

Ngoài ra, ngoài tất cả các câu trả lời ở đây được hiển thị, một trình xử lý sự kiện cũng có thể kích hoạt một lệnh sau khi nhận được thông báo rằng một sự kiện đã xảy ra.

Ví dụ: giả sử sau khi bạn tạo Khách hàng, bạn cũng muốn khởi tạo một số giá trị tài khoản, v.v. Sau khi Khách hàng AR của bạn thêm sự kiện vào EventDispatcher và điều này được đối tượng CustomerCreateEventHandler nhận được, trình xử lý này có thể kích hoạt gửi một lệnh. sẽ thực thi bất cứ điều gì bạn cần, v.v.

Ngoài ra, còn có DomainEvents và ApplicationEvents. Sự khác biệt chỉ đơn giản là khái niệm. Bạn muốn gửi tất cả các sự kiện miền của mình trước (một số trong số chúng có thể tạo ra Sự kiện ứng dụng). Ý tôi là gì?

Việc khởi tạo tài khoản sau khi CustomerCreateEvent đã xảy ra là một sự kiện DOMAIN. Gửi thông báo qua email cho Khách hàng là một Sự kiện Ứng dụng.

Lý do bạn không nên trộn chúng đã rõ ràng. Nếu máy chủ SMTP của bạn tạm thời ngừng hoạt động, điều đó không có nghĩa là HOẠT ĐỘNG TRONG MIỀN của bạn sẽ bị ảnh hưởng bởi điều đó. Bạn vẫn muốn giữ trạng thái không bị hỏng của các tập hợp của mình.

Tôi thường thêm các sự kiện vào Điều phối viên của mình ở cấp Gốc tổng hợp. Sự kiện này là DomainEvents hoặc ApplicationEvents. Có thể là cả hai và có thể là nhiều người trong số họ. Sau khi trình xử lý lệnh của tôi hoàn tất và tôi quay lại ngăn xếp để mã thực thi trình xử lý lệnh, sau đó tôi kiểm tra Điều phối viên của mình và gửi bất kỳ DomainEvent nào khác. Nếu tất cả những điều này thành công, sau đó tôi đóng giao dịch.

Nếu tôi có bất kỳ Sự kiện Ứng dụng nào, đây là lúc để gửi chúng. Gửi email không nhất thiết cần kết nối mở với cơ sở dữ liệu cũng như mở phạm vi giao dịch.

Tôi đã đi lạc một chút so với câu hỏi ban đầu nhưng điều quan trọng là bạn phải hiểu cách các sự kiện cũng có thể được đối xử khác nhau về mặt khái niệm.

Sau đó, bạn có Sagas .... nhưng đó là CÁCH CÓ THỂ của phạm vi câu hỏi này :)

Nó có ý nghĩa không?


Nhưng bạn có thể dễ dàng tranh luận rằng bạn có Sự kiện miền, Sự kiện ứng dụng và Sự kiện người dùng. Sau đó, nó chỉ trở thành nguồn gốc của sự kiện là gì. Tôi hiểu cách phân biệt có thể hữu ích, nhưng có lẽ nhiều hơn trong việc phân biệt giữa yêu cầu và phản hồi / hành động do nó tạo ra. Nhưng ngay cả khi ở đó tôi vẫn chưa hoàn toàn bị thuyết phục. Cần phải điều tra thêm.
Arwin

Vì vậy, bây giờ tôi đọc phản hồi của chính mình, tôi có thể thấy sự nhầm lẫn. Các sự kiện thực sự có thể là một. Sự kiện. Sau đó, bạn có những người xử lý có thể có sự khác biệt đó. Bạn có thể có hai trình xử lý cho cùng một sự kiện, sau đó bạn có thể kích hoạt chúng trong các ngữ cảnh khác nhau. Bạn có thể quyết định kích hoạt "Trình xử lý sự kiện miền" trong Giao dịch đang hoạt động và sau đó, bạn có thể kích hoạt "Trình xử lý sự kiện ứng dụng" của mình. Tôi chỉ không thấy điểm của việc gửi email hoặc sms trong một Giao dịch. Có thể bạn có thể thêm một "nhiệm vụ" vào cơ sở dữ liệu và có một quy trình khác thực hiện chúng, chẳng hạn như gửi email. Nó có ý nghĩa không?
Pepito Fernandez

6

Sự kiện là một sự thật từ quá khứ.

Các lệnh chỉ là một yêu cầu, và do đó có thể bị từ chối.

Một đặc điểm quan trọng của một lệnh là nó chỉ được xử lý một lần bởi một bộ thu duy nhất. Điều này là do một lệnh là một hành động hoặc giao dịch đơn lẻ mà bạn muốn thực hiện trong ứng dụng. Ví dụ, không nên xử lý cùng một lệnh tạo lệnh nhiều lần. Đây là sự khác biệt quan trọng giữa lệnh và sự kiện. Sự kiện có thể được xử lý nhiều lần vì nhiều hệ thống hoặc microservices có thể quan tâm đến sự kiện. 'msdn'


5

Sau khi xem qua một số ví dụ và đặc biệt là phần trình bày của Greg Young ( http://www.youtube.com/watch?v=JHGkaShoyNs ), tôi đã đi đến kết luận rằng các lệnh là thừa. Chúng chỉ đơn giản là các sự kiện từ người dùng của bạn, họ đã nhấn nút đó. Bạn nên lưu trữ những sự kiện này theo cách giống hệt như các sự kiện khác vì nó là dữ liệu và bạn không biết liệu mình có muốn sử dụng nó trong chế độ xem trong tương lai hay không. Người dùng của bạn đã thêm và sau đó loại bỏ mục đó khỏi giỏ hoặc ít nhất là cố gắng. Sau này, bạn có thể muốn sử dụng thông tin này để nhắc nhở người dùng về điều này sau này.


Theo cách được mô tả bởi @ quentin-starin, nghĩ về sự kiện như một điều gì đó đã xảy ra và một lệnh như một điều gì đó chúng ta muốn xảy ra (một yêu cầu), không dừng các sự kiện nhấn nút được ghi lại, chỉ là những sự kiện đó không ' không nhất thiết phải dẫn đến một lệnh, hoặc dẫn đến một lệnh đã được thực hiện.
fractor

Tôi vẫn quan điểm rằng các lệnh là thừa. Tôi chỉ gọi những gì tôi làm tìm nguồn cung ứng sự kiện chức năng. Một blog gần đây của tôi với ES và F # Elm là một hệ thống hoàn chỉnh: anthonylloyd.github.io/blog/2016/11/27/event-sourcing
Ant

2
Các lệnh tách các sự kiện cục bộ khỏi các hành động từ xa. Trong ví dụ của bạn, người tiêu dùng sự kiện UserPressedButton sẽ không phản ứng với UserSelectedMenu hoặc ScriptDidSomething trừ khi họ cũng biết về những điều này. Ngoài ra, các lệnh thường được hướng đến một người tiêu dùng cụ thể; một lần nữa, trong ví dụ của bạn, người tiêu dùng sự kiện UserPressedButton không thể biết liệu người dùng đã chọn hộp kiểm "Xác nhận" hay chưa, trừ khi chúng tôi thêm nhiều khớp nối hơn nữa. Với các lệnh, hành động được thực hiện có thể phụ thuộc vào trạng thái của người gửi hoặc thậm chí là chính sách bên ngoài. Sự kiện, một mình, làm cho điều này gần như không thể.
Bác sĩ Đánh giá

Kiểm tra dự án này - github.com/gregoryyoung/mr . Nó sử dụng cả lệnh và sự kiện.
xhafan

6
Sẽ dễ dàng hơn để phân biệt các lệnh với các sự kiện khi nghĩ đến cách triển khai chặt chẽ nhất: tìm nguồn cung ứng sự kiện. Ở đây các sự kiện là nguồn sự thật duy nhất. Bạn có thể xây dựng trạng thái đầy đủ của mình bất kỳ lúc nào bằng cách chỉ phát lại các sự kiện. Ngược lại, các lệnh là các yêu cầu có thể dẫn đến các sự kiện, nhưng cũng có thể bị từ chối. Đối với việc xây dựng lại các lệnh trạng thái là không quan trọng. Vì vậy, nếu hệ thống của bạn không xử lý được một lệnh (ví dụ như do lỗi xác thực) thì không sao cả. Nếu hệ thống của bạn không xử lý được một sự kiện, trạng thái của bạn sẽ bị hỏng.
sven

2

Chúng được đại diện riêng biệt bởi vì chúng đại diện cho những thứ rất khác nhau. Như @qstarin đã nói các lệnh là các thông báo có thể bị từ chối và nếu thành công sẽ tạo ra một sự kiện. Các lệnh và sự kiện là Dtos, chúng là các thông điệp và chúng có xu hướng trông rất giống nhau khi tạo và thực thể, tuy nhiên từ đó trở đi thì không nhất thiết.

Nếu bạn lo lắng về việc sử dụng lại, thì bạn có thể sử dụng các lệnh và sự kiện làm phong bì cho tải trọng (lộn xộn) của mình

class CreateSomethingCommand
{
    public int CommandId {get; set;}

    public SomethingEnvelope {get; set;}
 }

tuy nhiên, điều tôi muốn biết là tại sao bạn lại hỏi: D tức là bạn có quá nhiều lệnh / sự kiện không?


1
Không, tôi không có quá nhiều vì tôi đang tìm cách xây dựng hệ thống như vậy đầu tiên của mình! :) Tôi đang ở chế độ học tập. Tôi đang cố gắng tìm hiểu xem CommandHandlers và EventHandlers có làm gì khác nhau không hay về cơ bản có cùng giao diện.
alphadogg

Một điểm thú vị để tìm hiểu là các lệnh và sự kiện thể khác nhau, ví dụ bạn có CheckoutCartCommand, sự kiện có thể sẽ có nhiều dữ liệu hơn lệnh, cũng có thể có nhiều lệnh. Rất mong bạn hãy xem github.com/MarkNijhof/Fohjingithub.com/gregoryyoung/mr
roundcrisis 14/02

Đối với ví dụ của bạn, phong bì thường ở bên ngoài, không ở bên trong (ví dụ như phong bì xà phòng). Và tôi nghĩ rằng có một tên thuộc tính bị thiếu (Tải trọng?).
Yves Reynhout

@Yves: tốt cho rằng Something evelope có thông tin cần thiết cho lệnh (nếu đây là email do khách hàng tạo ra) Tôi sẽ thấy điều đó khá lạ, bạn có nghĩ vậy không?
roundcrisis

Tôi không nghĩ rằng bạn nắm bắt được khái niệm của một phong bì. Nếu bạn có những thứ cụ thể cho lệnh thì hãy đặt chúng vào lệnh, dưới dạng trọng tải hoặc dưới dạng tiêu đề (ngoài dải). Nhưng đừng gọi payload / headers là một phong bì.
Yves Reynhout

2

Ngoài sự khác biệt về khái niệm được đề cập ở trên, tôi nghĩ rằng có một sự khác biệt khác liên quan đến các triển khai chung:

Các sự kiện thường được xử lý trong một vòng lặp nền cần phải thăm dò các hàng đợi sự kiện. Bất kỳ bên nào quan tâm đến việc thực hiện sự kiện, thông thường, có thể đăng ký một lệnh gọi lại được gọi là kết quả của quá trình xử lý hàng đợi sự kiện. Vì vậy, một sự kiện có thể là một hoặc nhiều .

Các lệnh có thể không cần được xử lý theo cách như vậy. Người khởi tạo lệnh thường sẽ có quyền truy cập vào người thực thi lệnh dự kiến. Ví dụ, điều này có thể ở dạng một hàng đợi thông báo đến trình thực thi. Do đó, một lệnh dành cho một thực thể duy nhất .


1

Tôi nghĩ điều cần thêm vào câu trả lời của quentin-santin là họ:

Đóng gói một yêu cầu dưới dạng một đối tượng, do đó cho phép bạn tham số hóa các máy khách với các yêu cầu khác nhau, yêu cầu hàng đợi hoặc nhật ký và hỗ trợ các hoạt động hoàn tác.

Nguồn .


0

Bạn không thể tính toán lại một trạng thái dựa trên các lệnh, vì nói chung chúng có thể tạo ra các kết quả khác nhau mỗi khi chúng được xử lý.

Ví dụ, hãy tưởng tượng một GenerateRandomNumberlệnh. Mỗi lần nó được gọi, nó sẽ tạo ra một số ngẫu nhiên khác nhau X. Do đó, nếu trạng thái của bạn phụ thuộc vào số này, mỗi lần bạn tính toán lại trạng thái của mình từ lịch sử lệnh, bạn sẽ nhận được một trạng thái khác.

Sự kiện giải quyết vấn đề này. Khi bạn thực hiện một lệnh, nó tạo ra một chuỗi các sự kiện thể hiện kết quả của việc thực hiện lệnh. Ví dụ, GenerateRandomNumberlệnh có thể tạo ra một GeneratedNumber(X)sự kiện ghi lại số ngẫu nhiên đã tạo. Bây giờ, nếu bạn tính toán lại trạng thái của mình từ nhật ký sự kiện, bạn sẽ luôn nhận được trạng thái tương tự, vì bạn sẽ luôn sử dụng cùng một số được tạo ra bởi một lần thực thi lệnh cụ thể.

Nói cách khác, các lệnh là các hàm với các tác dụng phụ, các sự kiện ghi lại kết quả của một lần thực hiện một lệnh cụ thể.

Lưu ý: Bạn vẫn có thể ghi lại lịch sử các lệnh cho mục đích kiểm tra hoặc gỡ lỗi. Vấn đề là để tính toán lại trạng thái, bạn sử dụng lịch sử của các sự kiện, không phải lịch sử của các lệnh.


0

Chỉ để thêm vào những câu trả lời tuyệt vời này. Tôi muốn chỉ ra những điểm khác biệt liên quan đến khớp nối.

Các lệnh được hướng tới một bộ xử lý cụ thể. Do đó, có một số mức độ phụ thuộc / kết hợp với bộ khởi tạo Lệnh và bộ xử lý.

Ví dụ: UserServicekhi tạo một người dùng mới sẽ gửi Lệnh "Gửi Email" tới EmailService.

Thực tế là cái UserServicebiết rằng nó cần cái EmailService, cái đó đã được ghép nối. Nếu EmailServicethay đổi lược đồ API của nó hoặc gặp sự cố, nó sẽ ảnh hưởng trực tiếp đến UserServicehàm.


Sự kiện không hướng tới bất kỳ trình xử lý sự kiện cụ thể nào. Do đó, nhà xuất bản sự kiện trở nên liên kết lỏng lẻo. Nó không quan tâm những gì dịch vụ tiêu thụ sự kiện của nó. Việc có 0 khách hàng tham gia một Sự kiện thậm chí còn hợp lệ.

Ví dụ: UserServicekhi tạo một người dùng mới sẽ xuất bản một "Sự kiện do người dùng tạo". Có khả năng một EmailServicecó thể sử dụng sự kiện đó và gửi email cho người dùng.

Ở đây UserServicekhông nhận thức được EmailService. Chúng hoàn toàn tách rời. Nếu sự cố EmailServicehoặc thay đổi các quy tắc kinh doanh, chúng tôi chỉ cần chỉnh sửaEmailService


Cả hai cách tiếp cận đều có giá trị. Khó theo dõi hơn một thiết kế Kiến trúc hướng sự kiện thuần túy vì nó được kết hợp quá lỏng lẻo, đặc biệt là trên các hệ thống lớn. Và một Kiến trúc nặng chỉ huy có mức độ khớp nối cao. Vì vậy, một sự cân bằng tốt là lý tưởng.

Hy vọng điều đó có ý nghĩa.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.