分層架構最佳實踐

概述

分層的目的是爲了將某個功能的實現邏輯,根據一定規則拆分到各層次,從而降低各層的複雜度,保證代碼的可讀性和可維護性。

我當過大量實踐總結,設計瞭如下圖所示的分層規範:


該分層規範的核心思想是將業務和技術細節相分離,也即分層架構下的業務邏輯層和服務層的劃分。

接下來我說下如何分離。

業務與技術相分離

其分離可通過如下表格來說明。

邏輯 數據 異常
業務 業務邏輯 業務數據 業務異常
技術 技術邏輯 技術數據 技術異常

技術邏輯

技術邏輯,即我們的一些技術實現手段性的代碼。比如我們要實現一個關閉訂單的業務動作,其技術實現手段可能就是將數據庫的某個字段值從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;
    }

}

以上就是本人關於分層架構的最佳實踐。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章