国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

產(chǎn)品代碼都給你看了,可別再說不會(huì)DDD(六):聚合根與資源庫(kù)

這篇具有很好參考價(jià)值的文章主要介紹了產(chǎn)品代碼都給你看了,可別再說不會(huì)DDD(六):聚合根與資源庫(kù)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

這是一個(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í)的諸多取舍。

本系列包含以下文章:

  1. DDD入門
  2. DDD概念大白話
  3. 戰(zhàn)略設(shè)計(jì)
  4. 代碼工程結(jié)構(gòu)
  5. 請(qǐng)求處理流程
  6. 聚合根與資源庫(kù)(本文)
  7. 實(shí)體與值對(duì)象
  8. 應(yīng)用服務(wù)與領(lǐng)域服務(wù)
  9. 領(lǐng)域事件
  10. 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)原因:

  1. 聚合根遵循了軟件中“高內(nèi)聚,低耦合”的基本原則
  2. 聚合根體現(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)建信息(createdAtcreatedBy)和更新信息(updatedAtupdatedBy)等數(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)用法先拿到Membermembers引用,然后由外部自行向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),而在managersmembers字段中,則是以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),從上面的例子中可以看到,AggregateRootGroup對(duì)Spring框架中的@Version、@Document@TypeAlias3個(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í)可分兩種情況:

  1. 如果聚合根位于不同的進(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)的聚合根更新。
  2. 如果聚合根位于同一個(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ì)QRPlate的持久化。

如果你希望了解事件驅(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ù)GroupRepositorybyIdAndCheckTenantShip()方法得到聚合根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)好處:

  1. 通過依賴反轉(zhuǎn),使得領(lǐng)域模型不依賴于基礎(chǔ)設(shè)施
  2. 實(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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 30個(gè)ElasticSearch 調(diào)優(yōu)知識(shí)點(diǎn),都給你整理好了

    30個(gè)ElasticSearch 調(diào)優(yōu)知識(shí)點(diǎn),都給你整理好了

    ES官方調(diào)優(yōu)指南 第一部分:調(diào)優(yōu)索引速度 第二部分:調(diào)優(yōu)搜索速度 第三部分:通用的一些建議 ES發(fā)布時(shí)帶有的默認(rèn)值,可為es的開箱即用帶來很好的體驗(yàn)。全文搜索、高亮、聚合、索引文檔 等功能無(wú)需用戶修改即可使用,當(dāng)你更清楚的知道你想如何使用es后,你可以作很多的

    2023年04月08日
    瀏覽(25)
  • Selenium+Unittest自動(dòng)化測(cè)試框架實(shí)戰(zhàn)(框架源碼都給你)

    Selenium+Unittest自動(dòng)化測(cè)試框架實(shí)戰(zhàn)(框架源碼都給你)

    目錄 前言 項(xiàng)目框架 首先管理時(shí)間 !/usr/bin/env python3 -- coding:utf-8 -- 配置文件 conf.py config.ini# 讀取配置文件 記錄操作日志 簡(jiǎn)單理解POM模型 管理頁(yè)面元素 封裝Selenium基類 創(chuàng)建頁(yè)面對(duì)象 熟悉unittest測(cè)試框架 編寫測(cè)試用例 執(zhí)行用例 生成測(cè)試報(bào)告 執(zhí)行并生成報(bào)告 發(fā)送結(jié)果郵件 se

    2024年02月15日
    瀏覽(25)
  • 記錄--求你了,別再說不會(huì)JSONP了

    記錄--求你了,別再說不會(huì)JSONP了

    JSONP是一種很遠(yuǎn)古用來解決跨域問題的技術(shù),當(dāng)然現(xiàn)在實(shí)際工作當(dāng)中很少用到該技術(shù)了,但是很多同學(xué)在找工作面試過程中還是經(jīng)常被問到,本文將帶您深入了解JSONP的工作原理、使用場(chǎng)景及安全注意事項(xiàng),讓您輕松掌握J(rèn)SONP。 JSONP,全稱JSON with Padding,是一項(xiàng)用于在不同域之

    2024年02月05日
    瀏覽(20)
  • LAL v0.36.7發(fā)布,Customize Sub,我有的都給你

    Go語(yǔ)言流媒體開源項(xiàng)目 LAL 今天發(fā)布了v0.36.7版本。 LAL 項(xiàng)目地址:https://github.com/q191201771/lal 老規(guī)矩,簡(jiǎn)單介紹一下: ? Customize Sub,我有的都給你 這是提供給用lalserver做二次開發(fā)的小伙伴們的一個(gè)重要的功能,業(yè)務(wù)方可以通過設(shè)置回調(diào)函數(shù)的方式獲取lalserver內(nèi)部的流。 獲取到

    2024年02月16日
    瀏覽(48)
  • 當(dāng)面試問你接口測(cè)試時(shí),不要再說不會(huì)了

    當(dāng)面試問你接口測(cè)試時(shí),不要再說不會(huì)了

    很多人會(huì)談?wù)摻涌跍y(cè)試。到底什么是接口測(cè)試?如何進(jìn)行接口測(cè)試?這篇文章會(huì)幫到你。 在談?wù)摻涌跍y(cè)試之前,讓我們先明確前端和后端這兩個(gè)概念。 前端是我們?cè)诰W(wǎng)頁(yè)或移動(dòng)應(yīng)用程序中看到的頁(yè)面,它由 HTML 和 CSS 編寫而成,讓我們看到漂亮的頁(yè)面,并進(jìn)行一些簡(jiǎn)單的校驗(yàn)

    2024年02月08日
    瀏覽(20)
  • 不要再說你不會(huì)了——網(wǎng)絡(luò)性能問題排查思路

    不要再說你不會(huì)了——網(wǎng)絡(luò)性能問題排查思路

    服務(wù)監(jiān)控系列文章 服務(wù)監(jiān)控系列視頻 網(wǎng)絡(luò)問題往往是性能排查中最復(fù)雜的一個(gè)問題,因?yàn)榫W(wǎng)絡(luò)問題往往涉及的鏈路比較長(zhǎng),排查起來不僅僅是看本地機(jī)器的指標(biāo)就可以了。本文將展示一個(gè)比較系統(tǒng)的排查網(wǎng)絡(luò)問題的思路。 我們往往都是通過類似prometheus,grafana搭建的監(jiān)控平

    2023年04月13日
    瀏覽(21)
  • 軟件測(cè)試項(xiàng)目去哪里找?我都給你整理好了【源碼+操作視頻】

    軟件測(cè)試項(xiàng)目去哪里找?我都給你整理好了【源碼+操作視頻】

    目錄 一、引言 二、測(cè)試任務(wù) 三、測(cè)試進(jìn)度 四、測(cè)試資源 五、測(cè)試策略 六、測(cè)試完成標(biāo)準(zhǔn) 七、風(fēng)險(xiǎn)和約束 八、問題嚴(yán)重程度描述和響應(yīng)時(shí)間規(guī)范 九、測(cè)試的主要角色和職責(zé) ?有需要實(shí)戰(zhàn)項(xiàng)目的評(píng)論區(qū)留言吧! 軟件測(cè)試是使用人工或者自動(dòng)的手段來運(yùn)行或者測(cè)定某個(gè)軟件

    2024年02月07日
    瀏覽(16)
  • jmeter接口測(cè)試項(xiàng)目實(shí)戰(zhàn)詳解,零基礎(chǔ)也能學(xué),源碼框架都給你

    jmeter接口測(cè)試項(xiàng)目實(shí)戰(zhàn)詳解,零基礎(chǔ)也能學(xué),源碼框架都給你

    目錄 1.什么是jmeter? 2.jmeter能做什么? 3.jmeter環(huán)境搭建 3.1前提: 3.2jmeter下載: 3.3jmeter環(huán)境搭建: 3.3.1mac當(dāng)中jmeter環(huán)境搭建: 3.4jmeter基本配置 3.4.1.切換語(yǔ)言 ?3.4.2.安裝插件 4.jmeter組件 4.1測(cè)試計(jì)劃 4.2線程組 4.2.1取樣器錯(cuò)誤后要執(zhí)?的動(dòng)作 4.2.2線程屬性 4.3jmeter監(jiān)聽器 4.3.1聚合

    2024年02月08日
    瀏覽(23)
  • Selenium中操作iframe,別再說你不會(huì)了

    Selenium中操作iframe,別再說你不會(huì)了

    ??專注于分享軟件測(cè)試干貨內(nèi)容,歡迎點(diǎn)贊 ?? 收藏 ?留言 ?? 如有錯(cuò)誤敬請(qǐng)指正! ??交流討論:歡迎加入我們一起學(xué)習(xí)! ??資源分享:耗時(shí)200+小時(shí)精選的「軟件測(cè)試」資料包 ?? 軟件測(cè)試學(xué)習(xí)教程推薦:火遍全網(wǎng)的《軟件測(cè)試》教程 這里是清安,本章一起來了解一下

    2024年02月03日
    瀏覽(19)
  • 大佬都是怎么畫交換機(jī)拓?fù)鋱D的?都給你整理好了

    大佬都是怎么畫交換機(jī)拓?fù)鋱D的?都給你整理好了

    老楊的網(wǎng)工交流群里經(jīng)常會(huì)有這種現(xiàn)象: 一群小伙伴在問各類型拓?fù)鋱D的問題,怎么設(shè)計(jì),怎么配置,或者讓群里的大佬幫忙看看,這圖有沒有啥問題的…… 太多了。 網(wǎng)絡(luò)拓?fù)洌∟etwork Topology)是啥?你可以把他理解成是用傳輸媒體互連各種設(shè)備的物理布局。 為了更好的連

    2024年02月09日
    瀏覽(19)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包