概述
分層的目的是爲了將某個功能的實現邏輯,根據一定規則拆分到各層次,從而降低各層的複雜度,保證代碼的可讀性和可維護性。
我當過大量實踐總結,設計瞭如下圖所示的分層規範:
該分層規範的核心思想是將業務和技術細節相分離,也即分層架構下的業務邏輯層和服務層的劃分。
接下來我說下如何分離。
業務與技術相分離
其分離可通過如下表格來說明。
邏輯 | 數據 | 異常 | |
---|---|---|---|
業務 | 業務邏輯 | 業務數據 | 業務異常 |
技術 | 技術邏輯 | 技術數據 | 技術異常 |
技術邏輯
技術邏輯,即我們的一些技術實現手段性的代碼。比如我們要實現一個關閉訂單的業務動作,其技術實現手段可能就是將數據庫的某個字段值從0更新爲1的過程。
技術數據
技術數據,通常情況下可以和業務數據一樣。典型有區別的就是狀態類的數據。比如我們的訂單狀態,從技術數據角度,它是0表示正常,1表示關閉。而在業務數據角度,它應該是枚舉類型,CLOSED表示關閉,OPEN表示正常的。
技術異常
技術異常,通常也是系統性的異常。例如網絡超時,遠程服務報錯,代碼bug導致的空指針等異常。此類異常的特點是不可枚舉且無法避免。該類異常一定要打印異常日誌用於排查問題。同時該異常最好不要拋給調用方,因爲調用方也看不懂。如果調用方是客戶端,將異常信息展示給了用戶,則體驗極差,用戶會懵逼。
業務邏輯
業務邏輯,即我們實現某個功能的業務流程。
業務數據
技術數據這一段已做說明,這裏不多說了。
業務異常
業務異常,通常是不滿足某個業務前提條件而進行某個業務動作時,被攔截終止流程的異常。比如用戶要關閉一個訂單,但目前訂單已經是關閉了的,則可以拋出一個業務異常:訂單已經關閉,無需重複關閉!。
以上就是業務和技術相分離的思想。瞭解了這些我們就可以接下來學習各層的職責劃分。
各層職責
數據傳輸層
用於實現與遠程服務的通信。遠程服務包括db、rpc服務端、kafka服務端、redis服務端等等
該層比較簡單,通常不需要我們開發,我們常見的操作數據庫的Mapper、調用http服務的httpClient,以及調用遠程RPC服務的client包,都是作爲數據傳輸層來看待。
異常情況:該層可能會因爲網絡超時,遠程服務無響應拋出系統異常。
服務層(Service)
爲業務邏輯層提供服務
職責
服務層,也作爲防腐層,它用來封裝對數據傳輸層的技術調用細節,爲業務邏輯層提供具有業務含義的原子性業務動作
和業務數據
。
其中的方法命名要儘可能的擁有業務含義。例如關閉訂單這個場景,其應該提供一個closeOrder(long orderId)
方法,內部技術細節則可能是調用mybatis的Mapper將訂單表的status
字段由0更新爲1.
異常情況:該層可能發生的異常有,你代碼本身的bug導致的異常,以及調用數據傳輸層產生的系統異常。爲了業務邏輯層可以感知到該層的異常,該層的異常通常不需要自己捕獲,用默認拋給業務邏輯層即可。
業務邏輯層
該層就是用來實現業務流程。
該層就是用來調用服務層,實現業務流程的編排。該層作爲業務邏輯層,要儘可能的保證業務邏輯的可讀性強,而不被一些技術細節所幹擾。
異常情況:該層拋出的異常,是在不滿足某個業務前提條件時,終止業務流程的業務異常。以及調用服務層可能產生的系統異常。
接口層
作爲服務對外的通道,要保證確定性的輸入和確定性的輸出。
職責
-
入參校驗
對參數進行校驗、解析、以及初始化工作。儘可能保證進入業務邏輯層前,參數是可靠符合預期的。
-
結果返回
業務邏輯執行完後,將結果響應給調用方。通常是將響應數據進行序列化以方便網絡傳輸。
-
異常處理
這是比較重要的一個職責,也是最容易被忽略的。
- 該層在對入參進行校驗,可能會有參數異常產生,這時我們需要捕獲該異常,將異常告知調用方。
- 該層還會調用業務邏輯層完成業務流程,則可能會收到業務邏輯層拋出來的業務異常和系統異常。我們也需要將異常捕獲,通過某個手段告知調用方。
-
日誌打印
爲了方便問題排查我們通常需要打印日誌。在該層我們可以根據自身情況打印如下日誌。
- 入參
- 異常
- 異常信息,尤其是系統異常一定要打印,用於排查問題必不可少的依據。對於參數異常和業務異常,可無需打印或者以warn級別打印,因爲這不是你的錯。
異常情況:該層要對所有異常進行捕獲處理。通常我們接口響應數據中定義code
碼,來告知調用方本次調用的異常情況。不要再往上拋異常,否則你就無法確定調用方到底得到怎樣的響應。
以上就是分層的詳細說明。還很抽象對吧,沒關係,看個示例感受感受。
示例
數據傳輸層
由於該層不需要開發,所以就不再貼代碼示例了。
服務層
訂單服務
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper;
/**
* 訂單是否存在。
* @param id
* @return
*/
public boolean isExist(long id) {
//調用mapper查詢數據庫該訂單是否存在
}
/**
* 關閉訂單
* @param id
*/
public void closeOrder(long id) {
//調用mapper將狀態字段從0更新爲1
}
}
消息隊列服務
@Service
public class MqService {
public void sendOrderClosedMsg(long orderId,String msg){
//構建消息體
//調用mq客戶端發消息
}
}
業務邏輯層
訂單關閉的業務邏輯(該處業務流程純虛構)
@Service
public class OrderBiz {
@Resource
private OrderService orderService;
@Resource
private MqService mqService;
public void closeOrder(long id)throws Exception{
//判斷訂單是否存在
if(!orderService.isExist(id)){
throw new BizException("訂單不存在!");
}
//關閉訂單
orderService.closeOrder(id);
//發送訂單關閉消息
mqService.sendOrderClosedMsg(id,"訂單已關閉");
}
}
接口層
接口層參數校驗、異常處理、響應返回
@RestController
@RequestMapping("/order")
public class OrderController {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderController.class);
@Resource
private OrderBiz orderBiz;
@RequestMapping("/close")
public Response<Void> closeOrder(Long orderId){
try {
if(orderId == null || orderId < 1){
throw new ParamException("訂單id不合法");
}
orderBiz.closeOrder(orderId);
return Response.success(null);
}catch (ParamException e){//參數異常
return new Response<>(ResponseCode.PARAM_ERROR,e.getMessage());
}catch (BizException e){//業務異常
return new Response<>(ResponseCode.BIZ_EXCEPTION,e.getMessage());
}catch (Exception e){//保證任何異常都能被捕獲
LOGGER.error("關閉訂單時發生異常:",e);
return new Response<>(ResponseCode.SERVER_ERROR,"服務端開小差,請稍後重試!");
}
}
}
響應實體
@Data
public class Response<T> {
protected Integer code;
protected String errMsg;
protected T data;
public Response() {
}
public Response(Integer code, String errMsg) {
this.code = code;
this.errMsg = errMsg;
}
public static <T> Response<T> success(T data){
Response response = new Response();
response.setCode(ResponseCode.SUCCESS);
response.setData(data);
return response;
}
}
以上就是本人關於分層架構的最佳實踐。