這是一個(gè)講解DDD落地的文章系列,作者是《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》的譯者滕云。本文章系列以一個(gè)真實(shí)的并已成功上線的軟件項(xiàng)目——碼如云(https://www.mryqr.com)為例,系統(tǒng)性地講解DDD在落地實(shí)施過程中的各種典型實(shí)踐,以及在面臨實(shí)際業(yè)務(wù)場(chǎng)景時(shí)的諸多取舍。
本系列包含以下文章:
- DDD入門
- DDD概念大白話
- 戰(zhàn)略設(shè)計(jì)
- 代碼工程結(jié)構(gòu)
- 請(qǐng)求處理流程
- 聚合根與資源庫(kù)(本文)
- 實(shí)體與值對(duì)象
- 應(yīng)用服務(wù)與領(lǐng)域服務(wù)
- 領(lǐng)域事件
- CQRS
案例項(xiàng)目介紹
既然DDD是“領(lǐng)域”驅(qū)動(dòng),那么我們便不能拋開業(yè)務(wù)而只講技術(shù),為此讓我們先從業(yè)務(wù)上了解一下貫穿本文章系列的案例項(xiàng)目 ——?碼如云(不是馬云,也不是碼云)。如你已經(jīng)在本系列的其他文章中了解過該案例,可跳過。
碼如云是一個(gè)基于二維碼的一物一碼管理平臺(tái),可以為每一件“物品”生成一個(gè)二維碼,并以該二維碼為入口展開對(duì)“物品”的相關(guān)操作,典型的應(yīng)用場(chǎng)景包括固定資產(chǎn)管理、設(shè)備巡檢以及物品標(biāo)簽等。
在使用碼如云時(shí),首先需要?jiǎng)?chuàng)建一個(gè)應(yīng)用(App),一個(gè)應(yīng)用包含了多個(gè)頁(yè)面(Page),也可稱為表單,一個(gè)頁(yè)面又可以包含多個(gè)控件(Control),比如單選框控件。應(yīng)用創(chuàng)建好后,可在應(yīng)用下創(chuàng)建多個(gè)實(shí)例(QR)用于表示被管理的對(duì)象(比如機(jī)器設(shè)備)。每個(gè)實(shí)例均對(duì)應(yīng)一個(gè)二維碼,手機(jī)掃碼便可對(duì)實(shí)例進(jìn)行相應(yīng)操作,比如查看實(shí)例相關(guān)信息或者填寫頁(yè)面表單等,對(duì)表單的一次填寫稱為提交(Submission);更多概念請(qǐng)參考碼如云術(shù)語(yǔ)。
在技術(shù)上,碼如云是一個(gè)無(wú)代碼平臺(tái),包含了表單引擎、審批流程和數(shù)據(jù)報(bào)表等多個(gè)功能模塊。碼如云全程采用DDD完成開發(fā),其后端技術(shù)棧主要有Java、Spring Boot和MongoDB等。
碼如云的源代碼是開源的,可以通過以下方式訪問:
碼如云源代碼:https://github.com/mryqr-com/mry-backend
聚合根與資源庫(kù)
在上一篇請(qǐng)求處理流程中我們講到,領(lǐng)域模型是DDD的核心,而聚合根又是領(lǐng)域模型的核心。從某種意義上講,DDD中其它組件均可看作是對(duì)聚合根的支撐或輔助。在本文中,我們將對(duì)聚合根以及與之密切相關(guān)的資源庫(kù)(Repository)做詳細(xì)的講解。
聚合根是什么
在DDD概念大白話一文中,我們講到了“什么是聚合根”,這里再重復(fù)一下。聚合根中的“聚合”即“高內(nèi)聚,低耦合”中的“內(nèi)聚”之意;而“根”則是“根部”的意思,也即聚合根是一種統(tǒng)領(lǐng)式的存在。事實(shí)上,并不存在一個(gè)教科書式的對(duì)聚合根的理論定義,你可以將聚合根理解為一個(gè)系統(tǒng)中最重要最顯著的那些名詞,這些名詞是其所在的軟件系統(tǒng)之所以存在的原因。為了給你一個(gè)直觀的理解,以下是幾個(gè)聚合根的例子:
- 在一個(gè)電商系統(tǒng)中,一個(gè)訂單(Order)對(duì)象表示一個(gè)聚合根
- 在一個(gè)CRM系統(tǒng)中,一個(gè)客戶(Customer)對(duì)象表示一個(gè)聚合根
- 在一個(gè)銀行系統(tǒng)中,一次交易(Transaction)對(duì)象表示一個(gè)聚合根
你可能會(huì)問,軟件中的概念已經(jīng)很多了,為什么還要搞出個(gè)聚合根的概念?我們認(rèn)為這里至少有2點(diǎn)原因:
- 聚合根遵循了軟件中“高內(nèi)聚,低耦合”的基本原則
- 聚合根體現(xiàn)了一種模塊化的原則,模塊化思想是被各個(gè)行業(yè)所證明的可以降低系統(tǒng)復(fù)雜度的一種思想。所謂的DDD是“軟件核心復(fù)雜性應(yīng)對(duì)之道”,也即這個(gè)意思,它將軟件系統(tǒng)在人腦中所呈現(xiàn)地更加有序和簡(jiǎn)單,讓人可以更好地理解和管控軟件系統(tǒng)。
在實(shí)際項(xiàng)目中識(shí)別聚合根時(shí),我們需要對(duì)業(yè)務(wù)有深入的了解,因?yàn)橹挥羞@樣你才知道到底哪些業(yè)務(wù)邏輯是內(nèi)聚在一起的。這也是我們一直建議程序員和架構(gòu)師們不要一味地埋頭于技術(shù)而要多關(guān)注業(yè)務(wù)的原因。
事實(shí)上,如果讓一個(gè)從來沒有接觸過DDD的人來建模,十有八九也能設(shè)計(jì)出上面的訂單、客戶和交易對(duì)象出來。沒錯(cuò),DDD絕非什么顛覆式的發(fā)明,依然只是在前人基礎(chǔ)上的一種進(jìn)步而已,這種進(jìn)步更多的體現(xiàn)在一些設(shè)計(jì)原則上,對(duì)此我們將在下文進(jìn)行詳細(xì)闡述。
聚合根基類
在代碼實(shí)現(xiàn)層面,一般的實(shí)踐是將所有的聚合根都繼承自一個(gè)公共基類AggregateRoot
:
//AggregateRoot
@Getter
public abstract class AggregateRoot implements Identified {
private String id;//聚合根ID
private String tenantId;//租戶ID
private Instant createdAt;//創(chuàng)建時(shí)間
private String createdBy;//創(chuàng)建人的MemberId
private String creator;//創(chuàng)建人姓名
private Instant updatedAt;//更新時(shí)間
private String updatedBy;//更新人MemberId
private String updater;//更新人姓名
private List<DomainEvent> events;//臨時(shí)存放領(lǐng)域事件
private LinkedList<OpsLog> opsLogs;//操作日志
@Version
@Getter(PRIVATE)
private Long _version;//版本號(hào),實(shí)現(xiàn)樂觀鎖
//...此處省略了AggregateRoot中行為方法
@Override
public String getIdentifier() {
return id;
}
}
源碼出處:com/mryqr/core/common/domain/AggregateRoot.java
在AggregateRoot
中,包含聚合根ID(id
)、創(chuàng)建信息(createdAt
和createdBy
)和更新信息(updatedAt
和updatedBy
)等數(shù)據(jù)。租戶ID(tenantId
)用于標(biāo)定聚合根所在的租戶(碼如云是一個(gè)多租戶系統(tǒng))。另外,events
用于臨時(shí)性存放聚合根中所產(chǎn)生的領(lǐng)域事件,我們將在領(lǐng)域事件一文中對(duì)此所詳細(xì)解釋。
實(shí)際的聚合根繼承自AggregateRoot
,例如,在碼如云中,分組(Group)聚合根的實(shí)現(xiàn)如下:
@Getter
@Document(GROUP_COLLECTION)
@TypeAlias(GROUP_COLLECTION)
@NoArgsConstructor(access = PRIVATE)
public class Group extends AggregateRoot {
private String name;//名稱
private String appId;//所在的app
private List<String> managers;//管理員
private List<String> members;//普通成員
private boolean archived;//是否歸檔
private String customId;//自定義編號(hào)
private boolean active;//是否啟用
private String departmentId;//由哪個(gè)部門同步而來
//...此處省略了Group的行為方法
}
源碼出處:com/mryqr/core/group/domain/Group.java
聚合根基本原則
從上面的代碼例子可以看出,聚合根只是普通的Java對(duì)象而已,真正使之成為聚合根的是一些特定的設(shè)計(jì)原則。
內(nèi)聚性原則
這個(gè)原則不用我們?cè)偌?xì)講了吧,估計(jì)你在大學(xué)里就學(xué)過,只舉個(gè)例子,對(duì)于上面的分組Group
對(duì)象來說,管理員managers
、普通成員members
以及啟用標(biāo)志active
均是Group
不可分割的屬性,這些屬性獨(dú)立于Group
是無(wú)法存在的。
對(duì)外黑盒原則
對(duì)外黑盒原則講的是,聚合根的外部(也即聚合根的調(diào)用方或客戶方)不需要關(guān)心聚合根內(nèi)部的實(shí)現(xiàn)細(xì)節(jié),而只需要通過調(diào)用聚合根向外界暴露的共有業(yè)務(wù)方法即可。具體表現(xiàn)為,外部對(duì)聚合根的調(diào)用只能通過根對(duì)象完成,而不能調(diào)用聚合根內(nèi)部對(duì)象上的方法。舉個(gè)例子,在碼如云中,管理員可以向分組(Group)中添加成員,具體的實(shí)現(xiàn)代碼如下:
//Group
public void addMembers(List<String> memberIds, User user) {
if (isSynced()) {
throw new MryException(GROUP_SYNCED,
"無(wú)法添加成員,已設(shè)置從部門同步。",
"groupId", this.getId());
}
this.members = concat(members.stream(), memberIds.stream())
.distinct()
.collect(toImmutableList());
addOpsLog("設(shè)置成員", user);
}
源碼出處:com/mryqr/core/group/domain/Group.java
這里,外部在向分組中添加成員時(shí),需要調(diào)用Group
上的addMembers()
方法,該方法知道將memberIds
添加到自身的members
字段中,這個(gè)過程對(duì)外部是不可見的。與之相對(duì)的另一種方式是,外部調(diào)用法先拿到Member
的members
引用,然后由外部自行向members
中添加memberIds
:
//外部調(diào)用方
@Transactional
public void addGroupMembers(String groupId, List<String> memberIds, User user) {
Group group = groupRepository.byIdAndCheckTenantShip(groupId, user);
if (group.isSynced()) {
throw new MryException(GROUP_SYNCED,
"無(wú)法添加成員,已設(shè)置從部門同步。",
"groupId", group.getId());
}
List<String> members = group.getMembers();
members.addAll(memberIds);
groupRepository.save(group);
log.info("Added members{} to group[{}].", memberIds, groupId);
}
源碼出處:com/mryqr/core/group/command/GroupCommandService.java
這種方式是一種反模式,存在以下缺點(diǎn):
- 外部需要了解
Group
的內(nèi)部結(jié)構(gòu),背離了對(duì)外黑盒原則,本例中,外部通過group.getMembers()
獲取到了Group
內(nèi)部的members
屬性 - 聚合根內(nèi)部的業(yè)務(wù)邏輯泄漏到了外部,背離了內(nèi)聚性原則,本例中,對(duì)
group.isSynced()
的調(diào)用原本應(yīng)該放在Group
中的,結(jié)果卻由外部承擔(dān)了該職責(zé)
在對(duì)外黑盒原則的指導(dǎo)下,聚合根自然形成了一個(gè)邊界,它站在這個(gè)邊界上向外聲明:“我所包圍著的內(nèi)部的所有均由我負(fù)責(zé),如果誰(shuí)想訪問我的內(nèi)部,直接訪問是被禁止的,只能通過我這個(gè)“根”來訪問?!?/p>
不變條件原則
不變條件(Invariants)表示聚合根需要保證其內(nèi)部在任何時(shí)候均處于一種合法的狀態(tài)(也即數(shù)據(jù)一致性需要得到保證),一個(gè)常見的例子是訂單(Order)中有訂單項(xiàng)(OrderItem)和訂單價(jià)格(Price),當(dāng)訂單項(xiàng)發(fā)生變化時(shí),其價(jià)格應(yīng)該隨之發(fā)生變化,并且這兩種變化應(yīng)該在訂單的同一個(gè)業(yè)務(wù)方法中完成。這一點(diǎn)是好理解的,既然聚合根對(duì)外是一個(gè)黑盒,那么外界便不會(huì)負(fù)責(zé)給你聚合根擦屁股,你聚合根自己需要保證自身的正確性。
在碼如云中,應(yīng)用管理員可以向分組(Group)中添加分組管理員。這其中有層隱含意思是,既然分組管理員也是分組成員,那么在添加分組管理員的同時(shí)需要一并將其添加到分組成員中,具體實(shí)現(xiàn)代碼如下:
//Group
public void addManager(String memberId, User user) {
if (!this.members.contains(memberId)) {
this.members = concat(members.stream(), Stream.of(memberId))
.distinct()
.collect(toImmutableList());
}
this.managers = concat(this.managers.stream(), Stream.of(memberId))
.distinct()
.collect(toImmutableList());
raiseEvent(new GroupManagersChangedEvent(this.getId(), this.getAppId(), user));
addOpsLog("添加管理員", user);
}
源碼出處:com/mryqr/core/group/domain/Group.java
在本例的添加分組管理員addManager()
方法中,我們除了向managers
中添加成員外,還保證了該成員也出現(xiàn)在members
中。這里的“分組管理員也是分組成員”即是一種不變條件,我們需要在聚合根內(nèi)部保證不變條件不被破壞,因?yàn)椴蛔儣l件往往意味著核心的業(yè)務(wù)邏輯。
通過ID引用其他聚合根原則
當(dāng)一個(gè)聚合根需要引用另一個(gè)聚合根時(shí),并不需要維持對(duì)另一聚合根的整體引用,而是只需通過ID進(jìn)行引用即可。這個(gè)原則的出發(fā)點(diǎn)是:聚合根和聚合根之間是一種平級(jí)關(guān)系,并不是隸屬關(guān)系,每個(gè)聚合根本身是一個(gè)相對(duì)獨(dú)立的模塊,其與其他聚合根的關(guān)系應(yīng)該通過ID這種松耦合的方式進(jìn)行引用,如果整體引用則更像是一種包含關(guān)系。
在碼如云中,分組(Group)通過appId
引用其所屬的應(yīng)用(App),通過departmentId
引用所同步的部門(Department),而在managers
和members
字段中,則是以memberId
引用相應(yīng)成員(Member):
@Getter
@Document(GROUP_COLLECTION)
@TypeAlias(GROUP_COLLECTION)
@NoArgsConstructor(access = PRIVATE)
public class Group extends AggregateRoot {
private String name;//名稱
private String appId;//所在的app
private List<String> managers;//管理員
private List<String> members;//普通成員
private boolean archived;//是否歸檔
private String customId;//自定義編號(hào)
private boolean active;//是否啟用
private String departmentId;//由哪個(gè)部門同步而來
//...省略其他代碼
}
源碼出處:com/mryqr/core/group/domain/Group.java
與基礎(chǔ)設(shè)施無(wú)關(guān)原則
既然整個(gè)領(lǐng)域模型與基礎(chǔ)設(shè)施無(wú)關(guān),那么位于領(lǐng)域模型之內(nèi)的聚合根自然也不能與基礎(chǔ)設(shè)施相關(guān),這樣好處是將業(yè)務(wù)復(fù)雜度與技術(shù)復(fù)雜度解耦開來,讓業(yè)務(wù)模型可以獨(dú)立于技術(shù)設(shè)施而完成自身的演變。比如,假設(shè)一個(gè)項(xiàng)目需要從Spring框架遷移到Guice框架,此時(shí)如果能夠保證領(lǐng)域模型與基礎(chǔ)設(shè)施的無(wú)關(guān)性,那么對(duì)領(lǐng)域模型的遷移過程講變得非常簡(jiǎn)單,基本上無(wú)需修改任何代碼直接拷貝到新的項(xiàng)目中即可。
事實(shí)上,碼如云尚未完全做到這一點(diǎn),從上面的例子中可以看到,AggregateRoot
和Group
對(duì)Spring框架中的@Version
、@Document
和@TypeAlias
3個(gè)與持久化相關(guān)的注解存在引用。如需解決這個(gè)問題,可以考慮在領(lǐng)域模型之外另建專門用于數(shù)據(jù)庫(kù)訪問的持久化對(duì)象(Persistence Model)。但是,引入持久化對(duì)象是有成本的,比如需要維護(hù)領(lǐng)域?qū)ο笈c持久化對(duì)象之間的相互轉(zhuǎn)化等。在碼如云,我們選擇了妥協(xié),一方面考慮到持久化對(duì)象的成本,另一方面我們也預(yù)見在將來要遷移出Spring框架的幾率是非常小的。不過,除了前面提到的3個(gè)注解之外,碼如云中的聚合根可以做到對(duì)基礎(chǔ)設(shè)施沒有任何其他引用。關(guān)于持久化對(duì)象,在Stackoverflow上有過非常有意義的討論,讀者可自行閱覽。
跨聚合根用例
通常來講,一個(gè)業(yè)務(wù)用例只會(huì)操作一個(gè)(或一種)聚合根。但有時(shí),一個(gè)業(yè)務(wù)用例可能會(huì)導(dǎo)致多個(gè)(或多種)聚合根對(duì)象的更新,此時(shí)可分兩種情況:
- 如果聚合根位于不同的進(jìn)程空間(比如不同的微服務(wù))中,那么解決方式一是可以使用事件驅(qū)動(dòng)架構(gòu)(EDA),二是通過全局事務(wù)(比如JTA)完成。基于全局事務(wù)的性能和效率低下等問題,DDD社區(qū)一般建議采用事件驅(qū)動(dòng)架構(gòu),即在一個(gè)進(jìn)程空間中只對(duì)其包含的聚合根進(jìn)行操作,然后通過向其他進(jìn)程空間發(fā)送事件通知的方式,使得其他進(jìn)程空間做相應(yīng)的聚合根更新。
- 如果聚合根位于同一個(gè)進(jìn)程空間,此時(shí)依然可以選擇事件驅(qū)動(dòng)架構(gòu),但是另一種更簡(jiǎn)單實(shí)用的方式是直接同時(shí)更新多個(gè)聚合根,畢竟此時(shí)對(duì)所有聚合根的更新均處于同一個(gè)本地事務(wù)中。
碼如云是一個(gè)單體系統(tǒng),因此屬于以上的第2種情況,我們根據(jù)聚合根之間的業(yè)務(wù)緊密程度的不同,在有些場(chǎng)景下選擇了同時(shí)更新多個(gè)聚合根,在另一些場(chǎng)景下則選擇通過事件驅(qū)動(dòng)機(jī)制解決。比如,在“創(chuàng)建實(shí)例”的用例中,除了創(chuàng)建實(shí)例(QR)之外,還需要?jiǎng)?chuàng)建該實(shí)例對(duì)應(yīng)的碼牌(Plate),由于“有實(shí)例就必有碼牌”,因此它們之間是緊密聯(lián)系的,故在碼如云中我們選擇了在同一個(gè)本地事務(wù)中同時(shí)更新實(shí)例和碼牌:
//QrCommandService
@Transactional
public CreateQrResponse createQr(CreateQrCommand command, User user) {
String name = command.getName();
String groupId = command.getGroupId();
Group group = groupRepository.cachedByIdAndCheckTenantShip(groupId, user);
String appId = group.getAppId();
App app = appRepository.cachedById(appId);
PlatedQr platedQr = qrFactory.createPlatedQr(name, group, app, user);
QR qr = platedQr.getQr();
Plate plate = platedQr.getPlate();
//同時(shí)保存QR和Plate
qrRepository.save(qr);
plateRepository.save(plate);
log.info("Created qr[{}] of group[{}] of app[{}].",
qr.getId(), groupId, appId);
return CreateQrResponse.builder()
.qrId(qr.getId())
.plateId(plate.getId())
.groupId(groupId)
.appId(appId)
.build();
}
源碼出處:com/mryqr/core/group/command/GroupCommandService.java
可以看到,在用例方法createQr()
中,我們先后調(diào)用qrRepository.save(qr)
和plateRepository.save(plate)
分別完成了對(duì)QR
和Plate
的持久化。
如果你希望了解事件驅(qū)動(dòng)架構(gòu)相關(guān)的知識(shí),請(qǐng)參考本系列的領(lǐng)域事件一文。
資源庫(kù)
在DDD中,資源庫(kù)(Repository)以聚合根為單位完成對(duì)數(shù)據(jù)庫(kù)的訪問。這里的重點(diǎn)是“以聚合根為單位”,也即只有聚合根才配得上擁有資源庫(kù)(畢竟在DDD中大家都是圍繞著聚合根轉(zhuǎn)的嘛),其他對(duì)象(比如非聚合根實(shí)體)是沒有對(duì)應(yīng)資源庫(kù)的,這也是資源庫(kù)和DAO最大的區(qū)別。在編碼實(shí)現(xiàn)時(shí),資源庫(kù)方法所接受的參數(shù)和返回的數(shù)據(jù)都應(yīng)該是聚合根對(duì)象,例如,在碼如云中,成員(Member)聚合根對(duì)應(yīng)的資源庫(kù)定義如下:
public interface MemberRepository {
Member byId(String id); //返回聚合根
Optional<Member> byIdOptional(String id); //返回聚合根
Member byIdAndCheckTenantShip(String id, User user); //返回聚合根
void save(Member member); //聚合根作為參數(shù)
void delete(Member member); //聚合根作為參數(shù)
}
源碼出處:com/mryqr/core/member/domain/MemberRepository.java
行業(yè)中這么一個(gè)現(xiàn)象,很多程序員在面對(duì)一個(gè)新的業(yè)務(wù)需求時(shí),首先想到的是如何設(shè)計(jì)數(shù)據(jù)庫(kù)的表結(jié)構(gòu),然后再編寫業(yè)務(wù)代碼。在DDD中,這是一種反模式,既然是“領(lǐng)域驅(qū)動(dòng)”,那么我們首先應(yīng)該關(guān)心的是如何業(yè)務(wù)建模,而不是數(shù)據(jù)庫(kù)建模。事實(shí)上,正如Robert C. Martin在《整潔架構(gòu)》一書中所說,數(shù)據(jù)庫(kù)只是一個(gè)實(shí)現(xiàn)細(xì)節(jié)而已,不應(yīng)該成為軟件建模的主體。
資源庫(kù)的作用,在于它在業(yè)務(wù)復(fù)雜度和技術(shù)復(fù)雜度之間做了一層很好的隔離,讓我們可以獨(dú)立地看待軟件的業(yè)務(wù)模型而不受技術(shù)設(shè)施的影響。從本質(zhì)上講,資源庫(kù)做的事情只是實(shí)現(xiàn)數(shù)據(jù)在內(nèi)存和磁盤之間相互傳輸而已。在編程實(shí)現(xiàn)業(yè)務(wù)邏輯的時(shí)候,我們只需關(guān)心內(nèi)存中的那個(gè)聚合根對(duì)象即可,當(dāng)聚合根對(duì)象的狀態(tài)由于業(yè)務(wù)操作發(fā)生了改變之后,再調(diào)用資源庫(kù)將新的聚合根狀態(tài)同步到磁盤中完成持久化,在調(diào)用時(shí)我們假設(shè)并相信資源庫(kù)一定可以完成其自身的使命。
@Transactional
public void addGroupManager(String groupId, String memberId, User user) {
Group group = groupRepository.byIdAndCheckTenantShip(groupId, user);
group.addManager(memberId, user);
groupRepository.save(group);
log.info("Added manager[{}] to group[{}].", memberId, groupId);
}
源碼出處:com/mryqr/core/group/command/GroupCommandService.java
在上例的“向分組中添加管理員”用例中,首先通過資源庫(kù)GroupRepository
的byIdAndCheckTenantShip()
方法得到聚合根Group
對(duì)象,然后再完成后續(xù)操作。這里的addGroupManager()
無(wú)需知道Group
是如何加載的,甚至不用知道后臺(tái)使用的是MySQL還是MongoDB或是其他,反正通過調(diào)用GroupRepository.byIdAndCheckTenantShip()
可以得到一個(gè)完整合法的Group
對(duì)象即可。
在資源庫(kù)中,最重要的方法有以下3個(gè):
public interface GroupRepository {
Group byId(String id);
void save(Group group);
void delete(Group group);
}
源碼出處:com/mryqr/core/member/domain/MemberRepository.java
其中,byId()
用于根據(jù)ID獲取指定聚合根,save()
用于保存聚合根,delete()
則用于刪除聚合根。除此之外,資源庫(kù)中還可以包含更多的查詢方法,比如在GroupRepository
中還包含以下方法:
//根據(jù)部門ID查找分組
List<Group> byDepartmentId(String departmentId);
//根據(jù)ID查找分組,返回Optional
Optional<Group> byIdOptional(String id);
//根據(jù)ID查找分組,同時(shí)檢查租戶
Group byIdAndCheckTenantShip(String id, User user);
源碼出處:com/mryqr/core/member/domain/MemberRepository.java
需要注意的是,這里的查詢方法指的是在實(shí)現(xiàn)業(yè)務(wù)邏輯的過程中需要做的查詢操作,并不是為了前端顯示那種純粹的查詢,因?yàn)榧兇獾牟樵儾僮鞑灰姷靡欢ㄒ诺劫Y源庫(kù)中,而是可以作為一個(gè)單獨(dú)的關(guān)注點(diǎn)通過CQRS解決。
在DDD項(xiàng)目中,通常將資源庫(kù)分為接口類和實(shí)現(xiàn)類,將接口類放置在領(lǐng)域模型domain
包中,而將實(shí)現(xiàn)類放置在基礎(chǔ)設(shè)施infrastructure
包中,這種做法有2點(diǎn)好處:文章來源:http://www.zghlxwxcb.cn/news/detail-709842.html
- 通過依賴反轉(zhuǎn),使得領(lǐng)域模型不依賴于基礎(chǔ)設(shè)施
- 實(shí)現(xiàn)資源庫(kù)的可插拔性,比如未來需要從MongoDB遷移到MySQL,那么只需創(chuàng)建新的實(shí)現(xiàn)類即可
總結(jié)
在本文中,我們講到了作為DDD核心的聚合根的設(shè)計(jì)原則及實(shí)現(xiàn),其中包含內(nèi)聚原則、對(duì)外黑盒原則和不變條件原則等。此外,我們也對(duì)與聚合根密切相關(guān)的資源庫(kù)做了講解。在下一篇實(shí)體與值對(duì)象中,我們將講到實(shí)體和值對(duì)象之間的區(qū)別,以及各自的典型編碼實(shí)踐。文章來源地址http://www.zghlxwxcb.cn/news/detail-709842.html
到了這里,關(guān)于產(chǎn)品代碼都給你看了,可別再說不會(huì)DDD(六):聚合根與資源庫(kù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!