歡迎你們關注公衆號「JAVA前線」查看更多精彩分享文章,主要包括源碼分析、實際應用、架構思惟、職場分享、產品思考等等,同時歡迎你們加我微信「java_front」一塊兒交流學習java
最近在學習領域驅動設計,同時也學習了COLA代碼並進行了一些項目實踐,COLA代碼整潔優雅,但有必定學習成本和使用成本。最終一個工程思想仍是要落地,我綜合了一些DDD技術框架,刪除了CQRS和事件總線模式,整理了一個簡單實用易於落地的項目架構。數據庫
基礎層。包含基礎性功能,例如數據庫訪問功能,緩存訪問功能,消息發送功能,還須要提供通用工具包設計模式
外部訪問層。在這個模塊中調用外部RPC服務,解析返回碼和返回數據api
領域層。這個模塊包含相似於三層架構的BO(Business Object),但不一樣的是使用充血模式進行定義,因此領域層自己也包含業務邏輯,不是簡單進行屬性聲明緩存
業務層。雖然領域層和業務層都包含業務,可是用途不一樣。業務層能夠組合不一樣領域的業務,而且能夠增長流控、監控、日誌、權限控制切面,相較於領域層更爲豐富微信
對外接口層。提供面向外部接口聲明markdown
對外訪問層。提供面向外部訪問入口架構
在使用上述框架以前咱們通常使用三層架構進行業務開發:框架
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只有數據沒有業務邏輯。整個代碼風格偏向於面向過程,因此也有人把貧血模型稱爲反模式。
在基於充血模型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層面向過程的代碼,代碼結構更加內聚。
咱們再分析一個將校驗邏輯內聚充血模型實例。
/** * 校驗器 * * @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";
}
}
複製代碼
有人可能會說充血模式只是把一些業務放在Domain層進行,沒有什麼特別之處。關於這個觀點我有如下思考:
(1) 代碼業務風格更加面向對象,而不是面向過程,整個邏輯也變得更加內聚
(2) 在設計原則中有一條開閉原則:面向擴展開放,面向修改關閉,我認爲這是最重要的一條設計原則,不少設計模式如策略模式、模板方法模式都是基於這個原則設計的。充血模型BO中能夠包含各類策略,可使用策略模式管理策略
(3) Domain層不是取代Service層,而是一種補充加強,雖然領域層和業務層都包含業務可是用途不一樣。業務層能夠組合不一樣領域的業務,而且能夠增長流控、監控、日誌、權限控制切面,相較於領域層更爲豐富
(4) Lombok框架如今很是流行,使代碼變得很是簡潔。有一點須要注意,隨意使用Lombok可能會破壞代碼封裝性,例如AccountBO對象不該該暴露setBalance方法。但因爲各層對象須要屬性拷貝必須暴露setBalance方法,這也是一種權衡策略
歡迎你們關注公衆號「JAVA前線」查看更多精彩分享文章,主要包括源碼分析、實際應用、架構思惟、職場分享、產品思考等等,同時歡迎你們加我微信「java_front」一塊兒交流學習