DCI架構是如何解決DDD戰術建模缺點的? | IT人
文章推薦指數: 80 %
DCI架構 · Data,也即資料/領域物件,用來描述系統“是什麼”,通常採用DDD中的戰術建模來識別當前模型的領域物件,等同於DDD分層架構中的領域層。
· Context ...
Togglenavigation
IT人
IT人
DCI架構是如何解決DDD戰術建模缺點的?
華為雲開發者社群發表於
2021-10-11
摘要:將DCI架構總結成一句話就是:領域物件(Object)在不同的場景(Context)中扮演(Cast)不同的角色(Role),角色之間通過互動(Interactive)來完成具體的業務邏輯。
本文分享自華為雲社群《實現DCI架構》,作者:元閏子。
前言
在物件導向程式設計的理念裡,應用程式是對現實世界的抽象,我們經常會將現實中的事物建模為程式語言中的類/物件(“是什麼”),而事物的行為則建模為方法(“做什麼”)。
物件導向程式設計有三大基本特性(封裝、繼承/組合、多型)和五大基本原則(單一職責原則、開放封閉原則、里氏替換原則、依賴倒置原則、介面分離原則),但知道這些還並不足以讓我們設計出好的程式,於是很多方法論就湧現了出來。
近來最火的當屬領域驅動設計(DDD),其中戰術建模提出的實體、值物件、聚合等建模方法,能夠很好的指導我們設計出符合現實世界的領域模型。
但DDD也不是萬能的,在某些應用場景下,按照傳統的戰術建模/物件導向方法設計出來的程式,也會存在可維護性差、違反單一職責原則等問題。
本文介紹的DCI建模方法可以看成是戰術建模的一種輔助,在某些場景下,它可以很好的彌補DDD戰術建模的一些缺點。
接下來,我們將會通過一個案例來介紹DCI是如何解決DDD戰術建模的這些缺點的。
本文涉及的程式碼歸檔在github專案:https://github.com/ruanrunxue/DCI-Architecture-Implementation
案例
考慮一個普通人的生活日常,他會在學校上課,也會趁著暑假去公司工作,在工作之餘去公園遊玩,也會像普通人一樣在家吃喝玩樂。
當然,一個人的生活還遠不止這些,為了講解方便,本文只針對這幾個典型的場景進行建模示例。
使用DDD建模
按照DDD戰術建模的思路,首先,我們會列出該案例的通用語言:
人、身份證、銀行卡、家、吃飯、睡覺、玩遊戲、學校、學生卡、學習、考試、公司、工卡、上班、下班、公園、購票、遊玩
接著,我們使用戰術建模技術(值物件、實體、聚合、領域服務、資源庫)對通用語言進行領域建模。
DDD建模後的程式碼目錄結構如下:
-aggregate:聚合
-company.go
-home.go
-park.go
-school.go
-entity:實體
-people.go
-vo:值物件
-account.go
-identity_card.go
-student_card.go
-work_card.go
我們將身份證、學生卡、工卡、銀行卡這幾個概念,建模為值物件(ValueObject):
packagevo
//身份證
typeIdentityCardstruct{
Iduint32
Namestring
}
//學生卡
typeStudentCardstruct{
Iduint32
Namestring
Schoolstring
}
//工卡
typeWorkCardstruct{
Iduint32
Namestring
Companystring
}
//銀行卡
typeAccountstruct{
Iduint32
Balanceint
}
...
接著我們將人建模成實體(Entity),他包含了身份證、學生卡等值物件,也具備吃飯、睡覺等行為:
packageentity
//人
typePeoplestruct{
vo.IdentityCard
vo.StudentCard
vo.WorkCard
vo.Account
}
//學習
func(p*People)Study(){
fmt.Printf("Student%+vstudying\n",p.StudentCard)
}
//考試
func(p*People)Exam(){
fmt.Printf("Student%+vexaming\n",p.StudentCard)
}
//吃飯
func(p*People)Eat(){
fmt.Printf("%+veating\n",p.IdentityCard)
p.Account.Balance--
}
//睡覺
func(p*People)Sleep(){
fmt.Printf("%+vsleeping\n",p.IdentityCard)
}
//玩遊戲
func(p*People)PlayGame(){
fmt.Printf("%+vplayinggame\n",p.IdentityCard)
}
//上班
func(p*People)Work(){
fmt.Printf("%+vworking\n",p.WorkCard)
p.Account.Balance++
}
//下班
func(p*People)OffWork(){
fmt.Printf("%+vgettingoffwork\n",p.WorkCard)
}
//購票
func(p*People)BuyTicket(){
fmt.Printf("%+vbuyingaticket\n",p.IdentityCard)
p.Account.Balance--
}
//遊玩
func(p*People)Enjoy(){
fmt.Printf("%+venjoyingparkscenery\n",p.IdentityCard)
}
最後,我們將學校、公司、公園、家建模成聚合(Aggregate),聚合由一個或多個實體、值物件組合而成,組織它們完成具體的業務邏輯:
packageaggregate
//家
typeHomestruct{
me*entity.People
}
func(h*Home)ComeBack(p*entity.People){
fmt.Printf("%+vcomebackhome\n",p.IdentityCard)
h.me=p
}
//執行Home的業務邏輯
func(h*Home)Run(){
h.me.Eat()
h.me.PlayGame()
h.me.Sleep()
}
//學校
typeSchoolstruct{
Namestring
students[]*entity.People
}
func(s*School)Receive(student*entity.People){
student.StudentCard=vo.StudentCard{
Id:rand.Uint32(),
Name:student.IdentityCard.Name,
School:s.Name,
}
s.students=append(s.students,student)
fmt.Printf("%sReceivestduent%+v\n",s.Name,student.StudentCard)
}
//執行School的業務邏輯
func(s*School)Run(){
fmt.Printf("%sstartclass\n",s.Name)
for_,student:=ranges.students{
student.Study()
}
fmt.Println("studentsstarttoeating")
for_,student:=ranges.students{
student.Eat()
}
fmt.Println("studentsstarttoexam")
for_,student:=ranges.students{
student.Exam()
}
fmt.Printf("%sfinishclass\n",s.Name)
}
//公司
typeCompanystruct{
Namestring
workers[]*entity.People
}
func(c*Company)Employ(worker*entity.People){
worker.WorkCard=vo.WorkCard{
Id:rand.Uint32(),
Name:worker.IdentityCard.Name,
Company:c.Name,
}
c.workers=append(c.workers,worker)
fmt.Printf("%sEmployworker%s\n",c.Name,worker.WorkCard.Name)
}
//執行Company的業務邏輯
func(c*Company)Run(){
fmt.Printf("%sstartwork\n",c.Name)
for_,worker:=rangec.workers{
worker.Work()
}
fmt.Println("workerstarttoeating")
for_,worker:=rangec.workers{
worker.Eat()
}
fmt.Println("workergetoffwork")
for_,worker:=rangec.workers{
worker.OffWork()
}
fmt.Printf("%sfinishwork\n",c.Name)
}
//公園
typeParkstruct{
Namestring
enjoyers[]*entity.People
}
func(p*Park)Welcome(enjoyer*entity.People){
fmt.Printf("%+vcometopark%s\n",enjoyer.IdentityCard,p.Name)
p.enjoyers=append(p.enjoyers,enjoyer)
}
//執行Park的業務邏輯
func(p*Park)Run(){
fmt.Printf("%sstarttoselltickets\n",p.Name)
for_,enjoyer:=rangep.enjoyers{
enjoyer.BuyTicket()
}
fmt.Printf("%sstartashow\n",p.Name)
for_,enjoyer:=rangep.enjoyers{
enjoyer.Enjoy()
}
fmt.Printf("showfinish\n")
}
那麼,根據上述方法建模出來的模型是這樣的:
模型的執行方法如下:
paul:=entity.NewPeople("Paul")
mit:=aggregate.NewSchool("MIT")
google:=aggregate.NewCompany("Google")
home:=aggregate.NewHome()
summerPalace:=aggregate.NewPark("SummerPalace")
//上學
mit.Receive(paul)
mit.Run()
//回家
home.ComeBack(paul)
home.Run()
//工作
google.Employ(paul)
google.Run()
//公園遊玩
summerPalace.Welcome(paul)
summerPalace.Run()
貧血模型VS充血模型(工程派VS學院派)
上一節中,我們使用DDD的戰術建模完成了該案例領域模型。
模型的核心是People實體,它有IdentityCard、StudentCard等資料屬性,也有Eat()、Study()、Work()等業務行為,非常符合現實世界中定義。
這也是學院派所倡導的,同時擁有資料屬性和業務行為的充血模型。
然而,充血模型並非完美,它也有很多問題,比較典型的是這兩個:
問題一:上帝類
People這個實體包含了太多的職責,導致它變成了一個名副其實的上帝類。
試想,這裡還是裁剪了很多“人”所包含的屬性和行為,如果要建模一個完整的模型,其屬性和方法之多,無法想象。
上帝類違反了單一職責原則,會導致程式碼的可維護性變得極差。
問題二:模組間耦合
School與Company本應該是相互獨立的,School不必關注上班與否,Company也不必關注考試與否。
但是現在因為它們都依賴了People這個實體,School可以呼叫與Company相關的Work()和OffWork()方法,反之亦然。
這導致模組間產生了不必要的耦合,違反了介面隔離原則。
這些問題都是工程派不能接受的,從軟體工程的角度,它們會使得程式碼難以維護。
解決這類問題的方法,比較常見的是對實體進行拆分,比如將實體的行為建模成領域服務,像這樣:
typePeoplestruct{
vo.IdentityCard
vo.StudentCard
vo.WorkCard
vo.Account
}
typeStudentServicestruct{}
func(s*StudentService)Study(p*entity.People){
fmt.Printf("Student%+vstudying\n",p.StudentCard)
}
func(s*StudentService)Exam(p*entity.People){
fmt.Printf("Student%+vexaming\n",p.StudentCard)
}
typeWorkerServicestruct{}
func(w*WorkerService)Work(p*entity.People){
fmt.Printf("%+vworking\n",p.WorkCard)
p.Account.Balance++
}
func(w*WorkerService)OffWOrk(p*entity.People){
fmt.Printf("%+vgettingoffwork\n",p.WorkCard)
}
//...
這種建模方法,解決了上述兩個問題,但也變成了所謂的貧血模型:People變成了一個純粹的資料類,沒有任何業務行為。
在人的心理上,這樣的模型並不能在建立起對現實世界的對應關係,不容易讓人理解,因此被學院派所抵制。
到目前為止,貧血模型和充血模型都有各有優缺點,工程派和學院派誰都無法說服對方。
接下來,輪到本文的主角出場了。
DCI架構
DCI(Data,Context,Interactive)架構是一種物件導向的軟體架構模式,在《TheDCIArchitecture:ANewVisionofObject-OrientedProgramming》一文中被首次提出。
與傳統的物件導向相比,DCI能更好地對資料和行為之間的關係進行建模,從而更容易被人理解。
Data,也即資料/領域物件,用來描述系統“是什麼”,通常採用DDD中的戰術建模來識別當前模型的領域物件,等同於DDD分層架構中的領域層。
Context,也即場景,可理解為是系統的UseCase,代表了系統的業務處理流程,等同於DDD分層架構中的應用層。
Interactive,也即互動,是DCI相對於傳統物件導向的最大發展,它認為我們應該顯式地對領域物件(Object)在每個業務場景(Context)中扮演(Cast)的角色(Role)進行建模。
Role代表了領域物件在業務場景中的業務行為(“做什麼”),Role之間通過互動完成完整的義務流程。
這種角色扮演的模型我們並不陌生,在現實的世界裡也是隨處可見,比如,一個演員可以在這部電影裡扮演英雄的角色,也可以在另一部電影裡扮演反派的角色。
DCI認為,對Role的建模應該是面向Context的,因為特定的業務行為只有在特定的業務場景下才會有意義。
通過對Role的建模,我們就能夠將領域物件的方法拆分出去,從而避免了上帝類的出現。
最後,領域物件通過組合或繼承的方式將Role整合起來,從而具備了扮演角色的能力。
DCI架構一方面通過角色扮演模型使得領域模型易於理解,另一方面通過“小類大物件”的手法避免了上帝類的問題,從而較好地解決了貧血模型和充血模型之爭。
另外,將領域物件的行為根據Role拆分之後,模組更加的高內聚、低耦合了。
使用DCI建模
回到前面的案例,使用DCI的建模思路,我們可以將“人”的幾種行為按照不同的角色進行劃分。
吃完、睡覺、玩遊戲,是作為人類角色的行為;學習、考試,是作為學生角色的行為;上班、下班,是作為員工角色的行為;購票、遊玩,則是作為遊玩者角色的行為。
“人”在家這個場景中,充當的是人類的角色;在學校這個場景中,充當的是學生的角色;在公司這個場景中,充當的是員工的角色;在公園這個場景中,充當的是遊玩者的角色。
需要注意的是,學生、員工、遊玩者,這些角色都應該具備人類角色的行為,比如在學校裡,學生也需要吃飯。
最後,根據DCI建模出來的模型,應該是這樣的:
在DCI模型中,People不再是一個包含眾多屬性和方法的“上帝類”,這些屬性和方法被拆分到多個Role中實現,而People由這些Role組合而成。
另外,School與Company也不再耦合,School只引用了Student,不能呼叫與Company相關的Worker的Work()和OffWorker()方法。
程式碼實現DCI模型
DCI建模後的程式碼目錄結構如下;
-context:場景
-company.go
-home.go
-park.go
-school.go
-object:物件
-people.go
-data:資料
-account.go
-identity_card.go
-student_card.go
-work_card.go
-role:角色
-enjoyer.go
-human.go
-student.go
-worker.go
從程式碼目錄結構上看,DDD和DCI架構相差並不大,aggregate目錄演變成了context目錄;vo目錄演變成了data目錄;entity目錄則演變成了object和role目錄。
首先,我們實現基礎角色Human,Student、Worker、Enjoyer都需要組合它:
packagerole
//人類角色
typeHumanstruct{
data.IdentityCard
data.Account
}
func(h*Human)Eat(){
fmt.Printf("%+veating\n",h.IdentityCard)
h.Account.Balance--
}
func(h*Human)Sleep(){
fmt.Printf("%+vsleeping\n",h.IdentityCard)
}
func(h*Human)PlayGame(){
fmt.Printf("%+vplayinggame\n",h.IdentityCard)
}
接著,我們再實現其他角色,需要注意的是,Student、Worker、Enjoyer不能直接組合Human,否則People物件將會有4個Human子物件,與模型不符:
//錯誤的實現
typeWorkerstruct{
Human
}
func(w*Worker)Work(){
fmt.Printf("%+vworking\n",w.WorkCard)
w.Balance++
}
...
typePeoplestruct{
Human
Student
Worker
Enjoyer
}
funcmain(){
people:=People{}
fmt.Printf("People:%+v",people)
}
//結果輸出,People中有4個Human:
//People:{Human:{}Student:{Human:{}}Worker:{Human:{}}Enjoyer:{Human:{}}}
為解決該問題,我們引入了xxxTrait介面:
//人類角色特徵
typeHumanTraitinterface{
CastHuman()*Human
}
//學生角色特徵
typeStudentTraitinterface{
CastStudent()*Student
}
//員工角色特徵
typeWorkerTraitinterface{
CastWorker()*Worker
}
//遊玩者角色特徵
typeEnjoyerTraitinterface{
CastEnjoyer()*Enjoyer
}
Student、Worker、Enjoyer組合HumanTrait,並通過Compose(HumanTrait)方法進行特徵注入,只要在注入的時候保證Human是同一個,就可以解決該問題了。
//學生角色
typeStudentstruct{
//Student同時也是個普通人,因此組合了Human角色
HumanTrait
data.StudentCard
}
//注入人類角色特徵
func(s*Student)Compose(traitHumanTrait){
s.HumanTrait=trait
}
func(s*Student)Study(){
fmt.Printf("Student%+vstudying\n",s.StudentCard)
}
func(s*Student)Exam(){
fmt.Printf("Student%+vexaming\n",s.StudentCard)
}
//員工角色
typeWorkerstruct{
//Worker同時也是個普通人,因此組合了Human角色
HumanTrait
data.WorkCard
}
//注入人類角色特徵
func(w*Worker)Compose(traitHumanTrait){
w.HumanTrait=trait
}
func(w*Worker)Work(){
fmt.Printf("%+vworking\n",w.WorkCard)
w.CastHuman().Balance++
}
func(w*Worker)OffWork(){
fmt.Printf("%+vgettingoffwork\n",w.WorkCard)
}
//遊玩者角色
typeEnjoyerstruct{
//Enjoyer同時也是個普通人,因此組合了Human角色
HumanTrait
}
//注入人類角色特徵
func(e*Enjoyer)Compose(traitHumanTrait){
e.HumanTrait=trait
}
func(e*Enjoyer)BuyTicket(){
fmt.Printf("%+vbuyingaticket\n",e.CastHuman().IdentityCard)
e.CastHuman().Balance--
}
func(e*Enjoyer)Enjoy(){
fmt.Printf("%+venjoyingscenery\n",e.CastHuman().IdentityCard)
}
最後,實現People這一領域物件:
packageobject
typePeoplestruct{
//People物件扮演的角色
role.Human
role.Student
role.Worker
role.Enjoyer
}
//People實現了HumanTrait、StudentTrait、WorkerTrait、EnjoyerTrait等特徵介面
func(p*People)CastHuman()*role.Human{
return&p.Human
}
func(p*People)CastStudent()*role.Student{
return&p.Student
}
func(p*People)CastWorker()*role.Worker{
return&p.Worker
}
func(p*People)CastEnjoyer()*role.Enjoyer{
return&p.Enjoyer
}
//People在初始化時,完成對角色特徵的注入
funcNewPeople(namestring)*People{
//一些初始化的邏輯...
people.Student.Compose(people)
people.Worker.Compose(people)
people.Enjoyer.Compose(people)
returnpeople
}
進行角色拆分之後,在實現Home、School、Company、Park等場景時,只需依賴相應的角色即可,不再需要依賴People這一領域物件:
//家
typeHomestruct{
me*role.Human
}
func(h*Home)ComeBack(human*role.Human){
fmt.Printf("%+vcomebackhome\n",human.IdentityCard)
h.me=human
}
//執行Home的業務邏輯
func(h*Home)Run(){
h.me.Eat()
h.me.PlayGame()
h.me.Sleep()
}
//學校
typeSchoolstruct{
Namestring
students[]*role.Student
}
func(s*School)Receive(student*role.Student){
//初始化StduentCard邏輯...
s.students=append(s.students,student)
fmt.Printf("%sReceivestduent%+v\n",s.Name,student.StudentCard)
}
//執行School的業務邏輯
func(s*School)Run(){
fmt.Printf("%sstartclass\n",s.Name)
for_,student:=ranges.students{
student.Study()
}
fmt.Println("studentsstarttoeating")
for_,student:=ranges.students{
student.CastHuman().Eat()
}
fmt.Println("studentsstarttoexam")
for_,student:=ranges.students{
student.Exam()
}
fmt.Printf("%sfinishclass\n",s.Name)
}
//公司
typeCompanystruct{
Namestring
workers[]*role.Worker
}
func(c*Company)Employ(worker*role.Worker){
//初始化WorkCard邏輯...
c.workers=append(c.workers,worker)
fmt.Printf("%sEmployworker%s\n",c.Name,worker.WorkCard.Name)
}
//執行Company的業務邏輯
func(c*Company)Run(){
fmt.Printf("%sstartwork\n",c.Name)
for_,worker:=rangec.workers{
worker.Work()
}
fmt.Println("workerstarttoeating")
for_,worker:=rangec.workers{
worker.CastHuman().Eat()
}
fmt.Println("workergetoffwork")
for_,worker:=rangec.workers{
worker.OffWork()
}
fmt.Printf("%sfinishwork\n",c.Name)
}
//公園
typeParkstruct{
Namestring
enjoyers[]*role.Enjoyer
}
func(p*Park)Welcome(enjoyer*role.Enjoyer){
fmt.Printf("%+vcomepark%s\n",enjoyer.CastHuman().IdentityCard,p.Name)
p.enjoyers=append(p.enjoyers,enjoyer)
}
//執行Park的業務邏輯
func(p*Park)Run(){
fmt.Printf("%sstarttoselltickets\n",p.Name)
for_,enjoyer:=rangep.enjoyers{
enjoyer.BuyTicket()
}
fmt.Printf("%sstartashow\n",p.Name)
for_,enjoyer:=rangep.enjoyers{
enjoyer.Enjoy()
}
fmt.Printf("showfinish\n")
}
模型的執行方法如下:
paul:=object.NewPeople("Paul")
mit:=context.NewSchool("MIT")
google:=context.NewCompany("Google")
home:=context.NewHome()
summerPalace:=context.NewPark("SummerPalace")
//上學
mit.Receive(paul.CastStudent())
mit.Run()
//回家
home.ComeBack(paul.CastHuman())
home.Run()
//工作
google.Employ(paul.CastWorker())
google.Run()
//公園遊玩
summerPalace.Welcome(paul.CastEnjoyer())
summerPalace.Run()
寫在最後
從前文所描述的場景中,我們可以發現傳統的DDD/物件導向設計方法在對行為進行建模方面存在著不足,進而導致了所謂的貧血模型和充血模型之爭。
DCI架構的出現很好的彌補了這一點,它通過引入角色扮演的思想,巧妙地解決了充血模型中上帝類和模組間耦合問題,而且不影響模型的正確性。
當然,DCI架構也不是萬能的,在行為較少的業務模型中,使用DCI來建模並不合適。
最後,將DCI架構總結成一句話就是:領域物件(Object)在不同的場景(Context)中扮演(Cast)不同的角色(Role),角色之間通過互動(Interactive)來完成具體的業務邏輯。
參考
1、TheDCIArchitecture:ANewVisionofObject-OrientedProgramming,TrygveReenskaug&JamesO.Coplien
2、軟體設計的演變過程,_張曉龍_
3、ImplementDomainObjectinGolang,_張曉龍_
4、DCI:程式碼的可理解性,chelsea
5、DCIinC++,MagicBowen
點選關注,第一時間瞭解華為雲新鮮技術~
相關文章
MySQL高可用架構-MMM、MHA、MGR、PXC
2021-10-02
MySQL
JetpackCompose學習(7)——MD樣式架構元件Scaffold及導航底部選單
2021-10-04
Dapr閃電說-Dapr落地雲原生架構
2021-10-07
Kubernetes全棧架構師(資源排程下)--學習筆記
2021-10-08
架構師全棧Kubernetes
細說JUC的執行緒池架構
2021-10-08
HBase與Cassandra架構對比分析的經驗分享
2021-10-08
HBase
架構師必備:MySQL主從同步原理和應用
2021-10-08
架構師MySQL
HongHu雲架構common-service程式碼結構分析
2021-10-08
【分散式微服務企業快速架構】SpringCloud分散式、微服務、雲架構快速開發平臺
2021-10-08
微服務Spring
Sentry監控-Snuba資料中臺架構簡介(Kafka+Clickhouse)
2021-10-09
Kafka
Sentry監控-Snuba資料中臺架構(DataModel簡介)
2021-10-10
最新文章
【矩陣基礎與維度分析】【公式細節推導】矩陣非線性最小二乘法泰勒展開
故障分析|MySQL從機故障重啟後主從同步報錯案例分析
網友:Go你是Google的,Go:我不是
Nebula在Akulaku智慧風控的實踐:圖模型的訓練與部署
基於飛凌FETA40i-C核心板實現的呼吸機解決方案
寧波國際賽車場圈速排行分析大屏製作教程
m1也能用的視訊無損放大軟體:topazvideoenhanceaimac版
[原始碼解析]NVIDIAHugeCTR,GPU版本引數伺服器---(7)---DistributedHash之前向傳播
vue--vue-router元件對映到路由
Rust在國內的發展逐步向上
『無為則無心』Python物件導向—58、類方法和靜態方法
為什麼機器學習會選擇Python語言?這篇文章一定要看!
延伸文章資訊
- 1DCI架構_百度百科
DCI是數據Data 場景Context 交互Interactions的簡稱,DCI是一種特別關注行為的模式(可以對應GoF行為模式)
- 2DCI架构是如何解决DDD战术建模缺点的?-华为开发者论坛
DCI架构 · Data,也即数据/领域对象,用来描述系统“是什么”,通常采用DDD中的战术建模来识别当前模型的领域对象,等同于DDD分层架构中的领域层。 · Context ...
- 3一文一点| 这就是你要了解的DCI 架构 - 腾讯云
我昨天晚上乘坐高铁,看了郑晔老师的极客专栏《软件设计之美》,其中讲面向对象继承的这课的思考题,提到了DCI架构。 这让我隐约想起来,记得《架构整洁之 ...
- 4DCI架構是如何解決DDD戰術建模缺點的? | IT人
DCI架構 · Data,也即資料/領域物件,用來描述系統“是什麼”,通常採用DDD中的戰術建模來識別當前模型的領域物件,等同於DDD分層架構中的領域層。 · Context ...
- 5张晓 龙中兴通讯资深软件架构师
DCI:Context 概念对应DDD:Domain Service. DCI:Role 的概念在DDD 中没有. < 对Role 的建模解决了贫⾎模型和充⾎模型之争. < 将Role 的建模看...