領(lǐng)域建模的貧血模型與充血模型

領(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)。

五、 源碼

https://gitee.com/hypier/barry-domain

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容