CQRS FAQ (翻譯)


  我從接觸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:

這是命令處理器的通用執行步驟:

  1. Validate the command on its own merits. 對它本身情況進行命令的確認.
  2. Validate the command on the current state of the aggregate.  對當前的聚合狀態進行命令的確認. (其實這里不僅僅是檢查命令, 還包括了聚合對命令的執行以及結果的驗證.)
  3. If validation is successful, 0..n events (1 is common). 如果確認成功了, 那么 會產生0-n個事件. (一般來說是一個)  (這里的確認通過實際意思是聚合成功執行了命令, 一般來說命令處理器中的事件/異常都是自聚合產生的, 當然也可能是命令處理器在校驗命令時產生的異常事件)
  4. 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.

對於根據它自身情況來驗證命令的邏輯總是放在命令處理器里的. 如果命令處理器僅僅是個聚合上的方法, 那么下一步就是簡單地使用聚合的狀態做進一步的確認(這里指的就是聚合進行業務處理). 考慮更多的功能性方面因素的話, 只要聚合的存在是獨立於命令處理器的, 那么下一步就是加載這個聚合並讓它去執行確認.

Provided validation is successful, the command handler should then produce events. Depending on the factoring, it may also take a further step to try and persist them.

一旦確認成功了, 那么命令處理器應該產生相應的事件. 依據實際情況, 它可能要進一步去持久化他們. 

In the Edument CQRS starter kit, command handlers are methods that return events. The loading of events, building up of the aggregate, and persisting of events is completely factored out of command handlers. This keeps them very clean and focused, and thus completely decoupled from persistence mechanisms.

根據Edument CQRS的初學者套件, 命令處理器是一些返回事件的方法. 加載事件, 重建聚合以及事件的持久化都完全在命令處理器之外進行處理的. 這使得他們非常清晰和專注於自己的職責, 並且這也使得和具體的持久化機制進行了解耦.

However you have it, the logic boils down to validation and some sequence of steps that lead to the command becoming an exception or event(s). If you're tempted to go beyond this, see the rest of the questions in this section.

然而你這么做的話, 歸結為確認和一系列執行步驟的邏輯會導致命令變成一個異常或者一些事件. 如果你想解決這個問題, 那么可以看看這個章節余下的幾個問題.

Can I call a read side from my command handler? 我能在命令處理器中調用讀端嗎?

No. 不可以.

Can I do logging, security, or auditing in my command handlers? 我可以在命令處理器中進行日志記錄, 安全檢查和審計工作嗎?

Yes. The decorator pattern comes in handy here to separate those concerns neatly.

可以的. 這里可以方便地使用裝飾者模式巧妙地分離不同的關注點.

How are conflicts between concurrent commands handled in the command handler? 在命令處理器中並發命令是如何沖突的?

The place where the new events for the aggregate are persisted is the only place in the system where we need to worry about concurrency conflicts. The event store knows the sequence number of the latest event applied on that aggregate, and the command handler knows the sequence number of the last event it read. If these numbers do not agree, it means some other thread or process got there first. The command handler can then load up the events again and make a new attempt.

在系統中新聚合事件被持久化的唯一地方,我們要擔心一下並發沖突的情況. 事件存儲器是知道聚合最后一次被應用的事件的序列號的, 並且命令處理器是知道上一次讀到的事件的序列號的. 如果這兩個序列號不相等, 那么就意味着另一個線程或者進程已經在它之前持久化化新的事件了. 這個時候命令處理器可以重新加載聚合事件重構聚合並且做一次新的嘗試.

Should I do things that have side-effects in the outside world (such as sending email) in a command handler?

我可以在命令處理器中做一些對外部有副作用的事情嗎(比如發送一個電子郵件)?

No, since a concurrency conflict will mean the command handler logic will be run again. Do such things in an event handler.

不可以, 因為並發沖突意味着命令處理器會嘗試再次執行. 在事件處理器中做這些事情吧.

Read sides 讀端

What is a read side? 什么是讀端?

A read side listens to events published from the write side, projects those events down as changes to a local model, and allows queries to be made on that model.

讀端監聽來自寫端發布的事件,  並根據那些事件映射到本地模型的修改, 並允許對這些模型進行查詢. (就是進行denormalize, 通過訂閱領域產生的事件去更新讀庫的模型)

What practical problems do read sides solve? 讀庫解決了什么樣的實際問題?

They make the cost of correlating model data (called JOIN in SQL lingo) from being per-read to being per-write. A query on a read side is just a straight SELECT, because data is already in the shape the client wants.

This is a net win, because usually, the ratio of reads to writes in a system is usually 10 or more. The idea is quite similar to "views" in SQL databases.

他們降低了從每讀一次到每寫一次的數據模型(在SQL術語中就是JOIN語句)的關聯成本. 在讀端的一個查詢就是一個直接的select, 因為數據已經是客戶端想要的格式了.

(這里的意思是一般的范式關系型數據設計是通過join多個表來獲得客戶端想要的數據, 所以每次讀意味着每次都要通過join重新構建了一個視圖對象, 而CQRS的讀端在訂閱到領域事件時可以直接生成客戶端需要的視圖模型, 這樣就省去了查詢時再做join的開銷,  因為讀庫里的數據結構就是客戶端想要的結構)

What if my domain has more writes than reads? 如果我們的領域寫操作比讀操作更多會怎么樣?

Are you sure? Make sure you measure before replying in the affirmative.

Some domains (telecommunications, for example) are very write-intense during short periods, and require much from the write side. But then the read side usually catches up and reads take over.

Some domains (real-time stock markets, for example) are completely dominated by incoming data, and the write side has to be optimized to apply commands in real time.

你確定嗎? 在肯定回答前, 你要確保你的猜測是正確的.

一些領域(比如在通信領域)在短時間內可能是有很強的寫意圖, 並且對寫端也有很大的要求. 但是往往讀端也會趕上寫端的要求並超過寫端.

一些領域(比如實時的股票市場)是完全由輸入數據控制的, 並且寫端必須被優化到能實時地進行命令處理.

What is a projection? 什么是投影?

A set of event handlers that work together to build and maintain a read model.

一組事件處理器, 他們為構建和維護讀模型而一起工作. (其實也可以被叫做denormalizer)

What if I build a read side and the projections turn out to be wrong somehow? 如果我構建的讀端和投影發生了錯誤怎么辦?

If you can't easily correct it in-flight, then build a new version of the read side with fixed projections, deploy it, have it re-process all the events from the event store so it's up with the latest data, and switch queries over to using it.

如果你不能簡單地在系統運行時改正的話, 那么通過修正的投影(作為denormalizer的eventhandler)構建一個新的讀端, 然后發布,  並讓它對事件存儲器里面的所有事件重新處理一次, 那么就可以更新到最新的數據了, 並且使得查詢切換到新構建的讀端上.

  Sagas 流程管理器

What is a saga? 什么是saga

An independent component that reacts to domain events in a cross-aggregate, eventually consistent manner. Time can also be a trigger. Sagas are sometimes purely reactive, and sometimes represent workflows.

From an implementation perspective, a saga is a state machine that is driven forward by incoming events (which may come from many aggregates). Some states will have side effects, such as sending commands, talking to external web services, or sending emails.

saga是一個以最終一致性方式對跨多個聚合產生的領域事件做出反應的獨立的組件.  時間也可能是一個觸發者. Sagas有時是純粹被動進行反應的, 有時表達了一個工作流.

從實現的方面講, 一個saga是一個狀態機, 它被到來的事件所驅動(可能來自很多聚合的事件). 有些狀態可能會有副作用, 比如發送命令, 和外部的webserivce交互, 或發送電子郵件.

Isn't a saga just leaked domain logic? saga沒有泄漏領域邏輯嗎?

No. 沒有泄漏.

Sagas are doing things that no individual aggregate can sensibly do. Thus, it's not a logic leak since the logic didn't belong in an aggregate anyway. Furthremore, we're not breaking encapsulation in any way, since sagas operate with commands and events, which are part of the public API.

sagas 做的是那些獨立聚合無法做到的事情. 所以, 它並不會造成邏輯泄漏 因為它處理的邏輯怎么也不屬於一個聚合. 再進一步, 我們並沒有以任何方式打破封裝性, 因為sagas只是操作命令和事件, 而他們只是公開API的一部分.

How can I make my saga react to events that did not happen? 我如何使saga對那些沒有發生的事件作出反應?

The saga, besides reacting to domain events, can be "woken up" by recurrent internal alarms. Implementing such alarms is easy. See cron in Unix, for example.

Saga 除了對領域事件產生反應外 , 它還可能被間隔性的時鍾喚醒. 要實現這樣的時鍾是很容易的. 可以參考一下Unix中的cron.

How does the saga interact with the write side? saga是如何和寫端進行交互的?

By sending commands to it.

通過向寫端發送命令.

Occasionally connected systems 偶爾連接的系統

What about offline clients? 離線客戶端是怎么樣的?

Clients can be made to work offline, allowing you to issue commands locally, which are synchronized with the write side when reconnecting.

客戶端可能被做成是離線工作的, 允許你可以在本地發送命令, 當連線時和寫端進行同步.

A client has a tendency to pull in features of the write side (for doing local validation) and of the read sides (for updaing faster than eventual consistency allows). In some sense, since the client is the user's window to the system, it always has a tendency to grow until it looks like a small copy of the whole system including write side and read sides.

根據寫端(為了做本地確認)和讀端(為了比最終一致性更快地更新數據)的特點客戶端傾向於做拉取操作. 在某些情況下, 因為客戶端是用戶對於系統的窗口, 它總是傾向於變得健壯起來, 直到它看上去像包含了寫端和讀端的整個系統的一個小備份.

What is command merging? 什么是命令合並?

Sometimes in a highly collaborative domain, commands arrive "too late" and the current state of an aggregate has already changed so that the command does not apply cleanly. Command merging is the act of extracting the underlying intent from the command, and then creating and applying a new command from that intent.

有時候在一個高度協作的領域內, 命令的到達可能"太晚了", 而聚合的當前狀態已經改變了, 這導致了命令沒有被明確地執行. 命令合並是解析命令的潛在意圖的一種操作, 那么根據那個意圖創建並執行一個新的命令.

How can command merging be done in practice in an occasionally connected client? 在一個偶爾連接的客戶端中如何實際地進行命令合並?

The git merging model seems an appropriate one to steal.

git的合並模型看上去是一個不錯的借鑒.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM