DDD項目架構與充血模型實例

歡迎你們關注公衆號「JAVA前線」查看更多精彩分享文章,主要包括源碼分析、實際應用、架構思惟、職場分享、產品思考等等,同時歡迎你們加我微信「java_front」一塊兒交流學習java


1 DDD

最近在學習領域驅動設計,同時也學習了COLA代碼並進行了一些項目實踐,COLA代碼整潔優雅,但有必定學習成本和使用成本。最終一個工程思想仍是要落地,我綜合了一些DDD技術框架,刪除了CQRS和事件總線模式,整理了一個簡單實用易於落地的項目架構。數據庫

(1) demo-infrastructure

基礎層。包含基礎性功能,例如數據庫訪問功能,緩存訪問功能,消息發送功能,還須要提供通用工具包設計模式

(2) demo-dependency

外部訪問層。在這個模塊中調用外部RPC服務,解析返回碼和返回數據api

(3) demo-domain

領域層。這個模塊包含相似於三層架構的BO(Business Object),但不一樣的是使用充血模式進行定義,因此領域層自己也包含業務邏輯,不是簡單進行屬性聲明緩存

(4) demo-service

業務層。雖然領域層和業務層都包含業務,可是用途不一樣。業務層能夠組合不一樣領域的業務,而且能夠增長流控、監控、日誌、權限控制切面,相較於領域層更爲豐富微信

(5) demo-api

對外接口層。提供面向外部接口聲明markdown

(6) demo-controller

對外訪問層。提供面向外部訪問入口架構


2 三層架構與貧血模型

在使用上述框架以前咱們通常使用三層架構進行業務開發:框架

Repository + Entity
Service + BO(Business Object)
Controller + VO(View Object)
複製代碼

在三層架構業務開發中,你們常常使用基於貧血模型的開發模式。貧血模型是指業務邏輯所有放在service層,業務對象只包含數據不包含業務邏輯。咱們來分析代碼實例。dom

/** * 帳戶業務對象 * * @author 微信公衆號「JAVA前線」 * */
public class AccountBO {

    /** * 帳戶ID */
    private String accountId;

    /** * 帳戶餘額 */
    private Long balance;

    /** * 是否凍結 */
    private boolean isFrozen;

    public String getAccountId() {
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }

    public Long getBalance() {
        return balance;
    }

    public void setBalance(Long balance) {
        this.balance = balance;
    }

    public boolean isFrozen() {
        return isFrozen;
    }

    public void setFrozen(boolean isFrozen) {
        this.isFrozen = isFrozen;
    }
}

/** * 轉帳業務服務實現 * * @author 微信公衆號「JAVA前線」 * */
@Service
public class TransferServiceImpl implements TransferService {

    @Autowired
    private AccountService accountService;

    @Override
    public boolean transfer(String fromAccountId, String toAccountId, Long amount) {
        AccountBO fromAccount = accountService.getAccountById(fromAccountId);
        AccountBO toAccount = accountService.getAccountById(toAccountId);

        /** 檢查轉出帳戶 **/
        if (fromAccount.isFrozen()) {
            throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN);
        }
        if (fromAccount.getBalance() < amount) {
            throw new MyBizException(ErrorCodeBiz.INSUFFICIENT_BALANCE);
        }
        fromAccount.setBalance(fromAccount.getBalance() - amount);

        /** 檢查轉入帳戶 **/
        if (toAccount.isFrozen()) {
            throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN);
        }
        toAccount.setBalance(toAccount.getBalance() + amount);

        /** 更新數據庫 **/
        accountService.updateAccount(fromAccount);
        accountService.updateAccount(toAccount);
        return Boolean.TRUE;
    }
}
複製代碼

TransferServiceImpl實現類中就是貧血模型開發方式,AccountBO只有數據沒有業務邏輯。整個代碼風格偏向於面向過程,因此也有人把貧血模型稱爲反模式。


3 充血模型

3.1 實例一

在基於充血模型DDD開發模式中咱們引入了Domain層。Domain層包含了業務對象BO,但並非僅僅包含數據,這一層也包含業務邏輯,咱們來分析代碼實例。

/** * 帳戶業務對象 * * @author 微信公衆號「JAVA前線」 * */
public class AccountBO {

    /** * 帳戶ID */
    private String accountId;

    /** * 帳戶餘額 */
    private Long balance;

    /** * 是否凍結 */
    private boolean isFrozen;

    /** * 出借策略 */
    private DebitPolicy debitPolicy;

    /** * 入帳策略 */
    private CreditPolicy creditPolicy;

    /** * 出借方法 * * @param amount 金額 */
    public void debit(Long amount) {
        debitPolicy.preDebit(this, amount);
        this.balance -= amount;
        debitPolicy.afterDebit(this, amount);
    }

    /** * 轉入方法 * * @param amount 金額 */
    public void credit(Long amount) {
        creditPolicy.preCredit(this, amount);
        this.balance += amount;
        creditPolicy.afterCredit(this, amount);
    }

    public boolean isFrozen() {
        return isFrozen;
    }

    public void setFrozen(boolean isFrozen) {
        this.isFrozen = isFrozen;
    }

    public String getAccountId() {
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }

    public Long getBalance() {
        return balance;
    }

    /** * BO和DO轉換必須加set方法這是一種權衡 */
    public void setBalance(Long balance) {
        this.balance = balance;
    }

    public DebitPolicy getDebitPolicy() {
        return debitPolicy;
    }

    public void setDebitPolicy(DebitPolicy debitPolicy) {
        this.debitPolicy = debitPolicy;
    }

    public CreditPolicy getCreditPolicy() {
        return creditPolicy;
    }

    public void setCreditPolicy(CreditPolicy creditPolicy) {
        this.creditPolicy = creditPolicy;
    }
}


/** * 入帳策略實現 * * @author 微信公衆號「JAVA前線」 * */
@Service
public class CreditPolicyImpl implements CreditPolicy {

    @Override
    public void preCredit(AccountBO account, Long amount) {
        if (account.isFrozen()) {
            throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN);
        }
    }

    @Override
    public void afterCredit(AccountBO account, Long amount) {
        System.out.println("afterCredit");
    }
}

/** * 出借策略實現 * * @author 微信公衆號「JAVA前線」 * */
@Service
public class DebitPolicyImpl implements DebitPolicy {

    @Override
    public void preDebit(AccountBO account, Long amount) {
        if (account.isFrozen()) {
            throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN);
        }
        if (account.getBalance() < amount) {
            throw new MyBizException(ErrorCodeBiz.INSUFFICIENT_BALANCE);
        }
    }

    @Override
    public void afterDebit(AccountBO account, Long amount) {
        System.out.println("afterDebit");
    }
}

/** * 轉帳業務服務實現 * * @author 微信公衆號「JAVA前線」 * */
@Service
public class TransferServiceImpl implements TransferService {

    @Resource
    private AccountService accountService;
    @Resource
    private CreditPolicy creditPolicy;
    @Resource
    private DebitPolicy debitPolicy;

    @Override
    public boolean transfer(String fromAccountId, String toAccountId, Long amount) {
        AccountBO fromAccount = accountService.getAccountById(fromAccountId);
        AccountBO toAccount = accountService.getAccountById(toAccountId);
        fromAccount.setDebitPolicy(debitPolicy);
        toAccount.setCreditPolicy(creditPolicy);

        fromAccount.debit(amount);
        toAccount.credit(amount);
        accountService.updateAccount(fromAccount);
        accountService.updateAccount(toAccount);
        return Boolean.TRUE;
    }
}
複製代碼

AccountBO包含了策略對象,策略對象能夠實現業務邏輯,這樣把業務邏輯實如今策略對象,減小service層面向過程的代碼,代碼結構更加內聚。

3.2 實例二

咱們再分析一個將校驗邏輯內聚充血模型實例。

/** * 校驗器 * * @author 微信公衆號「JAVA前線」 * */
public interface BizValidator {
    BizValidateResult validate();
}

/** * 校驗結果 * * @author 微信公衆號「JAVA前線」 * */
public class BizValidateResult {
    private boolean isSuccess;
    private String message;
    public BizValidateResult(boolean isSuccess, String message) {
        super();
        this.isSuccess = isSuccess;
        this.message = message;
    }
    public boolean isSuccess() {
        return isSuccess;
    }
    public void setSuccess(boolean isSuccess) {
        this.isSuccess = isSuccess;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}

/** * 商品業務對象 * * @author 微信公衆號「JAVA前線」 * */
public class GoodsBO implements BizValidator {
    private String goodsId;
    private String goodsName;
    private GoodsService goodsService;
    public String getGoodsId() {
        return goodsId;
    }
    public void setGoodsId(String goodsId) {
        this.goodsId = goodsId;
    }
    public String getGoodsName() {
        return goodsName;
    }
    public void setGoodsName(String goodsName) {
        this.goodsName = goodsName;
    }
    public GoodsService getGoodsService() {
        return goodsService;
    }
    public void setGoodsService(GoodsService goodsService) {
        this.goodsService = goodsService;
    }

    @Override
    public BizValidateResult validate() {
        if(StringUtils.isEmpty(goodsId)) {
            throw new MyBizException(ErrorCodeBiz.ILLEGAL_ARGUMENT);
        }
        if(StringUtils.isEmpty(goodsName)) {
            throw new MyBizException(ErrorCodeBiz.ILLEGAL_ARGUMENT);
        }
        Integer stock = goodsService.getGoodsStock(goodsId);
        if(stock <= 0) {
            throw new MyBizException(ErrorCodeBiz.NO_STOCK);
        }
        return new BizValidateResult(Boolean.TRUE, null);
    }
}

/** * 訂單服務實現 * * @author 微信公衆號「JAVA前線」 * */
@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private GoodsService goodsService;

    @Override
    public String createOrder(GoodsBO goods) {
        if(null == goods) {
            throw new MyBizException(ErrorCodeBiz.ILLEGAL_ARGUMENT);
        }
        goods.setGoodsService(goodsService);
        goods.validate();
        System.out.println("建立訂單");
        return "orderId_111";
    }
}
複製代碼

4 一些思考

有人可能會說充血模式只是把一些業務放在Domain層進行,沒有什麼特別之處。關於這個觀點我有如下思考:

(1) 代碼業務風格更加面向對象,而不是面向過程,整個邏輯也變得更加內聚

(2) 在設計原則中有一條開閉原則:面向擴展開放,面向修改關閉,我認爲這是最重要的一條設計原則,不少設計模式如策略模式、模板方法模式都是基於這個原則設計的。充血模型BO中能夠包含各類策略,可使用策略模式管理策略

(3) Domain層不是取代Service層,而是一種補充加強,雖然領域層和業務層都包含業務可是用途不一樣。業務層能夠組合不一樣領域的業務,而且能夠增長流控、監控、日誌、權限控制切面,相較於領域層更爲豐富

(4) Lombok框架如今很是流行,使代碼變得很是簡潔。有一點須要注意,隨意使用Lombok可能會破壞代碼封裝性,例如AccountBO對象不該該暴露setBalance方法。但因爲各層對象須要屬性拷貝必須暴露setBalance方法,這也是一種權衡策略

歡迎你們關注公衆號「JAVA前線」查看更多精彩分享文章,主要包括源碼分析、實際應用、架構思惟、職場分享、產品思考等等,同時歡迎你們加我微信「java_front」一塊兒交流學習

相關文章
相關標籤/搜索