Aggregates 聚合體
As said before, an Aggregate is a cluster of objects (entities and value objects) bound together by an Aggregate Root object. This section will introduce the principles and rules related to the Aggregates.
如前所述,一個聚合體是由聚合根對象綁定在一起的一組對象(實體和值對象)。本節將介紹與聚合體有關的原則和規則。
We refer the term Entity both for Aggregate Root and sub-collection entities unless we explicitly write Aggregate Root or sub-collection entity.
除非我們明確寫出Aggregate Root或sub-collection entities,否則我們對Aggregate Root和sub-collection entity都使用Entity這個術語。
Aggregate / Aggregate Root Principles 聚合/聚合根的原則
Business Rules 業務規則
Entities are responsible to implement the business rules related to the properties of their own. The Aggregate Root Entities are also responsible for their sub-collection entities.
實體負責實現與其自身屬性有關的業務規則。聚合根實體還負責其子集實體。
An aggregate should maintain its self integrity and validity by implementing domain rules and constraints. That means, unlike the DTOs, Entities have methods to implement some business logic. Actually, we should try to implement business rules in the entities wherever possible.
一個聚合體應該通過實現領域規則和約束來保持其自我完整性和有效性。這意味着,與DTO不同,實體有方法來實現一些業務邏輯。實際上,我們應該盡可能地在實體中實現業務規則。
Single Unit 單元
An aggregate is retrieved and saved as a single unit, with all the sub-collections and properties. For example, if you want to add a Comment
to an Issue
, you need to;
一個聚合體是被檢索並保存為一個單元,包括所有的子集和屬性。例如,如果你想給一個問題(Issue)添加一個評論(Comment),你需要做以下事情:
- Get the
Issue
from database with including all the sub-collections (Comments
andIssueLabels
). - 從數據庫中取得問題
Issue
,包括所有子集(評論Comments
和問題標簽IssueLabels
)。 - Use methods on the
Issue
class to add a new comment, likeIssue.AddComment(...);
. - 使用
Issue
類的方法來添加一個新的評論,如Issue.AddComment(...);
。 - Save the
Issue
(with all sub-collections) to the database as a single database operation (update). - 將問題
Issue
(連同所有子集)作為一個單一的數據庫操作(更新)保存到數據庫。
That may seem strange to the developers used to work with EF Core & Relational Databases before. Getting the Issue
with all details seems unnecessary and inefficient. Why don't we just execute an SQL Insert
command to database without querying any data?
對於以前使用EF Core和關系型數據庫的開發者來說,這可能顯得很奇怪。獲取問題Issue
的所有細節似乎沒有必要,而且效率很低。為什么我們不直接執行一個SQL插入Insert
命令到數據庫而不用查詢任何數據呢?
The answer is that we should implement the business rules and preserve the data consistency and integrity in the code. If we have a business rule like "Users can not comment on the locked issues", how can we check the Issue
's lock state without retrieving it from the database? So, we can execute the business rules only if the related objects available in the application code.
答案是,我們應該在代碼中實現業務規則並保持數據的一致性和完整性。如果我們有一個業務規則,如 "用戶不能對鎖定的問題發表評論",我們如何在不從數據庫中檢索的情況下檢查問題Issue
的鎖定狀態?所以,只有當應用程序代碼中的相關對象可用時,我們才能執行業務規則。
On the other hand, MongoDB developers will find this rule very natural. In MongoDB, an aggregate object (with sub-collections) is saved in a single collection in the database (while it is distributed into several tables in a relational database). So, when you get an aggregate, all the sub-collections are already retrieved as a part of the query, without any additional configuration.
另一方面,MongoDB開發者會發現這個規則非常自然。在MongoDB中,一個聚合對象(包括子集)被保存在數據庫中的一個集合中(而在關系數據庫中它被分配到幾個表中)。因此,當你得到一個聚合時,所有的子集已經作為查詢的一部分被檢索出來了,不需要任何額外的配置。
ABP Framework helps to implement this principle in your applications.
ABP框架幫助你在你的應用程序中實現這一原則。
Example: Add a comment to an issue 例子:添加一個問題評論
_issueRepository.GetAsync
method retrieves the Issue
with all details (sub-collections) as a single unit by default. While this works out of the box for MongoDB, you need to configure your aggregate details for the EF Core. But, once you configure, repositories automatically handle it. _issueRepository.GetAsync
method gets an optional parameter, includeDetails
, that you can pass false
to disable this behavior when you need it.
issueRepository.GetAsync
方法默認獲取問題Issue
及其所有細節(子集)作為一個單元。雖然這對MongoDB來說是開箱即用,但你需要為EF Core配置你的聚合細節。但是,一旦你配置了,存儲庫就會自動處理它。issueRepository.GetAsync
方法得到一個可選的參數,includeDetails
,你可以在需要時傳入false
來禁用這個行為。
See the Loading Related Entities section of the EF Core document for the configuration and alternative scenarios.
關於配置和備用方案,請參見EF Core文檔的加載相關實體部分。
Issue.AddComment
gets a userId
and comment text
, implements the necessary business rules and adds the comment
to the Comments collection of the Issue
.
Issue.AddComment
得到一個userId
和評論文本text
,實現必要的業務規則,並將評論comment
添加到問題Issue
的評論集合中。
Finally, we use _issueRepository.UpdateAsync
to save changes to the database.
最后,我們使用_issueRepository.UpdateAsync
來保存變化到數據庫。
EF Core has a change tracking feature. So, you actually don't need to call _issueRepository.UpdateAsync
. It will be automatically saved thanks to ABP's Unit Of Work system that automatically calls DbContext.SaveChanges()
at the end of the method. However, for MongoDB, you need to explicitly update the changed entity.
EF Core有一個變化跟蹤功能。所以,你實際上不需要調用_issueRepository.UpdateAsync
方法。它將被自動保存,由於ABP的工作單元系統在方法結束時自動調用DbContext.SaveChanges()
。然而,對於MongoDB,你需要顯式地更新改變的實體。
So, if you want to write your code Database Provider independent, you should always call the UpdateAsync
method for the changed entities.
所以,如果你想寫獨立於數據庫提供程序的代碼,你應該總是為改變的實體調用UpdateAsync
方法。
Transaction Boundary 事務邊界
An aggregate is generally consideredl… as a transaction boundary. If a use case works with a single aggregate, reads and saves it as a single unit, all the changes made to the aggregate objects are saved together as an atomic operation and you don't need to an explicit database transaction.
一個聚合體通常被認為是......一個事務的邊界。如果一個與一個聚合體一起工作的用例,將其作為一個單元進行讀取和保存,那么對聚合體對象所做的所有改變都將作為一個原子操作進行保存,你不需要一個明確的數據庫事務。
However, in real life, you may need to change more than one aggregate instances in a single use case and you need to use database transactions to ensure atomic update and data consistency. Because of that, ABP Framework uses an explicit database transaction for a use case (an application service method boundary). See the Unit Of Work documentation for more info.
然而,在現實生活中,你可能需要在一個用例中改變一個以上的聚合實例,你需要使用數據庫事務來確保原子更新和數據一致性。正因為如此,ABP框架為一個用例(應用服務方法邊界)使用了一個明確的數據庫事務。更多信息請參見工作單元相關文檔。
Serializability 可序列化
An aggregate (with the root entity and sub-collections) should be serializable and transferrable on the wire as a single unit. For example, MongoDB serializes the aggregate to JSON document while saving to the database and deserializes from JSON while reading from the database.
一個聚合體(包括根實體和子集)應該是可序列化的,並且可以作為一個單元在網上傳輸。例如,MongoDB在保存到數據庫時將聚合體序列化為JSON文本,在從數據庫讀取時從JSON反序列化。
This requirement is not necessary when you use relational databases and ORMs. However, it is an important practice of Domain Driven Design.
當你使用關系型數據庫和ORM時,這個要求是不必要的。然而,它是領域驅動設計的一個重要實踐。
The following rules will already bring the serializability.
以下規則用於可序列化。
Aggregate / Aggregate Root Rules & Best Practices 聚合/聚合根的規則和最佳實踐
The following rules ensures implementing the principles introduced above.
以下規則來確保實現上述介紹的原則。
Reference Other Aggregates Only by ID 只通過ID引用其他聚合體
The first rule says an Aggregate should reference to other aggregates only by their Id. That means you can not add navigation properties to other aggregates.
第一條規則說,一個聚合體應該只通過它們的Id來引用其他聚合體。這意味着你不能向其他聚合體添加導航屬性。
- This rule makes it possible to implement the serializability principle.
- 這個規則使得實現可序列化原則成為可能。
- It also prevents different aggregates manipulate each other and leaking business logic of an aggregate to one another.
- 它還可以防止不同的聚合體相互操控,以及將聚合體的業務邏輯泄露給對方。
You see two aggregate roots,GitRepository
andIssue
in the example below;
在下面的例子中,你可以看到兩個聚合根分別是GitRepository
和Issue
:
GitRepository
should not have a collection of theIssue
s since they are different aggregates.GitRepository
不應該有一個Issue
的集合,因為它們是不同的聚合體。Issue
should not have a navigation property for the relatedGitRepository
since it is a different aggregate.Issue
不應該有關聯的GitRepository
的導航屬性,因為它是一個不同的聚合體。Issue
can haveRepositoryId
(as aGuid
).Issue
可以有RepositoryId
(Guid
類型)。
So, when you have anIssue
and need to haveGitRepository
related to this issue, you need to explicitly query it from database by theRepositoryId
.
所以,當你有一個Issue
,需要有與這個Issue
相關的GitRepository
,你需要顯式地通過RepositoryId
從數據庫中查詢。
For EF Core & Relational Databases 關於EF Core和關系型數據庫
In MongoDB, it is naturally not suitable to have such navigation properties/collections. If you do that, you find a copy of the destination aggregate object in the database collection of the source aggregate since it is being serialized to JSON on save.
在MongoDB中,自然不適合有這樣的導航屬性/集合。如果你這樣做,你會找到根據源聚合的數據庫集合拷貝的目標聚合對象,因為它在保存時被序列化為JSON。
However, EF Core & relational database developers may find this restrictive rule unnecessary since EF Core can handle it on database read and write. We see this an important rule that helps to reduce the complexity of the domain prevents potential problems and we strongly suggest to implement this rule. However, if you think it is practical to ignore this rule, see the Discussion About the Database Independence Principle section above.
然而,EF Core和關系型數據庫的開發者可能會發現這個限制性的規則是不必要的,因為EF Core可以在數據庫的讀寫中處理它。我們認為這是一條重要的規則,有助於降低領域的復雜性防止潛在的問題,我們強烈建議實現這條規則。然而,如果你認為忽略這條規則是切實可行的,請參閱上面關於數據庫獨立原則部分的討論。
Keep Aggregates Small 保持聚合體簡單
One good practice is to keep an aggregate simple and small. This is because an aggregate will be loaded and saved as a single unit and reading/writing a big object has performance problems. See the example below:
一個好的做法是讓一個聚合體保持簡單和小巧。這是因為一個聚合體將作為一個單元被加載和保存,讀/寫一個大的對象可能帶來性能問題。請看下面的例子:
Role
aggregate has a collection of UserRole
value objects to track the users assigned for this role. Notice that UserRole
is not another aggregate and it is not a problem for the rule Reference Other Aggregates Only By Id. However, it is a problem in practical. A role may be assigned to thousands (even millions) of users in a real life scenario and it is a significant performance problem to load thousands of items whenever you query a Role
from database (remember: Aggregates are loaded by their sub-collections as a single unit).
Role
聚合體有一個UserRole
的值對象集合,用來跟蹤為這個角色分配的用戶。請注意,UserRole
不是別的聚合體,對於只通過Id引用其他聚合體的規則來說,這不是問題。然而,這在實踐中是一個問題。在現實生活中,一個角色可能被分配給數上千(甚至上百萬)的用戶,每當你從數據庫中查詢一個Role
時,加載上千個個體是一個重要的性能問題(記住:聚合體是由它們的子集作為一個單元加載的)。
On the other hand, User
may have such a Roles
collection since a user doesn't have much roles in practical and it can be useful to have a list of roles while you are working with a User Aggregate
.
另一方面,User
可能有這樣一個Roles
集合,因為一個用戶在實際工作中並沒有太多的角色,當你在處理User Aggregate
的時候,有一個角色列表是很有用的。
If you think carefully, there is one more problem when Role
and User
both have the list of relation when use a non-relational database, like MongoDB. In this case, the same information is duplicated in different collections and it will be hard to maintain data consistency (whenever you add an item to User.Roles
, you need to add it to Role.Users
too).
如果你仔細想想,當使用非關系型數據庫(如MongoDB)時,當Role
和User
都有關系列表時還有一個問題。在這種情況下,相同的信息會在不同的集合中重復出現,這將很難保持數據的一致性(每當你在User.Roles
中添加一個項目,你也需要將它添加到Role.Users
中)。
So, determine your aggregate boundaries and size based on the following considerations;
因此,確定你的聚合體的界限和大小時請參考以下考慮因素:
- Objects used together.
- 一起使用的對象。
- Query (load/save) performance and memory consumption.
- 查詢(加載/保存)性能和內存消耗。
- Data integrity, validity and consistency.
- 數據的完整性、有效性和一致性。
In practical;
實際上: - Most of the aggregate roots will not have sub-collections.
- 大多數聚合根不會有子集。
- A sub-collection should not have more than 100-150 items inside it at the most case. If you think a collection potentially can have more items, don't define the collection as a part of the aggregate and consider to extract another aggregate root for the entity inside the collection.
- 在最大限度下,一個子集不應該有超過100-150個項。如果你認為一個集合可能有更多的項目,就不要把這個集合定義為聚合體的一部分,並考慮為這個集合中的實體提取成為另一個聚合根。
Primary Keys on the Aggregate Roots / Entities 聚合根/實體上的主鍵
- An aggregate root typically has a single
Id
property for its identifier (Primark Key: PK). We preferGuid
as the PK of an aggregate root entity (see the Guid Genertation document to learn why). - 一個聚合根通常有一個
Id
屬性作為其標識符(Primark Key: PK)。我們更喜歡用Guid
作為聚合根實體的PK(見Guid Genertation文檔以了解原因)。 - An entity (that's not the aggregate root) in an aggregate can use a composite primary key.
- 聚合中的實體(不是聚合根)可以使用組合主鍵。
For example, see the Aggregate root and the Entity below:
例如,請看下面的聚合根和實體。
Organization
has aGuid
identifier (Id
).Organization
有一個Guid
類型的標識符(Id
)。OrganizationUser
is a sub-collection of anOrganization
and has a composite primary key consists of theOrganizationId
andUserId
.OrganizationUser
是一個Organization
的子集,有一個由OrganizationId
和UserId
組成的組合主鍵。
That doesn't mean sub-collection entities should always have composite PKs. They may have single Id
properties when it's needed.
這並不意味着子集實體應該總是有復合PK。必要時,他們可以有單一的Id
屬性。
Composite PKs are actually a concept of relational databases since the sub-collection entities have their own tables and needs to a PK. On the other hand, for example, in MongoDB you don't need to define PK for the sub-collection entities at all since they are stored as a part of the aggregate root.
組合主鍵實際上是關系型數據庫的一個概念,因為子集實體有自己的表,需要一個主鍵。另一方面,例如在MongoDB中,你根本不需要為子集實體定義主鍵,因為它們是作為聚合根的一部分來存儲的。
Constructors of the Aggregate Roots / Entities 聚合根/實體的構造函數
The constructor is located where the lifecycle of an entity begins. There are a some responsibilities of a well designed constructor:
構造函數位於一個實體的生命周期開始的地方。一個設計良好的構造函數應具有一下職責:
- Gets the required entity properties as parameters to create a valid entity. Should force to pass only for the required parameters and may get non-required properties as optional parameters.
- 獲取所需的實體屬性作為參數來創建一個有效的實體。應該強制只傳遞必要的參數,並可以將非必要的屬性作為可選參數。
- Checks validity of the parameters.
- 檢查參數的有效性。
- Initializes sub-collections.
- 初始化子集。
Example Issue
(Aggregate Root) constructor Issue
(聚合根)的構造函數示例:
Issue
class properly forces to create a valid entity by getting minimum required properties in its constructor as parameters.Issue
類通過在其構造函數中獲得最小限度的所需屬性作為參數,來強制創建一個正確有效的實體。- The constructor validates the inputs (
Check.NotNullOrWhiteSpace(...)
throwsArgumentException
if the given value is empty). - 構造函數驗證輸入(
Check.NotNullOrWhiteSpace(...)
如果給定值為空,則拋出ArgumentException
)。 - It initializes the sub-collections, so you don't get a null reference exception when you try to use the
Labels
collection after creating theIssue
. - 它對子集進行初始化,所以當你在創建
Issue
之后試圖使用Labels
集合時,你不會得到一個空引用異常。 - The constructor also takes the
id
and passes to thebase
class. We don't generateGuid
s inside the constructor to be able to delegate this responsibility to another service (see Guid Generation). - 構造函數也接受
id
並傳遞給基類。我們不在構造函數中生成Guid
s,以便能夠將這個責任委托給其它服務處理(詳見Guid Generation)。 - Private empty constructor is necessary for ORMs. We made it private to prevent accidently using it in our own code.
- 私有化空構造函數對ORM來說是必要的。我們把它變成私有的,以防止在我們自己的代碼中意外地使用它。
See the Entities document to learn more about creating entities with the ABP Framework.
參見實體文檔以了解有關用ABP框架創建實體的更多信息。
Entity Property Accessors & Methods 實體屬性訪問器和方法
The example above may seem strange to you! For example, we force to pass a non-null Title
in the constructor. However, the developer may then set the Title
property to null
without any control. This is because the example code above just focuses on the constructor.
上面的例子對你來說可能很奇怪!例如,我們強制在構造函數中傳遞一個非空的Title
。然而,開發人員可能會在沒有任何控制的情況下將Title
屬性設置為null
。這是因為上面的例子代碼只是集中在構造函數上。
If we declare all the properties with public setters (like the example Issue
class above), we can't force validity and integrity of the entity in its lifecycle. So;
如果我們用公共的設置器聲明所有的屬性(就像上面的Issue
類的例子),我們就不能在實體的生命周期中強制保持其有效性和完整性。所以:
- Use private setter for a property when you need to perform any logic while setting that property.
- 當你需要在設置一個屬性時執行任何邏輯,請為該屬性使用私有設置器。
- Define public methods to manipulate such properties.
- 定義公共方法來操作這些屬性。
Example: Methods to change the properties in a controlled way
RepositoryId
setter made private and there is no way to change it after creating anIssue
because this is what we want in this domain: An issue can't be moved to another repository.RepositoryId
設置器被做成私有的,在創建Issue
后沒有辦法改變它,因為這就是我們在這個領域范圍所希望的。一個Issue
不能被轉移到另一個資源庫。Title
setter made private andSetTitle
method has been created if you want to change it later in a controlled way.- 如果你想以后以可控的方式改變它,將
Title
設置器變成私有的,並創建SetTitle
方法。 Text
andAssignedUserId
has public setters since there is no restriction on them. They can be null or any other value. We think it is unnecessary to define separate methods to set them. If we need later, we can add methods and make the setters private. Breaking changes are not problem in the domain layer since the domain layer is an internal project, it is not exposed to clients.Text
和AssignedUserId
有公有設置器,因為對它們沒有任何限制。它們可以是null
或任何其他值。我們認為沒有必要單獨定義方法來設置它們。如果我們以后需要,我們可以添加方法並使其設置器成為私有的。由於領域層是一個內層項目,它沒有暴露給客戶,所以破壞性的改變在域層中不是問題。IsClosed
andIssueCloseReason
are pair properties. DefinedClose
andReOpen
methods to change them together. In this way, we prevent to close an issue without any reason.IsClosed
和IssueCloseReason
是一對屬性。定義了Close
和ReOpen
方法來改變它們。通過這種方式,我們可以防止在沒有任何原因的情況下關閉一個Issue
。
Business Logic & Exceptions in the Entities 實體中的業務邏輯和異常處理
When you implement validation and business logic in the entities, you frequently need to manage the exceptional cases.
當你在實體中實現驗證和業務邏輯時,你經常需要處理異常情況。
In these cases;
例如:
- Create domain specific exceptions.
- 生成領域的特例異常。
- Throw these exceptions in the entity methods when necessary.
- 必要時在實體方法中拋出這些異常。
There are two business rules here;
有以下兩條業務規則:
- A locked issue can not be re-opened.
- 一個被鎖定的
Issue
不能被重新打開。 - You can not lock an open issue.
- 你不能鎖定一個打開的
Issue
。
Issue
class throws an IssueStateException
in these cases to force the business rules:
在這些情況下,Issue
類會拋出一個IssueStateException
,以強制執行這條業務規則。
There are two potential problems of throwing such exceptions;
拋出這種異常有兩個潛在的問題:
- In case of such an exception, should the end user see the exception (error) message? If so, how do you localize the exception message? You can not use the localization system, because you can't inject and use
IStringLocalizer
in the entities. - 如果出現這樣的異常,終端用戶是否應該看到異常(錯誤)信息?如果可以看到,你如何將異常信息進行本地化?你不能使用本地化系統,因為你不能在實體中注入和使用
IStringLocalizer
。 - For a web application or HTTP API, what HTTP Status Code should return to the client?
- 對於一個Web應用程序或HTTP API,應該向客戶端返回什么樣的HTTP狀態代碼?
ABP's Exception Handling system solves these and similar problems.
ABP的異常處理系統解決了這些以及類似的問題。
Example: Throwing a business exception with code 示例:用編碼拋出一個業務異常
IssueStateException
class inherits theBusinessException
class. ABP returns 403 (forbidden) HTTP Status code by default (instead of 500 - Internal Server Error) for the exceptions derived from theBusinessException
.IssueStateException
類繼承了BusinessException
類。對於從BusinessException
派生的異常,ABP默認返回403(禁止)HTTP狀態代碼(而不是500 - 內部服務器錯誤)。- The
code
is used as a key in the localization resource file to find the localized message. - 這個
code
被用作本地化資源文件中的一個關鍵字,通過它可以找到本地化的信息。
Now, we can change the ReOpen
method as shown below:
現在,我們可以改變ReOpen
方法,如下圖所示。
Use constants instead of magic strings.
使用常量而不是特殊的字符串。
And add an entry to the localization resource like below:
並在本地化資源中添加一個條目,如下所示:
- When you throw the exception, ABP automatically uses this localized message (based on the current language) to show to the end user.
- 當你拋出異常時,ABP自動使用這個本地化的消息(基於當前的語言)來顯示給終端用戶。
- The exception code (
IssueTracking:CanNotOpenLockedIssue
here) is also sent to the client, so it may handle the error case
programmatically. - 異常代碼(此處為
IssueTracking:CanNotOpenLockedIssue
)也會被發送到客戶端,因此它可以通過程序化地處理這些錯誤信息。
For this example, you could directly throw BusinessException
instead of defining a specialized IssueStateException
. The result will be same. See the exception handling document for all the details.
對於這個例子,你可以直接拋出BusinessException
而不是定義一個特定的IssueStateException
。結果將是一樣的。所有的細節見異常處理文檔。
Business Logic in Entities Requiring External Services 實體中的業務邏輯需要外部服務
It is simple to implement a business rule in an entity method when the business logic only uses the properties of that entity. What if the business logic requires to query database or use any external services that should be resolved from the dependency injection system. Remember; Entities can not inject services!
當業務邏輯只使用該實體的屬性時,在實體方法中實現業務規則很簡單。如果業務邏輯需要查詢數據庫或使用任何從依賴注入系統中的外部服務來解決問題,該怎么辦?請記住,實體不允許注入服務。
There are two common ways of implementing such a business logic:
有兩種常見的方式來實現這樣的業務邏輯:
- Implement the business logic on an entity method and get external dependencies as parameters of the method.
- 在一個實體方法上實現業務邏輯,並作為方法的參數獲得外部依賴。
- Create a Domain Service.
- 創建一個領域服務。
Domain Services will be explained later. But, now let's see how it can be implemented in the entity class.
領域服務將在后面解釋。但是,現在讓我們看看如何在實體類中實現它。
Example: Business Rule: Can not assign more than 3 open issues to a user concurrently 示例:業務規則:不能給一個用戶同時分配超過3個開放問題
AssignedUserId
property setter made private. So, the only way to change it to use theAssignToAsync
andCleanAssignment
methods.AssignedUserId
屬性設置器做成私有的。所以,改變它的唯一方法是使用AssignToAsync
和CleanAssignment
方法。AssignToAsync
gets anAppUser
entity. Actually, it only uses the user.Id, so you could get aGuid
value, likeuserId
. However, this way ensures that theGuid
value isId
of an existing user and not a randomGuid
value.AssignToAsync
獲得一個AppUser
實體。實際上,它只使用user.Id,所以你可以得到一個Guid
值,比如userId
。然而,這種方式可以確保Guid
值是現有用戶的Id
,而不是一個隨機的Guid
值。IUserIssueService
is an arbitrary service that is used to get open issue count for a user. It's the responsibility of the code part (that calls theAssignToAsync
) to resolve theIUserIssueService
and pass here.IUserIssueService
是一個任意的服務,用於獲取用戶的開放問題計數值。代碼部分(調用AssignToAsync
)的職責就是解決IUserIssueService
並在此傳遞它。AssignToAsync
throws exception if the business rule doesn't meet.- 如果不滿足業務規則
AssignToAsync
會拋出異常。 - Finally, if everything is correct,
AssignedUserId
property is set. - 最后,如果一切正確,
AssignedUserId
屬性被設置。
This method perfectly guarantees to apply the business logic when you want to assign an issue to a user. However, it has some problems;
當你想把一個問題分配給一個用戶時,這個方法可以完美地保證應用業務邏輯。然而,它也有一些問題,如下所示:
- It makes the entity class depending on an external service which makes the entity complicated.
- 它使實體類依賴於外部服務,從而使實體變得復雜。
- It makes hard to use the entity. The code that uses the entity now needs to inject
IUserIssueService
and pass to theAssignToAsync
method. - 它使得很難使用實體。使用該實體的代碼現在需要注入
IUserIssueService
並傳遞給AssignToAsync
方法。
An alternative way of implementing this business logic is to introduce a Domain Service, which will be explained later.
實現這一業務邏輯的另一種方法是引入一個領域服務,這將在后面解釋。