我從接觸ddd到學習cqrs有6年多了, 其中也遇到了不少疑問, 也向很多的前輩牛人請教得到了很多寶貴的意見和建議. 偶爾的機會看到國外有個站點專門羅列了ddd, cqrs和事件溯源的常見問題. 其中很多也是我一路過來都曾遇到過的. 這是原站地址http://www.cqrs.nu/Faq. 在EN ...
我從接觸ddd到學習cqrs有6年多了, 其中也遇到了不少疑問, 也向很多的前輩牛人請教得到了很多寶貴的意見和建議. 偶爾的機會看到國外有個站點專門羅列了ddd, cqrs和事件溯源的常見問題. 其中很多也是我一路過來都曾遇到過的. 這是原站地址http://www.cqrs.nu/Faq. 在ENODE群中不少新學習cqrs的朋友都會遇到一些類似的入門問題, 作為群管理員的我也想為群里朋友做點貢獻, 所以有了翻譯一下CQRS FAQ的念頭, 並加入一些自己的理解, 希望對大家會有所幫助.
PS. 本人英語能力也一般,所以難免有些翻譯拗口的地方, 很多地方可能只能意會不可言傳. :)
Domain-Driven Design 領域驅動設計
What is a domain? 什麼是領域?
The field for which a system is built. Airport management, insurance sales, coffee shops, orbital flight, you name it.
為瞭解決某個問題而去構建一個系統的對象. 比如機場管理, 保險銷售, 咖啡店, 軌道飛行, 只要你說得出個所以然的.
It's not unusual for an application to span several different domains. For example, an online retail system might be working in the domains of shipping (picking appropriate ways to deliver, depending on items and destination), pricing (including promotions and user-specific pricing by, say, location), and recommendations (calculating related products by purchase history).
對於一個應用程式來說跨越多個不同領域並不是不尋常的. 比如, 一個線上零售系統可能涉及到運輸(根據貨物和目的地挑選合適的送達方式), 定價(包括促銷和特定用戶定價)和商品推薦(根據購買歷史計算相關的產品)領域.
What is a model? 什麼是模型?
"A useful approximation to the problem at hand." -- Gerry Sussman
"在手邊的一個對問題有用的近似模擬." -- Gerry Sussman
An Employee
class is not a real employee. It models a real employee. We know that the model does not capture everything about real employees, and that's not the point of it. It's only meant to capture what we are interested in for the current context.
Different domains may be interested in different ways to model the same thing. For example, the salary department and the human resources department may model employees in different ways.
一個雇員類並不是一個真正的雇員. 它模擬了一個真正的雇員. 我們知道該模型並不會捕捉一個真正雇員的所有方面, 而且那也不是它的重點. 它僅僅意味著去捕捉那些在當前上下文中我們感興趣的內容.
不同的領域可能致力於以不同的方式去建模同一個事物. 比如, 薪酬管理部門和人力資源管理部門可能以不同的方式去建模雇員.
What is a domain model? 什麼是領域模型?
A model for a domain.
一個針對領域的模型.
What is Domain-Driven Design (DDD)? 什麼是領域驅動設計 (DDD)?
It is a development approach that deeply values the domain model and connects it to the implementation. DDD was coined and initially developed by Eric Evans.
它是一種能深度給予領域模型價值並且能將模型和實現連接起來的一種開發方式. DDD是由Eric Evans打造並最初發展起來的.
What is the blue book that everyone is talking about? 那本大家都在討論的藍皮書是什麼呢?
This one? It is the defining text on Domain-Driven Design, by Eric Evans, the founder of DDD. It comes highly recommended.
這個? 他是DDD的創始人Eric Evans 對於領域驅動設計書面上的定義. 真的是強烈推薦.
What is a ubiquitous language? 什麼是統一語言?
A set of terms used by all people involved in the domain, domain model, implementation, and backends. The idea is to avoid translation, because as Eric Evans points out,
Translation blunts communication and makes knowledge crunching anemic.
That is, every time we have to translate concepts between people — "oh, you're using 'user' in these cases where I'm using 'account'" — we lose a direct ability to think clearly about the thing we are building and to let new knowledge flow back and forth between domain and implementation.
Investing in a ubiquitous language pays off in that it makes communication clearer, and allows teams to see more opportunities.
被所有涉及到領域,領域模型,開發實現和後端操作人員所使用的一個術語集合. 這個主意是為了避免語義轉化, 因為Eric Evans提出 "語義的轉化使得交流變得遲鈍生硬而且使得知識消化起來很乏力. "
那好比每次我們都不得不在大家溝通時進行概念轉化 -- "噢, 你正在那些場景中用'user'而我用的是'account' -- 我們丟失了一個能把我們正在構建的事物想清楚的直接機會, 而且無謂的在領域和實現之間產生了新的知識點.
把精力投在統一語言上是值得的因為它使得交流更加清晰並且能讓團隊獲得更多的先機.
What is a bounded context? 什麼是邊界上下文
A division of a larger system that has its own ubiquitous language and domain model. The pricing, shipping, and recommendations aspects of an online retailer would count as separate bounded contexts, as they have significantly different concerns.
擁有自己統一語言和領域模型的一種大系統的劃分結果. 線上零售系統的定價, 運輸和推薦這些方面會被看成分別的邊界上下文, 因為他們有各自的明顯不同的關註點.
As with other DDD concepts, bounded contexts are most valuable when carried through into the implementation.
和其他的DDD概念一起, 邊界上下文是我們進行開發實現中最優價值的.
How do I go about identifying bounded contexts? 我該如何著手識別邊界上下文?
Some common things to look for are:
- The natural boundaries in an organization (within a bounded context, most often you'll find that people collaborate and communicate closely; between bounded contexts the communication is less, and often asynchronous)
- Where the same word is given different meanings (product to pricing is a thing with a price; product to shipping is a thing with a weight and dimensions, etc.)
去找一些通用的事物:
組織結構的自然邊界(你經常會發現在一個邊界上下文中人們都是緊密地進行協作和交流; 而在邊界上下文之間的交流是比較少的而且不是那麼實時性的)
不同的邊界上下文中同一個單詞往往表達了不同的含義(產品對於銷售價格來說是一個有價格的物體; 產品對於運輸來說是一個有重量和大小的物體).
Generally, good bounded contexts look like products (a pricing strategy product, a shipping calculation product, a product recommendation engine product, etc.) This aligns well with the products-over-projects team structure.
一般來說, 好的邊界上下文看上去就像不同範疇的商品(一個帶有售價策略的商品, 一個可以運輸計費的商品, 一個推薦商品引擎中的商品 , 等等). 這很符合不同項目團隊結構里的不同商品感念的情況.
How isolated should a bounded context be from the rest of my system? 如何從系統中分離出邊界上下文呢?
Quite strongly. In general, direct dependencies are best avoided. For example, in .Net separate assemblies would be fairly sensible. In a distributed paradigm, such as SOA or microservices, then finding process boundaries between bounded contexts would be normal.
非常重要的是一般來說最好是避免直接的依賴. 像.Net 開發中分成多個程式集是很明智的做法. 在一個分散式系統範例中, 像面向服務架構和微服務, 在邊界上下文中去發現業務處理的邊界是比較常見的做法.
How can I communicate between bounded contexts? 邊界上下文之間如何交互呢?
Exclusively in terms of their public API. This could involve subscribing to events coming from another bounded context. Or one bounded context could act like a regular client of another, sending commands and queries.
僅僅通過他們的公開API. 這可能涉及到訂閱另一個邊界上下文的事件. 或者某個邊界上下文可以像另一個邊界上下文的普通客戶端一樣發送命令或查詢.
What are entities? What are value objects? 什麼是實體? 什麼是值對象?
Entities or reference types are characterized by having an identity that's not tied to their attribute values. All attributes in an entity can change and it's still "the same" entity. Conversely, two entities might be equivalent in all their attributes, but will still be distinct.
實體或者引用類型被描述成擁有一個不和他們屬性值相關聯的標識. 一個實體內的所有屬性都可以變化而且變化之後它還是原來的那個實體. 相反地, 兩個不同的實體可能他們所有的屬性值都一樣, 但他們仍然是不同的實體.
Value objects have no separate identity; they are defined solely by their attribute values. Though we are typically talking of objects when referring to value types, native types are actually a good example of value types. It is common to make value types immutable. For example,String
in many languages is immutable, and every time you want to "change" a string, you derive a new one.
值對象沒有各自的標識; 他們僅僅通過他們的屬性值來定義. 儘管我們經常討論引用值類型的對象, 但是本地化的類型確實是值類型的一個很好的列子. 它往往使得值類型是不可變的. 比如, 在許多語言中, 字元串都是不可變的, 每次你想改變一個字元串的時候, 你實際上是派生了一個新實例.
From an event sourcing perspective, both entities and value objects play important roles in the domain, but only entities need be persisted, since only these change.
從事件溯源的角度看的話, 在領域中實體和值對象都扮演了十分重要的角色, 但是只有實體是要被持久化的, 因為只有他們是會變化的. (其實值對象一般都包含在實體內一同被持久化了)
Commands and events 命令和事件
What is an event? 什麼是事件?
An event represents something that took place in the domain. They are always named with a past-participle verb, such as OrderConfirmed
. It's not unusual, but not required, for an event to name an aggregate or entity that it relates to; let the domain language be your guide.
一個事件表達了領域里發生了什麼. 他們總是被命名為過去時, 比如OrderConfirmed(訂單被確認了). 為一個事件命名成一個和它相關的聚合根或者實體的名字並不是不尋常的, 但不是必須的. 讓領域語言作為你的指導.
Since an event represents something in the past, it can be considered a statement of fact and used to take decisions in other parts of the system.
既然一個事件表達了過去發生的事情, 他可以被認為是一個事實的闡述並且可以被系統的其他部分用來做決定的依據.
What is a command? 什麼是命令?
People request changes to the domain by sending commands. They are named with a verb in the imperative mood plus and may include the aggregate type, for example ConfirmOrder
. Unlike an event, a command is not a statement of fact; it's only a request, and thus may be refused. (A typical way to convey refusal is to throw an exception).
人民通過發送命令來要求領域發生變化. 他們被命名成一個帶有啟示語氣的一個動詞而且可以帶有聚合根類型, 比如ConfirmOrder(確認訂單). 不像一個事件, 一個命令並不是一個事實的闡述, 他只是一個請求, 而且可能被拒絕執行. (系統要表達拒絕的典型方式是拋出一個異常)
What does a command or an event look like? 命令和事件看上去是什麼樣的?
Commands and events are simply data structures that contain data for reading, and no behavior. We call such structures "Data Transfer Objects" (DTOs). The name indicates the purpose. In many languages they are represented as classes, but they are not true classes in the real OO sense.
命令和事件是簡單的數據結構, 他們包含一些讀取的數據且沒有行為. 我們將這樣的結構稱為數據傳輸對象(DTO). 他們的名字表達了他們的意圖. 在許多語言中, 他們以類的形式呈現, 但是他們往往不是真正意義上面向對象概念的類.
Here's an example of a command:
命令的例子:
public class ConfirmOrder {
public Guid OrderId;
}
And here's an example of an event:
事件的例子:
public class OrderConfirmed {
public Guid OrderId;
public DateTime ConfirmationDate;
}
What is the difference between a command and an event? 命令和事件的不同點是什麼?
Their intent.
主要體現在他們不同的意圖上.
What is immutability? Why are commands and events immutable? 什麼是不變性? 為什麼命令和事件是不可變的?
For the purpose of this question, immutability is not having any setters, or other methods which change internal state. The string type in Java and C# is a familiar example; you never actually change an existing string value, you just create new string values based on old ones.
Commands are immutable because their expected usage is to be sent directly to the domain model side for processing. They do not need to change during their projected lifetime in traveling from client to server.
Events are immutable because they represent domain actions that took place in the past. Unless you're Marty McFly, you can't change the past, and sometimes not even then.
就這個問題的而言, 不變性就是沒有任何設置方法也沒有任何可以改變內部狀態的方法. java和c#中的字元串類型就是個類似的例子. 你永遠不能去改變一個已存在的字元串, 你只是基於老的值創建了一個新的字元串.
命令的不可變是因為他們預期的使用方式是直接被送達到領域去執行.他們不需要在客戶端至服務端的傳輸期間做任何的改變.
事件的不可變是因為他們表現了領域在過去發生的動作. 除非你是Marty McFly(回到未來的主角), 你不能改變過去, 有時甚至都沒有過去.
What is command upgrading?什麼是命令升級?
Upgrading commands becomes necessary when new requirements cause existing commands not to be sufficient. Maybe a new field needs to be added, for example, or maybe an existing field should really have been split into several different ones.
當新的需求導致現有命令不夠充分時, 升級命令就變得很有必要了. 有可能是需要加一個新的欄位, 也有可能一個已存在的欄位應該被分解成若幹個不同的欄位.
How do I upgrade my commands? 應該如何升級命令?
How you do the upgrade depends how much control you have over your clients. If you can deploy your client updates and server updates together, just change things in both and deploy the updates. Job done. If not, it's usually best to have the updated command be a new type and have the command handler accept both for a while.
你如何升級命令取決於你對客戶端的掌控度. 如果你可以一起發佈你的客戶端和服務端, 那就可以同時在兩端進行修改併發布升級後的版本, 那升級就搞定了. 如果不是的話, 通常最好的做法是用一個新的命令來代替要升級的命令, 並且同時讓命令處理器能夠接受他們.
Could you give an example of names of some versioned commands? 能否給出一個帶有版本信息的命令命名的一個例子?
Sure. 當然.
UploadFile
UploadFile_v2
UploadFile_v3
It's just a convention, but a sane one.
這就是一種約定, 但是卻是健全的做法.
Command/Query Responsibility Segregation 命令/查詢職責分離
What is CQRS? 什麼是CQRS?
CQRS means "Command-query responsibility segregation". We segregate the responsibility between commands (write requests) and queries(read requests). The write requests and the read requests are handled by different objects.
CQRS的意思是"命令查詢職責分離". 我們將命令(寫請求) 和查詢(讀請求)的職責分離開. 寫請求和讀請求由不同的對象進行處理.
That's it. We can further split up the data storage, having separate read and write stores. Once that happens, there may be many read stores, optimized for handling different types of queries or spanning many bounded contexts. Though separate read/write stores are often discussed in relation with CQRS, this is not CQRS itself. CQRS is just the first split of commands and queries.
就是這樣. 我們可以進一步以分離讀寫庫來進行數據存儲的分離. 一旦這麼做了, 那麼就會有很多的讀存儲, 他們可以被優化以至於可以處理不同類型的查詢或者這些讀存儲可以跨越多個邊界上下文.
CQRS sounds like one of those newfangled diets. Who made up the term? CQRS聽上去像個新流行的東西. 誰想出的這個術語?
Greg Young.
He has been complaining for years about search engines innocently asking "Did you mean CARS?" when one searches for CQRS.
Greg young. 他已經對搜索引擎天真地把CQRS當成CARS抱怨了好幾年了.
I've heard there's something called CQS too. What is it, and how does it relate to CQRS? 我也已經有聽到過CQS這種東西. 它是什麼? 它和CQRS有什麼關係?
CQS means "Command-query separation". It was introduced by Bertrand Meyer as part of his work on the Eiffel programming language.
CQS的 意思是"命令查詢分離". 它是由Bertrand Meyer以他在Eiffel 編程語言工作上的一部分來介紹的.
It means that a method is either a command performing an action, or a query that returns data, but not both. Being purely action-performing methods, commands always have a void
return type. Queries, on the other hand, should be idempotent, that is, they don't have any visible side effects on the system.
他的意思是一個方法要麼是一個執行動作的命令, 要麼是一個帶有返回數據的查詢, 但不是同時擁有這兩種行為. 作為純粹的動作執行方法, 命令執行方法總是沒有返回值的. 而另一面,查詢應該是冪等的, 就是說他們不會對系統造成任何可見的副作用.
(在Eric Evans的ddd原著里, 柔性設計章節中也有提到, 領域的執行應該分為業務邏輯的計算和改變領域對象狀態, 業務計算執行多少次都是沒有副作用的,因為計算方法的輸入和輸出參數都是值對象, 而將計算結果賦值到領域對象上則是真正改變領域狀態的時候. 這樣使得程式在編寫時職責更加明確, 這裡我只是拿出來和cqs中提到的無副作用進行一個類比, 讓大家體會一下無副作用的含義.其實cqrs的領域處理也正是ddd中提到的這種做法, 聚合根的公共方法里只是進行業務計算產生領域是事件, 而聚合根的eventhandler中才是真正改變聚合根狀態的地方. 這裡可能扯得有點遠了:) )
Originally, CQRS was called "CQS", too. But it was determined that the two are different enough for CQRS to have its own name. The main distinguishing feature is this:
一開始, CQRS也曾被稱作CQS. 但是這兩者也有足夠的不同之處以至於CQRS擁有他自己的名字. 主要的特征區別是:
- CQS puts commands and queries in different methods within a type.
- CQRS puts commands and queries on different objects.
- CQS將命令和查詢作為不同的方法放在一個類中.
- CQRS將命令和查詢放到不同的對象上.
Can CQRS ever be a simplification? CQRS能成為一個簡單的事物嗎?
Sure. Generic repositories are a common sight in many systems. They work well in CRUD scenarios - typically, those you may not be applying DDD to. They tend to work out fine for creates, updates, deletes, and reading individual entities. But as soon as there's a query that spans multiple entities where should it go?
當然可以. 一般的倉儲在很多系統中已經司空見慣了. 他們已經在CRUD這種典型場景中工作得很不錯了, 而那些場景中你可能並沒有運用DDD. 他們想更好地解決獨立實體的增刪改查. 但是一旦出現了一個跨域多個實體的查詢後, 它應該何去何從? (因為一般來說一個倉儲只能對一種實體進行CRUD操作)
Rather than agonizing over it, and trying to shoe-horn queries into the generic repository arrangement, it's far easier to put them on a separate object. No questions where they go, and they can return simple, lightweight DTOs of data.
CQRS doesn't have to mean doing event sourcing, introducing commands, event, read sides, sagas, and so forth.
比起為此煩惱以及試圖將這種查詢融入到一般的倉儲里,將查詢放到不同的對象上就簡單得多了.不管他們怎麼查都不是問題, 他們可以返回簡單的, 輕量級的數據傳輸對象. CQRS並不是說一定要用事件溯源, 命令, 事件, 讀端, sagas(流程處理器)之類的高大上東西.
Will CQRS not make my application more complex? CQRS不會使我的應用程式變得更複雜吧?
A typical CQRS + Event Sourcing system will seemingly have more components, since commands, events, exceptions, and queries become part of the public interface. Aggregates, command handlers, read side projections, sagas, and clients further contribute to the proliferation of components.
一個典型的CQRS+ Event Sourcing系統看上去會有更多的組件, 因為命令,事件, 異常和查詢都成為了公開介面的一部分. 聚合根, 命令處理器, 讀庫投影, 流程處理器以及客戶端進一步使得組件更為擴大化了.
However, each component is neatly uncoupled from the rest. Originally, "complex" means "braided together". The components in a CQRS+ES system are independent in a way that favors reasoning about the system, and responding to changing requirements:
然而每個組件都是巧妙得和其他部分解耦了. 本來, "複雜" 的意思就是"編製在一起". 在CQRS+ES系統中的組件都是以一種能更好地推理出系統以及應對需求變化的方式獨立存在的.
-
The public interface of message types forms a layer of your application that encourages you to think in terms of user intent, not updating data.
-
The division of the system into client, write side, and read side makes it easy to divide work between various teams.
-
Perhaps most importantly, testing becomes very natural, even of the most important and complex parts of the business logic.
- 消息類型的公開介面組成了應用的一個層次, 鼓勵你以符合用戶意圖的方式去思考, 而不只是更新數據.
- 將系統分解成客戶端 , 寫端和讀端使得在不同團隊之間分配工作變得更簡單了.
- 可能最重要的是 測試變得非常自然, 即使是最重要以及最複雜的業務邏輯部分也是如此.
Should the write side always be independent of the read side? 寫端是不是應該總是不依賴讀端?
No. But it often helps - for example, by enabling event sourcing to be used on the write side, which can offer a lot of benefits.
答案是否定的. 但是如果不依賴往往有很多的幫助. 比如, 在寫端使用了事件溯源, 那麼可以帶來很多好處.
Event sourcing 事件溯源
What is event sourcing? 什麼是事件溯源?
Storing all the changes (events) to the system, rather than just its current state.
將系統的變化存儲起來,而不是系統的當前狀態.
Why haven't I heard of event stores before? 為什麼以前沒聽說過存儲事件之說?
You have. Almost all transactional RDBMS systems use a transactional log for storing all changes applied to the database. In a pinch, the current state of the database can be recreated from this transaction log. This is a kind of event store. Event sourcing just means following this idea to its conclusion and using such a log as the primary source of data.
其實你是聽說過的. 幾乎所有帶有事務特性的關係型資料庫系統都使用了事務日誌去存儲所有應用到資料庫的變化. 必要時, 資料庫的當前狀態是可以通過事務日誌重新創建出來的. 這就是一種事件存儲. 事件溯源其實就是根據這種這種思路和它的推論而產生的並且用這種日誌來作為數據的主要來源.
What are some advantages of event sourcing? 事件溯源的優勢是什麼?
- Ability to put the system in any prior state. Useful for debugging. (I.e. what did the system look like last week?)
- Having a true history of the system. Gives further benefits such as audit and traceability. In some fields this is required by law.
- We mitigate the negative effects of not being able to predict future needs, by storing all events and being able to create arbitrary read-side projections as needed. This allows for more nimble responses to new requirements.
- The kind of operations made on an event store is very limited, making the persistence very predictable and thus easing testing.
- Event stores are conceptually simpler than full RDBMS solutions, and it's easy to scale up from an in-memory list of events to a full-featured event store.
- 能夠使得系統處於之前的任何一個狀態. 對於調試是很有幫助的. (比如, 在上周系統看上去是什麼樣的?)
- 擁有了系統的真實的歷史記錄. 給予了更多的好處比如審計和跟蹤能力. 在某些領域這些都是法律要求要做到的.
- 通過存儲所有的事件並能夠根據需要構建任意的讀庫投影, 我們就減輕因為無法預計的未來需求而帶來的負面影響. 這使得對新需求可以有更快的響應.
- 在事件存儲器上的操作種類是非常受限的, 這使得持久化是可預期的也就使得測試變得容易了.
- 事件存儲器從概念上講比全功能的關係型資料庫解決方案要更簡單, 而且他可以很容易地從記憶體中的事件集合擴展到全功能的事件存儲器.
Is event sourcing a requirement to do CQRS? 使用CQRS的時候事件溯源是不是必須的?
No. You can save your aggregates in any form you like. However, event sourcing works well with CQRS, and brings a number of additional benefits.
不. 你可以以任何形式保存你的聚合根. 然後事件溯源可以在CQRS下很好的工作, 並且帶來很多額外的好處.
What if an event in the event queue turns out to be wrong? 如果事件隊列中的事件發生了錯誤會怎麼樣?
In an event queue, new events are added to the end of the queue. Events are never removed or changed. (Just as in an accountant's ledger, incidentally.) Compensating actions are what you can add in order to correct actual mistakes. They are simply events which cancel out earlier events.
在一個事件隊列中(這裡指的是事件存儲器中的事件隊列), 新的事件被加到隊列的尾部. 事件永遠不會被移除或者發生改變. (就像附帶的會計記賬薄一樣) 修正操作是你可以用來修改實際的錯誤.
Won't the use of event sourcing make my system slow? 事件溯源不會讓我的系統比慢吧?
No. 不會的.
It takes more time to apply events to build up the current state. But processors are really fast; applying events takes on the order of microseconds. For most domains, performance isn't a problem.
Furthermore, the tight aggregate boundaries that come hand in hand with event sourcing should lead to systems that will scale well horizontally.
它確實會花更多的時間去應用事件來構建系統的當前狀態. 但是處理器是非常快的, 順序地應用事件進行溯源的時候是微秒級的. 對於大多數領域來說, 性能不是問題.
此外, 通過事件溯源而來的緊密聚合根邊界應該會使得系統水平擴展得更好.
What is snapshotting? 什麼是快照?
An optimization where a snapshot of the aggregate's state is also saved (conceptually) in the event queue every so often, so that event application can start from the snapshot instead of from scratch. This can speed things up. Snapshots can always be discarded or re-created as needed, since they represent computed information from the event stream.
一種聚合根狀態的快照也時常被保存在事件隊列裡面的一種優化, 以至於應用事件溯源的時候可以從一個快照開始而不是從頭開始. 這使得溯源的速度加快.
Typically, a background process, separate from the regular task of persisting events, takes care of creating snapshots.
Snapshotting has a number of drawbacks related to re-introducing current state in the database. Rather than assume you will need it, start without snapshotting, and add it only after profiling shows you that it will help.
一般來說, 用一個和常規的持久化事件任務分開的後臺進程來處理快照的創建工作.
涉及到在資料庫中重新引入了當前狀態快照會有些缺陷. 比起假定你需要它, 寧可一開始不要用快照, 只有當證明瞭他確實給你帶來幫助後再使用它.
How do I version/upgrade my events? 我應該如何進行事件的版本控制和升級?
You leave them as-is in the event-store, because it is conceptually an append-only list. However, both write side and read side can "upgrade" incoming events in their handlers. An event can always be upgraded to a newer version... if not, it was probably not a newer version after all, but a completely different event type.
你應該就這麼讓他們在事件存儲器中保持原樣, 因為從概念上講, 它是個只追加的隊列. 然而, 寫端和讀端都可以在他們自己的處理器中對事件進行升級. 一個事件總是可以被升級到一個新的版本... 如果不能的話, 他可能根本並不是事件的一個新版本, 而是完全的另一個類型的事件了.
How do I handle a growing/large event store over time? 隨著時間的推移, 我應該如何處理越來越大的事件存儲器?
Events are usually quite small, and you can easily store, index, and search millions of them on a low-end relational database.
That said, it's always good to plan ahead, and pick a serialization format that serves you well in terms of size. JSON tends to be smaller than the corresponding XML, for example.
If you feel the need to algorithmically compress your events, that's also an option. Google's protocol-buffers are a modern example of a compressed serialization to use.
For the cases where you actually literally run out of hard drive space: disks are cheap nowadays. Consider saving historical events in some permanent storage. The events carry important business value; do not throw them away.
If the event store outgrows a single machine, then it is easy to shard first by aggregate type, and with a little content-based routing even at the level of aggregates themselves.
事件一般來說都比較小, 你可以很容易地在一個廉價的關係型數據中對事件進行存儲, 做一些索引, 並且在上百萬的事件中進行搜索.
那就是說, 預先計劃並選擇一種在大小方面能滿足你要求的序列化格式總是好的. 比如JSON比相對應的xml格式來得更小點.
如果你覺得需要通過演算法對你的事件進行壓縮, 那也是一個選項. 谷歌的protocol-buffers是個現代化的壓縮的序列化例子.
對於那些你確實要用光硬碟驅動器空間的場景:考慮到將歷史事件保存在永久化存儲器中, 現今磁碟是很便宜的. 事件帶來了重要的商業價值. 不要將他們丟棄了.
如果事件存儲器在一臺機器上滿了, 那麼根據聚合類型先進行分片處理是很容易的, 然後還能根據聚合自己那一層再進行基於內容的邏輯. (比如聚合根id的hashcode值再進行路由分片存儲)
Could I persist commands, too? 我可以也持久化命令嗎?
It's often useful to log your commands, because they contain important information about the requests made on the domain model.
But commands are not events, and they don't belong in the event store. Simply consider logging of the commands as an additional aspect to be wrapped around your command handlers.
記錄命令往往是有幫助的. 因為他們包含了關於對領域模型請求的重要信息.
但是命令不是事件, 他們不屬於事件存儲器. 可以簡單地將命令記錄看作是命令處理器周邊被封裝的額外的方面.
CAP and eventual consistency CAP和最終一致性
What is the CAP theorem? 什麼是CAP原理
The CAP theorem states that in a distributed system, you can have two out of the following three properties at a given point in time:
CAP原理描述了在一個分散式系統中, 你只能同時擁有以下三個屬性中的兩個.
- Consistency
- Availability
- Partition tolerance
- 一致性 (分散式系統中的所有數據備份,在同一時刻是否同樣的值。等同於所有節點訪問同一份最新的數據副本)
- 可用性 (集群出現故障節點後,是否還能響應客戶端的讀寫請求。對數據更新具備高可用性)
- 分區容忍性 (實際情況中通信必定產生延時。系統如果不能在時限內達成數據一致性,就意味著發生了分區的情況,必須就當前操作在一致性C和可用性A之間做出選擇。)
To understand why, imagine what happens when two nodes on either side of a partition try to update the whole system.
想要明白為什麼, 想象一下當分區兩側的兩個結點試圖同時更新整個系統的時候會發生什麼.
對於分散式數據系統,分區容忍性是基本要求, 因為分散式系統必定會有通信上的延時情況發生, 所以當延時情況超過了限制時, 就不可能同時滿足一致性和可用性.
CAP的一致性和最終一致性可以認為沒有任何關係,不要混在一起討論.
At what level does CAP apply? CAP應用在什麼層次上?
CAP is fine-grained. You can make different choices in different parts of your system. For example, for accepting orders, usually availability is desirable as you don't want to lose the orders!
CAP是細粒度的. 你可以在系統的不同部分使用不同的選擇. 比如, 在接受訂單的時候, 往往可用性是值得保證的因為你不想失去訂單.
What is eventual consistency? 什麼是最終一致性?
A de-emphasizing of immediate consistency (that is, everything having the same view of the data all the time) in a system, in exchange for higher availability and greater autonomy of components.
在系統中不強調立即達到一致性(那意味著所有的東西在任何時候都有著相同的數據視圖),作為交換可以獲得更高的可用性和更好的組件自治性.
Messaging 消息
How do I handle duplicate command/event issues? 我如何處理重覆的命令和事件問題?
In the transport layer.
在傳輸層進行處理 .
這裡的回答比較籠統, 一般來說消息隊列本身很難做到永遠不重覆傳輸消息, 所以我們可以在消息傳輸層即接受消息端進行一些冪等處理, 即對處理過的消息進行記錄, 下次再收到相同消息的時候就可以判斷是否已經被處理過. 而對於消息隊列來說消息必達性才是必須要保證的特性.
Should I use push or pull when publishing my events?當我發佈事件的時候應該用推還是拉?
Push has the advantage that events can be pushed as they happen. Pull has the advantage that read sides can be more active and independent. Pull with a local event cache on the read side seems to us to be the nicest and most scalable solution. Push can work nicely with reactive programming and web sockets, however. Again, you needn't make the same choice everywhere in a system.
推的優勢是當事件發生的時候就可以被推出去. 拉的優勢是讀取端可以更主動和更獨立. 在讀端通過拉取消息到本地事件緩存里似乎是最好的而且是伸縮性最好的解決方案. 然而推模式可以在響應式編程以及web sockets的情況下更好的工作. 還是那句話, 你不需要在一個系統中的各個地方只使用一種選擇.
Testing 測試
How can I test my CQRS application? 我如何測試CQRS應用程式
Using exclusively the commands, events, and exceptions.
使用專用的命令, 事件和異常.
CQRS的特性決定了在給定命令後得到相應的事件或者異常, 使得測試容易很多.
What is behavioral testing? 測試的行為是什麼樣的?
Testing purely based on an object's behavior, without talking about its state. Concretely, this means that we only ever call methods. This fits well with testing in terms of commands and events, since applying events and handling commands are part of an aggregate's public API.
測試純粹基於一個對象的行為, 並不關心他的狀態. 具體地講, 這意味著我們只調用方法. 這很符合根據命令事件進行測試, 因為應用事件和處理命令都是聚合根公開API的一部分.
這裡的測試應該是指對聚合根的測試, 而聚合根行為的觸發對應於命令而聚合根行為的結果對應於產生的事件.
What does "Tell, don't ask" mean? "告知, 不要問"是什麼意思?
Decisions should be made inside of encapsulation boundaries, where the data is. The object or aggregate is the "expert", and things on the outside shouldn't ask for its state and then make decisions for it.
"Tell, don't ask" is considered a good principle of object-oriented design.
The testing encouraged by a CQRS application is an excellent example of "Tell, don't ask". The only thing we can do to test the behavior of our aggregates is to set them up (using events), tell them to do something (using a command), and then observe the results (more events, or an exception).
應該在封裝的邊界內來做出決定, 因為要用來做決定的數據都在那. 對象或者聚合根就是"專家", 而外部對象不應該詢問他的狀態去為他做決定. (這裡就是信息專家模式, 將職責分配給擁有能實現該職責數據的對象)
"告知, 不要問" 被認為是一個很好的面向對象設計原則.
被CQRS應用程式所支持的測試是一個完美的"告知, 不要問"的例子. 對於測試聚合根行為我們所能做的唯一的事情就是(使用事件)設置他們(改變聚合根的狀態). (使用命令)告訴他們做什麼, 並且觀察結果(更多的事件或者異常) .
How do I know a command failed for the right reason? 我如何知道一個命令失敗的原因呢?
Use typed exceptions to indicate the mode of failure, and except that type of exception in the test.
用定義好的異常去表達模式的失敗, 當然要排除測試中的異常類型.
可以通過發佈一個異常的消息來表達某個命令執行失敗了. 而成功的話則是通過發佈一系列聚合根的領域事件.
So I know I get the correct event, but how do I know it meant something? 我知道我得到了一個正確的事件, 但我如何知道他表達了什麼?
Testing that a given command leads to an expected event is only half the job. To make sure the event's application actually means something, write a test with that event in the history. For example, to test that an event indicating an appointment was made actually took effect, put it in the history and try to make a conflicting appointment.
對於給定的命令會產生一個預期的事件的測試只是完成了一半的工作. 為了確保事件的應用程式確實表達了一些信息 , 可以通過它的歷史事件編寫一個測試. 比如, 為了測試一個事件表明產生的預約確實生效了, 把這個預約放到歷史中並設法再產生一個衝突的預約. (以此來驗證之前預約成功的事件確實是表達了領域確切的意圖.)
Aggregates 聚合
What is an aggregate? 什麼是聚合?
A larger unit of encapsulation than just a class. Every transaction is scoped to a single aggregate. The lifetimes of the components of an aggregate are bounded by the lifetime of the entire aggregate.
Concretely, an aggregate will handle commands, apply events, and have a state model encapsulated within it that allows it to implement the required command validation, thus upholding the invariants (business rules) of the aggregate.
一個大於一個類的封裝的單元. 每個事務的作用域只對應到一個單獨的聚合. 聚合的組件的生命周期是和整個聚合生命周期綁定的.
具體而言, 一個聚合會處理命令, 應用事件, 並且在其內部有個帶有狀態的模型能夠讓他它去實現請求命令的驗證, 因而支持聚合的不變性(業務規則).
What is the difference between an aggregate and an aggregate root? 聚合和聚合根之間的區別是什麼?
The aggregate forms a tree or graph of object relations. The aggregate root is the "top" one, which speaks for the whole and may delegates down to the rest. It is important because it is the one that the rest of the world communicates with.
聚合形成了對象關係的一個樹形或者圖形結構. 聚合根是頂層節點, 它作為整個聚合以及聚合內部其他對象的代表. 這很重要, 因為世界上的其他對象都只和他進行交互(而不能和其內部的對象進行交互).
I know aggregates are transaction boundaries, but I really need to transactionally update two aggregates in the same transaction. What should I do?
我知道聚合是事務的邊界, 但是我確實需要在一個事務中更新兩個聚合. 我該怎麼做?
You should re-think the following:
你應該重新思考以下幾點:
- Your aggregate boundaries.
- The responsibilities of each aggregate.
- What you can get away with doing in a read side or in a saga.
- The actual non-functional requirements of your domain.
- 你聚合根的邊界是否正確
- 每個聚合的職責是什麼
- 當使用讀端或者流程式控制制器的時候你能否排脫這個困境
- 是你領域的非功能性需求嗎
If you write a solution where two or more aggregates are transactionally coupled, you have not understood aggregates.
如果你在一個事務中耦合了兩個或更多的聚合, 說明你還是沒有明白什麼是聚合. (這裡非常精彩, 可能只能意會了:) )
Why is the use of GUID as IDs a good practice? 為什麼用GUID作為唯一標識是一個好的實踐?
Because they are (reasonably) globally unique, and can be generated either by the server or by the client.
因為他們是全局唯一的, 並且在服務端或者客戶端都可以生成.
How can I get the ID for newly created aggregates? 我如何獲取到新創建的聚合的ID呢?
It's an important insight that the client can generate its own IDs.
客戶端可以生成它自己的IDs是一個很重要的做法.
If the client generates a GUID and places it in the create-the-aggregate command, this is a non-issue. Otherwise, you have to poll from the appropriate read side, where the ID will appear in an eventually consistent time frame. Clearly this is much more fragile than just generating it in the first place.
如果客戶端生成了一個GUID並且將他放入了創建聚合的命令中, 這是沒問題的. 否則你不得不去輪詢合適的讀端, 在那裡ID會在最終一致性的時間框架下出現. (可能需要通過一些額外的條件從讀端查詢到對應的ID, 比如email, account等等). 很明顯這比在客戶端生成的方案要脆弱很多.
Should I allow references between aggregates? 我可以允許聚合之間持有引用嗎?
In the sense of an actual "memory reference", absolutely not.
對於那種"記憶體引用", 很明顯不可以.
On the write side, an actual memory reference from one aggregate to another is forbidden and wrong, since aggregates by definition are not allowed to reach outside of themselves. (Allowing this would mean an aggregate is no longer a transaction boundary, meaning we can no longer sanely reason about its ability to uphold its invariants; it would also preclude sharding of aggregates.)
在寫端, 從一個聚合到另一個聚合的記憶體引用是被禁止的而且也是錯誤的, 因為根據定義聚合是不允許接觸到他們外部的. (如果允許了那意味著聚合不再是一個事務邊界, 我們也不再能充分的推導出聚合有能力確保他的不變性;這也將妨礙到聚合的分片處理.)
Referring to another aggregate using a string identifier is fine. It is useless on the write side (since the identifier must be treated as an opaque value, since aggregates can not reach outside of themselves). Read sides may freely use such information, however, to do interesting correlations.
通過用一個字元串標識去引用另一聚合是個很好的做法. 這個字元串標識在寫端是無效的(這裡指聚合是無法通過這個字元串標識去訪問到其他聚合的)(既然標識肯定被當成一個不透明的值來對待, 既然聚合不能接觸到他們的外部). 然而讀端就可以很自由的使用這種記憶體引用信息了, 可以做一些有趣的相關性操作.
How can I validate a command across a group of aggregates? 我如何在涉及到一組聚合時對命令進行驗證?
This is a common reaction to not being able to query across aggregates anymore. There are several answers:
通常的反應是我們不再能查詢多個聚合根了. 有幾種答案:
- Do client-side validation.
- Use a read side.
- Use a saga.
- If those are all completely impractical, then it's time to consider if you got your aggregate boundaries correct.
- 進行客戶端驗證.
- 使用讀模型
- 通過流程處理器
- 如果他們都是完全不切實際的,那麼就是時候考慮你的聚合邊界是否正確了.
客戶端驗證和使用讀模型驗證在讀庫非同步更新延遲和併發的場景下,是無法保證業務的正確性的, 只能是提高一下用戶體驗或者是減輕寫端系統的重覆驗證失敗的壓力.所以通過saga(流程處理器)來最終解決這種聚合根之間的一致性才是正途. 而這種一致性是最終一致性.
How can I guarantee referential integrity across aggregates? 如何在多個聚合間保證引用的完整性?
You're still thinking in terms of foreign relations, not aggregates. See last question. Also, remember that just because something would be a two tables in a relational design does not in any way suggest it should be two aggregates. Designing in aggregates is different.
你仍在思考外部關聯性方面, 而不是聚合. 看上一個問題. 要記住, 因為 即使某個東西在關係型設計中可能被設計成兩張表, 也無論如何不建議他被設計成兩個聚合. 在聚合內的設計是不同的.
How can I make sure a newly created user has a unique user name? 我該如何確保一個新建的用戶擁有一個唯一的用戶名?
This is a commonly occurring question since we're explicitly not performing cross-aggregate operations on the write side. We do, however, have a number of options:
這是通常會發生的一個問題, 既然我們明確不會在寫端進行跨聚合的操作. 然而我們仍有幾個選項可以採納:
- Create a read-side of already allocated user names. Make the client query the read-side interactively as the user types in a name.
- Create a reactive saga to flag down and inactivate accounts that were nevertheless created with a duplicate user name. (Whether by extreme coincidence or maliciously or because of a faulty client.)
- If eventual consistency is not fast enough for you, consider adding a table on the write side, a small local read-side as it were, of already allocated names. Make the aggregate transaction include inserting into that table.
- 創建一個讀端的已分配用戶名集合. 在客戶端當用戶鍵入一個用戶名的時候進行互動式地讀庫查詢. (當系統存在讀庫非同步更新或者併發場景時,這無法根本解決問題, 只能是提高一下用戶體驗和減輕寫端驗證失敗的壓力)
- 創建一個可響應的流程式控制制器去標記和禁用那些有著重覆用戶名也被創建出來的帳號. (不論是極端的巧合或者是惡意為之還是因為一個有缺陷的客戶端)
- 如果最終一致性對你來說不夠快速, 那麼可以考慮在寫端加入一張表, 作為一個小型的本地讀端(其實也可以是一個實現了驗證唯一性的領域服務, 至於怎麼實現可以是本地資料庫,也可以是遠程服務), 將已分配的用戶名存在其中.使得聚合的事務包含插入用戶名到表中的邏輯.
這裡我具體舉個例子來說明一下如何通過saga來實現這個唯一性的驗證, 首先領域內需要設計一個特殊的用來承擔起確保用戶名唯一性職責的聚合, 這個聚合內就是一個有效用戶名集合. 很顯然根據信息專家模式, 這個聚合擁有系統的所有用戶名, 所以理應他來負責用戶名唯一的業務規則.
1. 執行用戶註冊命令, 創建一個未認證通過的帳號並產生一個新帳號對象被創建的事件
2. saga訂閱到這個用戶對象創建事件, 併發送一個向索引聚合添加新用戶名的一個命令
3. 索引聚合執行添加新用戶名的命令, 當用戶名有效時產生一個事件,表示新用戶有效. 當用戶名已存在時產生一個異常消息表示添加新用戶名失敗.
4. 如果第三步產生的是新用戶名成功添加的事情, 那麼saga在接受到這個事件後發送一個命令使第一步中創建出來的帳號對象狀態變成有效完成註冊流程; 反之則發送一個命令使得第一步中的帳號狀態標記為驗證失敗的.
通過這樣的一個saga流程我們看出整個過程就是cmd->event->cmd->event->cmd->event 這樣的一個由命令和事件組成的消息鏈. 這也是EDA(事件驅動架構)的應用. 有心的你一定可以想出很多的變種解決方案, 比如我們使用另外一個邊界上下文去承擔索引聚合的職責(這個邊界上下文的領域對象的持久化方案可以不用事件溯源,而使用關係型資料庫), 這時saga流程就是在不同的邊界上下文中進行命令和事件的流轉控制.
How can I verify that a customer ID really exists when I place an order? 在我確定一個訂單的時候我如何驗證客戶ID是真實有效存在的?
Assuming customer and order are aggregates here, it's clear that the order aggregate cannot really validate this, since that would mean reaching out of the aggregate.
Checking up on it after the fact, in a saga or just in a read side that records "broken" orders, is one option. After all, the most important thing about an order is actually recording it, and presumably any interesting data about the recipient of the order is being copied into the order aggregate (referring to the customer to find the address is bad design; the order was always made to be deliverd to a particular address, whether or not that customer changes their address in the future).
Being able to use what data was recorded in this broken order means you've a chance to rescue it and rectify the situation - which makes a good bit more business sense than dropping the order on the floor because a foreign key constraint was violated!
假設客戶和訂單是不同的聚合. 很明顯訂單聚合是不能驗證這個的, 因為那將意味著訂單要訪問外部的客戶聚合了.
可以在事後進行檢查, 在一個流程處理中或者就在讀端記錄那些"損壞"的訂單, 是一種選擇. 畢竟, 對於一個訂單來說最終要的是要記錄它, 並且假定任何關於訂單接受者感興趣的數據正被覆制到訂單聚合里(通過引用客戶聚合去找到收貨地址是個不好的設計;訂單總是要設置要被送到某個特定的地址, 不管客戶是否會在將來改變了他們的地址).
能使用這個被記錄在損壞訂單里的數據意味著你有機會去解救和改正這種情況-- 和直接將訂單丟棄比起來, 這種方式使得業務看上去更對頭了. 因為外鍵約束被違反了.
(這裡的意思應該是這種事後處理的機制使得我們能更好得發現系統的一些問題,而不是通過自動丟棄無效訂單將問題隱藏起來了. 這裡當然是指排除了人為惡意偽造無效訂單的情況.)
How can I update a set of aggregates with a single command? 我如何通過一個命令去更新多個聚合根?
A single command can't act on a set of aggregates. It just can't. 一個命令不能操作多個聚合, 那是不可以的.
First off, ask yourself whether you really need to update several aggregtes using just one command. What in the situation makes this a requirement?
首先, 問一下你自己是否真的需要用一個命令去更新多個聚合. 什麼樣的場景需要這樣的要求?
However, here's what you could do. Allow a new kind of "bulk command", conceptually containing the command you want to issue, and a set of aggregates (specified either explicitly or implicitly) that you want to issue it on. The write side isn't powerful enough to make the bulk action, but it's able to create a corresponding "bulk event". A saga captures the event, and issues the command on each of the specified aggregates. The saga can do rollback or send an email, as appropriate, if some of the commands fail.
然而, 這裡是你可以做的. 允許一個"批量命令"的新類型, 概念上講它包含了你想要發送的命令, 和你想要發送到的(顯示或隱式指定的)聚合集合. 寫端並不夠強大去執行大批量的操作, 但是他能夠創建相應的"大批量事件". 一個流程式控制制器捕獲到事件,併發送命令到每個指定的聚合上. 如果一些命令失敗的話, 流程式控制制器可以適時地進行業務回滾或者發送郵件.
There are some advantages to this approach: we store the intent of the bulk action in the event store. The saga automates rollback or equivalent.
對於這個方法有一些優勢: 我們將批處理動作的意圖存儲在了事件存儲器中. 流程式控制制器自動進行回滾或類似的操作.
Still, having to resort to this solution is a strong indication that your aggregate boundaries are not drawn correctly. You might want to consider changing your aggregate boundaries rather than building a saga for this.
然而, 採取這種解決方案強烈地暗示了你聚合邊界劃分得並不太對. 你很可能要考慮改變你的聚合邊界, 而不是為此構建一個流程式控制制器.
What is sharding? 什麼是分片(分區)處理?
A way to distribute large amounts of aggregates on several write-side nodes. We can shard aggregates easily because they are completely self-reliant.
We can shard aggregates easily because they don't have any external references.
一種使大量聚合分佈在若幹個寫端節點的方式. 因為聚合都是完全自治的, 所以我們可以很容易的對聚合進行分片.
因為他們沒有任何的外部引用依賴, 所以我們可以很容易的對聚合進行分片處理.
Can an aggregate send an event to another aggregate? 一個聚合可以向另一個聚合發送事件嗎?
No. 不可以
The factoring of your aggregates and command handlers will typically already make this idea impossible to express in code. But there's a deeper philosophical reason: go back and re-read the first sentence in the answer to "What is an aggregate?". If you manage to circumvent command handlers and just push events into another aggregate somehow, you will have taken away that aggregate's chance to participate in validation of changes. That's ultimately why we only allow events to be created as a result of commands validated by a command handler on an aggregate.
一般來說由聚合的特點和命令處理器不太可能在編碼上表達這種做法. 但是有著更深入的哲學理由: 重讀一下"什麼是一個聚合?"回答中的第一句話. 如果你想避免使用命令處理器並且直接將事件推送給另外一個聚合, 你將抹殺了聚合參與變化驗證的機會. 那就是為什麼最終我們只允許事件是作為命令的一個結果被創建出來, 而這些命令都是由在聚合之上的命令處理器來驗證的.
Can I call a read side from my aggregate? 我可以在聚合內調用讀端嗎?
No. 不可以
How do I send e-mail in a CQRS system? 我如何在CQRS系統中發送電子郵件?
In an event handler outside of the aggregate. Do not do it in the command handler, as if the events are not persisted due to losing a race with another command then the email will have been sent on a false premise.
可以在聚合外部的事件處理器中來做這個事情. 不要在命令處理器中做這種事情, 好比在和其他命令競爭執行失敗的情況下(比如樂觀併發衝突的時候), 事件並沒有被持久化, 然而郵件可能已經在一個錯誤的假設下發送出去了.
Command handlers 命令處理器
What does a command handler do? 命令處理器是乾什麼的?
A command handler receives a command and brokers a result from the appropriate aggregate. "A result" is either a successful application of the command, or an exception.
命令處理器接受命令並且以經濟人的身份處理來自某個合適的聚合的執行結果. 這個"結果"可能是命令的成功應用或者是一個異常.
This is the common sequence of steps a command handler follows:
這是命令處理器的通用執行步驟:
- Validate the command on its own merits. 對它本身情況進行命令的確認.
- Validate the command on the current state of the aggregate. 對當前的聚合狀態進行命令的確認. (其實這裡不僅僅是檢查命令, 還包括了聚合對命令的執行以及結果的驗證.)
- If validation is successful, 0..n events (1 is common). 如果確認成功了, 那麼 會產生0-n個事件. (一般來說是一個) (這裡的確認通過實際意思是聚合成功執行了命令, 一般來說命令處理器中的事件/異常都是自聚合產生的, 當然也可能是命令處理器在校驗命令時產生的異常事件)
- Attempt to persist the new events. If there's a concurrency conflict during this step, either give up, or retry things. 進行持久化事件. 如果在這個時候存在併發衝突, 要麼放棄這次操作,要麼進行重試. (具體要根據業務來決定是放棄還是重試)
Should a command handler affect one or several aggregates? 一個命令處理器可以影響一個或者多個聚合嗎?
Only one. 只能是一個.
Do I put logic in command handlers? 我要把邏輯放在命令處理器中嗎?
Yes. Exactly what logic depends on your factoring.
是的, 具體的邏輯要根據你具體的業務特征來決定.
The logic for validating the command on its own merits always goes in the command handler. If the command handler is just a method on the aggregate, then the next step is simply to use the state of the aggregate to do further validation. In a more functional factoring, where the aggregate exists independently of the command handlers, the next step would be to load the aggregate and do validation against it.
對於根據它自身情況來驗證命令的邏輯總是放在命令處理器里的. 如果命令處理器僅僅是個聚合上的方法, 那麼下一步就