領(lǐng)域建模是通過識別領(lǐng)域?qū)ο笈c行為來連接與現(xiàn)實世界業(yè)務(wù)主體與操作的映射關(guān)系。對象與行為的組織設(shè)計原則更體現(xiàn)面向?qū)ο笤O(shè)計的思想,通過聚合、解耦、抽象、組合等多種設(shè)計方式達(dá)到系統(tǒng)可復(fù)用,可維護,易擴展的能力。
在實際程序代碼設(shè)計中,由于語言、結(jié)構(gòu)、技術(shù)的不一樣對領(lǐng)域建模代碼落地也有所不同,且各有優(yōu)缺點。
一、貧血模型
此種模型下領(lǐng)域?qū)ο蟮淖饔煤芎唵危挥兴袑傩缘膅et/set方式,以及少量簡單的屬性值轉(zhuǎn)換,不包含任何業(yè)務(wù)邏輯,不關(guān)系對象持久化,只是用來做為數(shù)據(jù)對象的承載和傳遞的介質(zhì)。
@Entity
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Id
private String userId;
private String userName;
private String password;
private boolean isLock;
}
而真正的業(yè)務(wù)邏輯則由領(lǐng)域服務(wù)負(fù)責(zé)實現(xiàn),此服務(wù)引入持久化倉庫,在業(yè)務(wù)邏輯完成之后持久化到倉庫中,并在此可以發(fā)布領(lǐng)域事件(Domain Event)
public interface UserService {
void create(User user);
void edit(User user);
void changePassword(String userId, String newPassword);
void lock(String userId);
void unlock(String userId);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository repo;
@Override
public void edit(User user) {
User dbUser = repo.findById(user.getUserId()).get();
dbUser.setUserName(user.getUserName());
repo.save(dbUser);
// 發(fā)布領(lǐng)域事件 ...
}
@Override
public void lock(String userId) {
User dbUser = repo.findById(userId).get();
dbUser.setLock(true);
repo.save(dbUser);
// 發(fā)布領(lǐng)域事件 ...
}
// ... 省略完整代碼
}
優(yōu)點: 結(jié)構(gòu)簡單,職責(zé)單一,相互隔離性好,使用單例模型提高運行性能
缺點: 對象狀態(tài)與行為分離,不能直觀地描述領(lǐng)域?qū)ο?。行為的設(shè)計主要考慮參數(shù)的輸入和輸出而非行為本身,不太具有面向?qū)ο笤O(shè)計的思考方式。行為間關(guān)聯(lián)性較小,更像是面向過程式的方法,可復(fù)用性也較小。
SpringBoot 采用單例模式,盡量不手動創(chuàng)建對象,對象無狀態(tài)化,故較推薦使用貧血模型
二、 充血模型
此種模型下領(lǐng)域?qū)ο笞饔么祟I(lǐng)域相關(guān)行為,包含此領(lǐng)域相關(guān)的業(yè)務(wù)邏輯,同時也包含對領(lǐng)域?qū)ο蟮某志没僮鳌?/p>
@Entity
@Data
@Builder
@AllArgsConstructor
public class User implements UserService {
@Id
private String userId;
private String userName;
private String password;
private boolean isLock;
// 持久化倉庫
@Transient
private UserRepository repo;
// 是否是持久化對象
@Transient
private boolean isRepository;
@PostLoad
public void per() {
isRepository = true;
}
public User() {
}
public User(UserRepository repo) {
this.repo = repo;
}
@Override
public void create(User user) {
repo.save(user);
}
@Override
public void edit(User user) {
if (!isRepository) {
throw new RuntimeException("用戶不存在");
}
userName = user.userName;
repo.save(this);
// 發(fā)布領(lǐng)域事件 ...
}
@Override
public void lock() {
if (!isRepository) {
throw new RuntimeException("用戶不存在");
}
isLock = true;
repo.save(this);
// 發(fā)布領(lǐng)域事件 ...
}
}
優(yōu)點: 對象自洽程度很高,表達(dá)能力很強,因此非常適合于復(fù)雜的企業(yè)業(yè)務(wù)邏輯的實現(xiàn),以及可復(fù)用程度比較高,更符合面向?qū)ο笤O(shè)計思想
缺點: 對象屬性中摻雜持久化倉庫,不夠純粹,持久化操作是否屬于業(yè)務(wù)邏輯有待求證。但由于持久化僅需暴露接口,對業(yè)務(wù)邏輯與持久化操作的耦合度有一定降低。
說明: 有人認(rèn)為對象中的Create(),是新建對象方法不應(yīng)該屬于對象本身,應(yīng)由其它對象產(chǎn)生或static方法產(chǎn)生。我的理解是不能把業(yè)務(wù)對象中的新建和程序?qū)ο笊系男陆ɑ煜I(yè)務(wù)對象的新建是指的是業(yè)務(wù)行為操作得出的結(jié)果,理應(yīng)屬于對象本身行為。而程序里的新建則是對象初始化過程New(),這是程序構(gòu)建邏輯不是業(yè)務(wù)概念,不能相等對待。
在領(lǐng)域?qū)ο笮袨檫壿嬢^復(fù)雜的情況下,需要多個行為共享對象狀態(tài)的時候,充血模型表現(xiàn)力更強,個人比較推薦此種模型
三、充血模型2
為了解決業(yè)務(wù)邏輯不純粹問題,也有將持久化操作移出業(yè)務(wù)邏輯的作法。
@Entity
@Data
@Builder
@AllArgsConstructor
public class User implements UserService {
@Id
private String userId;
private String userName;
private String password;
private boolean isLock;
// 是否是持久化對象
@Transient
private boolean isRepository;
@Override
public void create(User user) {
user.userId = UUID.randomUUID().toString();
}
@Override
public void edit(User user) {
userName = user.userName;
}
@Override
public void lock() {
isLock = true;
}
}
@Service
public class UserManager {
@Autowired
private UserRepository repo;
public User findOne(String userId){
return repo.findById(userId).get();
}
public void edit(User u) {
User user = findOne(u.getUserId());
user.edit(u);
repo.save(user);
// 發(fā)布領(lǐng)域事件 ...
}
public void lock(String userId) {
User user = findOne(userId);
user.lock();
repo.save(user);
// 發(fā)布領(lǐng)域事件 ...
}
}
優(yōu)點: 保持了業(yè)務(wù)邏輯的純粹性,去掉了持久化的入侵
缺點: 降低了領(lǐng)域服務(wù)的自治性,破壞了行為邏輯的完整性,部分邏輯混入了application層,尤其是領(lǐng)域事件的發(fā)布
此種方式是前兩種方式的折中,充分地做到了解耦,但也犧牲了部分內(nèi)聚
四、總結(jié)
架構(gòu)設(shè)計是一項持續(xù)性演進性的工作,不是一成不變的。架構(gòu)的選擇并沒有好壞只有適合,每一種都有自己的使用場景。如何選擇需要自身理論支持,保持相對方向性統(tǒng)一,并持續(xù)審視是否符合預(yù)期目標(biāo)。