簡體   English   中英

ORM 實體與 DDD 實體

[英]ORM Entities vs DDD Entities

我熟悉由服務、實體和存儲庫組成的典型分層架構。 服務操作由存儲庫持久化的注釋實體類。 在這個 model 中,實體類只是帶有一堆 getter 和 setter 的貧血數據容器。 業務邏輯駐留在程序服務類中(由 spring 容器管理的單例)。

我正在學習 DDD 作為一個愛好項目。 在 DDD 實體 forms 中,一個豐富的域 model 在聚合根方法(和值對象)中容納了大部分業務邏輯。 服務幾乎只是協作實體、存儲庫和其他服務的協調者。 富域 model 強制執行業務約束並使真正的 OOP 方式不變並提高代碼可維護性。 此外,域 model 是六邊形體系結構的核心,這意味着它只是 POJO,不依賴於源代碼級別的技術或框架問題。

但是 JPA 規范要求實體 bean 應該有公共的 getter 和 setter,本質上是一個貧血的數據容器,與 DDD 域 model 相對。那么我應該在 JPA 實體中捆綁域邏輯嗎? 或者在 DDD 上使用 ORM 時,我應該維護兩個不同的模型和映射邏輯嗎? 這些 model 和映射邏輯應該放在項目級別的什么地方?

為了維護 DDD,尤其是將域 model 與數據庫層分離(因為兩者都可能單獨更改),您需要兩個不同的模型。

然后,您需要某種存儲庫服務,它知道(即依賴於)您的域 model,並且可以進行某種雙向映射。 在實踐中,即使這違反了純 DDD 知識,您可能在您的域 model 中需要某種幫助(即轉儲已知的內部結構並從這樣的轉儲中恢復 state。)但它真的很難存儲並在持久存儲中恢復一個真正的黑盒子,除非你想要 go 進行簡單的 object 序列化。


關於評論中的問題:

這正是問題所在:您要么將基礎架構混合到域中,要么公開內部數據。 不知何故,每一個寫 DDD 的作者都只是通過不談論這個問題來躲過這顆子彈。 這兩種變體都同樣丑陋,但由於您似乎嘗試了一種相當純粹的 DDD 方法,因此我將創建一個耦合到域 object 的 DTO object,基礎結構層可以訪問它(例如,通過使用 package 保護訪問)。 但是,我不會授予訪問真實內部值的權限。 通過這種方式,您可以將“損壞”限制在一個點上,並且可以隨意更改實施細節。

將一些偽代碼放入答案中:

public class Invoice {   // Domain class
    // implementation details. @Rest of the world: none of your business!
    private Person creditor;        // other domain objects
    private MonetaryAmount amount;  // and value objects

    // Corruption starts here
    toInvoiceDto() {
        InvoiceDto res = new InvoiceDto();
        res.setCreditorId(creditor.getId()); // mapping into external representation
        ...
        return res;
    }

    static Invoice fromInvoiceDto(InvoiceDto persistentSource) {
        ...
    }
    // Corruption ends here

    // do real business :^)
}

public class InvoiceDto {
    ...
}

public class Repository {
    public void saveInvoice(Invoice businessObject) {
       // *very* roughly
       InvoiceDto dto = businessObject.toInvoiceDto();
       InvoiceEntity entity = someKindOfMapper.toEntity(dto);
       entityManager.save(entity);
    }
}

關於您的報價:

但是 JPA 規范要求實體 bean 應該有公共的 getter 和 setter,本質上是一個貧血的數據容器,與 DDD 域 model 相對。

我不是這個話題的專家,但據我所知,JPA 沒有強制要求公共 getter 和 setter,盡管文檔有時可能會含糊不清。

例如, Hibernate 5.6文檔說:

2.5.4. 為持久屬性聲明 getter 和 setter

JPA 規范要求這樣做,否則model 將阻止直接從實體本身外部訪問實體持久性 state 字段。

對於“上面引用的要求”,他們可能是這樣的:

實體的持久化 state 由實例變量表示,實例變量可能對應 JavaBean 風格的屬性。 實體實例本身只能從實體的方法中直接訪問實例變量。 實體的state只能通過實體的訪問器方法(getter/setter方法)或其他業務方法提供給客戶端

這些文本不是很清楚是否總是需要 setter 和 setter,即使使用了“ requires ”這個詞。 我認為它基本上說(或應該說)的是,如果您的客戶想要讀取和寫入屬性,那么您應該分別使用公共 getter 和 setter。 (對於“您的客戶類”,我的意思是您編寫的 class,而不是作為實體客戶的 Hibernate 本身。)但是如果您的客戶不需要讀取或寫入它,那么您就不需要根本不需要訪問器方法,只要您使用 JPA 字段訪問屬性注釋(相對於 JPA 方法/屬性訪問屬性注釋)。 除了 setter,您還可以對非空屬性使用構造函數參數。 (我的 Hibernate 代碼中有很多這樣的案例。)盡管 JPA/Hibernate 文檔最初可能是在編寫時考慮到更貧血的實體類,但您不需要擁有這樣的貧血訪問器。

關於您的報價:

那么我應該在 JPA 實體中捆綁域邏輯嗎? 或者在 DDD 上使用 ORM 時,我應該維護兩個不同的模型和映射邏輯嗎? 這些 model 和映射邏輯應該放在項目級別的什么地方?

制作兩個不同的模型會產生大量樣板代碼。

JPA 規范說:

除了返回和設置實例的持久化 state 之外,屬性訪問器方法還可能包含其他業務邏輯,例如執行驗證 當使用基於屬性的訪問時,持久性提供程序運行時執行此邏輯。

所以在實體類中有業務邏輯,應該不是問題。 在理想情況下,實體類是純 DDD 域類,其中 ORM 應用於它,僅限於元數據(例如注釋),它自己的代碼是 ORM 不可知的。

但是,大量 ORM 元數據與大量業務邏輯混合在一起,會損害可讀性。 對此的解決方案可能是 Kotlin(一種基於 Java 的語言)中的擴展方法。 Kotlin 擴展方法基本上是 Java 中的 static 方法,可以用作 Kotlin 中的實例方法,它在邏輯上添加到的 class 外部。

例如,您可以擁有以下實體 class: com.stackoverflow.domain.Question

無論出於何種原因,您想向其中添加一個persist()方法,但由於關注點分離,您不想將其包含在Question class 中,而您可以使用擴展方法完成此操作:

在文件com.stackoverflow.persistence.QuestionExt.kt

package com.stackoverflow.persistence
import com.stackoverflow.domain.Question

fun Question.persist() {
    // read "id" property of question object
    val id = this.id
    // persist the question
}

然后客戶端代碼可以這樣做:

package com.stackoverflow.persistence.repositories

class QuestionRepository {
    fun save(question: Question) {
        question.persist()
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM