DDD Repository Pattern + Bridge Design Pattern

Z-xuan Hong
10 min readNov 29, 2020

--

本文章要向各位分享如何結合DDD Repository Pattern與Bridge Design Pattern,看完此篇文章能夠達到以下目標:

  1. 了解Bridge Design Pattern要解決的問題
  2. 了解Repository Pattern要解決的問題
  3. 了解如何細緻地調整兩種Pattern結合的方式,達到不同的彈性

What is Bridge Design Pattern?

Decouple an abstraction from its implementation so that the two can vary independently

上述是Bridge Pattern在Gang of Four中的定義,針對抽象與實作進行解耦合,讓抽象與實作能夠各自獨立的變化。

為什麼要讓抽象與實作解耦合呢?一般物件導向會先定義介面(interface)讓不同類別(class)去實作此介面,這方式能解決大部分的問題,但當我們要更多的彈性時,此方式就不夠了。

上述圖中我們可以看到,當Window(抽象化)要擴充時,我們同時需要增加相對應X, PM子類別(實作),導致類別數量過多,因為X, PM是透過繼承的方式綁定到Window。如果能讓Window與X, PM以物件組合的方式解耦合,在擴充Window時,便不需新增X, PM

上述圖中定義抽象化介面(Window)與實作的介面(X, PM),與之前透過繼承綁定實作到抽象化的做法不同,Bridge使用物件組合(Object Composition)的方式,因此擴充Window便不需要擴充新的類別(X, PM)。

從上述介紹我們可以得出一個結論:繼承只支援One Dimension的變化,當使用繼承支援Two Dimension變化時,會導致繼承架構過度肥大類別數量過多,Bridge透過物件組合的方式削去肥大的繼承架構,讓抽象化繼承架構支援One Dimension,實作繼承架構支援One Dimension。

One Dimension: X, PM子類別對於Window的實作。

Two Dimension: X, PM子類別對於Window的實作、Window抽象化的擴充。

What is Repository Pattern?

For each type of object that needs global access, create an object that can provide the illusion of an in-memory collection of all objects of that type. Provide REPOSITORIES only for AGGREGATE roots that actually need direct access. Keep the client focused on the model, delegating all object storage and access to the REPOSITORIES.

上述是Repository Pattern在DDD中的部分定義,提供客戶端存取Aggregate的入口、封裝物件Persistence的機制。

在DDD中,Aggregate透過封裝Entity、Value Object來保護Business Invariant,因此客戶端不能存取Aggregate內部的Entity與Value Object,否則會破壞Business Invariant。

傳統後端架構會有Data Access Object(DAO),來封裝Persistence的機制,只使用DAO,客戶端能夠存取Aggregate、Aggregate內部的Entity、Aggregate內部的Value Object,而Repository與DAO最大的差異在於: Repository針對Aggregate定義的邊界(Transaction Boundary裡面的Entity與Value Object)進行存取,一個Aggregate對應一個Repository,DAO則沒有此限制,因此Repository相較於DAO有更強的封裝性,更能降低系統狀態出錯的機率。

Repository和DAO相同的是,都會封裝對於Persistence的存取,讓客戶端專心於Model要解決的問題。

How to Combine Repository Pattern with Bridge Pattern?

以下為Repository的實作(without bridge)

@Repository
public class JPAStudentRepository implements StudentRepository {
@PersistenceContext
private EntityManager entityManager;

@Override
public Student findBy(long id) {
return entityManager.find(Student.class, id);
}

@Override
public void add(Student student) {
entityManager.persist(student);
}
}

JPAStudentRepository透過JPA的Entity Manager實作StudentRepository介面。

當只有一個Aggregate時,此設計非常合理,但當Aggregate數量很多時,同樣的實作會一直出現,如果除了JPA我們還有Hibernate實作,會導致同樣的子類別在不同Aggregate中重複出現(JPAStudentRepository、HibernateStudentRepository、JPACourseRepository、HibernateCourseRepository),這正是Bridge Pattern想要解決的問題。

以下為套用Bridge Pattern後Repository的實作

@Repository
public class StudentRepositoryImp implements StudentRepository {
private final JPAGenericRepository<Student> jpaGenericRepository; public StudentRepositoryImp(JPAGenericRepository<Student> jpaGenericRepository) {
this.jpaGenericRepository = jpaGenericRepository;
}

@Override
public Student findBy(long id) {
return jpaGenericRepository.findBy(id);
}

@Override
public void add(Student student) {
jpaGenericRepository.add(student);
}
}
@Repository
public abstract class JPAGenericRepository<T> {
@PersistenceContext
EntityManager entityManager;

private Class<T> entityClass;

public JPAGenericRepository(Class<T> entityClass) {
this.entityClass = entityClass;
}

public T findBy(Long id) {
return entityManager.find(entityClass, id);
}

public void add(T t) {
entityManager.persist(t);
}
}

上述圖中StudentRepositoryImp透過JPAGenericRepository實作StudentRepository,我們把JPA重複的實作封裝在JPAGenericRepository,其他Repository也能重複利用此類別,達到減少程式碼重複的目的,也能降低類別的數量。

你可能會想,為什麼是注入JPAGenericRepository,而不是實作抽象化GenericRepositoryImp呢?因為目前只需要JPA,所以我們使用Degenerate Bridge Pattern(僅使用Object Composition、抽象與實作分離的概念,實作沒有完全抽象化),這樣能夠降低系統間接層過多的問題(To many indirection),間接層應該在真的需要時再加入,當需要不同的實作像是Hibernate、Cloud時,我們在套用完整的Bridge Pattern即可。

如果專案只需要JPA實作,也可以透過Class Adapter以繼承的方式轉接JPAGenericRepository來實作StudentRepository介面,也能達到降低重複程式碼的目的。

以下為套用Class Adapter後Repository的實作

@Repository
public class StudentRepositoryImp extends JPAGenericRepository<Student> implements StudentRepository {

public StudentRepositoryImp() {
super(Student.class);
}

@Override
public Student findBy(long id) {
return super.findBy(id);
}

@Override
public void add(Student student) {
super.add(student);
}
}
@Repository
public abstract class JPAGenericRepository<T> {
@PersistenceContext
EntityManager entityManager;

private Class<T> entityClass;

public JPAGenericRepository(Class<T> entityClass) {
this.entityClass = entityClass;
}

public T findBy(Long id) {
return entityManager.find(entityClass, id);
}

public void add(T t) {
entityManager.persist(t);
}
}

結論:

  • Bridge Pattern透過物件組合的方式,針對抽象化與實作解耦合
  • Repository Pattern封裝對於Aggregate的存取
  • Bridge Pattern + Repository Pattern能夠大幅度降低實作類別、重複程式碼的數量

--

--

Z-xuan Hong
Z-xuan Hong

Written by Z-xuan Hong

熱愛軟體開發、物件導向、自動化測試、框架設計,擅長透過軟工幫助企業達到成本最小化、價值最大化。單元測試企業內訓、導入請私訊信箱:t107598042@ntut.org.tw。

No responses yet