<bdo id="ks4iu"><del id="ks4iu"></del></bdo>
  • 
    <pre id="ks4iu"></pre>
  • <bdo id="ks4iu"><del id="ks4iu"></del></bdo>
    <input id="ks4iu"><em id="ks4iu"></em></input>
    
    
  • <center id="ks4iu"><cite id="ks4iu"></cite></center>
  • 首頁 > 智能影音 >

    領域驅動編程,代碼怎么寫?

    簡介:領域驅動開發最重要的當然是正確地進行領域拆解,這個拆解工作可以在理論的指導下,結合設計者對業務的深入分析和充分理解進行。本文假定開發前已經進行了領域劃分,側重于研究編碼階段具體如何實踐才能體現領域驅動的優勢。

    作者 | 昌夜

    前言

    相較于大家熟練使用的 MVC 分層架構,領域驅動設計更適用于復雜業務系統和需要持續迭代的軟件系統的架構模型。關于領域驅動設計的概念及優勢,可以參考的文獻非常多,大多數的同學都看過相關的書籍,所以本文不討論領域驅動概念層面的東西,而是試圖從編程實踐的層面,對領域驅動開發做一些簡單的介紹。

    加入阿里健康之后,我所在的團隊也在積極推進領域驅動設計的應用,相關同學也曾給出優秀的腳手架代碼,但目前看起來落地情況并不太理想,個人淺見,造成這種結果主要有四個原因。

    大家更熟悉 MVC 的編程模式,需要快速實現某個功能的時候,往往傾向于使用較為穩妥、熟悉的方式。 大家對領域驅動編程應該怎么編寫并沒有一個統一的認知(Axon Framework[1] 對領域驅動設計實現的非常好,但它太“重”了)。 DDD 落地本身就比較難,往往需要事件驅動和 Event Store 來完美實現,而這二者是我們不常用的。 領域驅動設計是面向復雜系統的,業務發展初期看上去都比較簡單,一上來就搞領域驅動設計有過度設計之嫌。這也是領域驅動設計常常在系統不得不重構的是時候才被拿出來討論的原因。

    筆者曾在研發過程中研究、實踐過領域驅動編程,對領域驅動框架 Axon Framework 也做了深入的了解,(也許是因為業務場景相對簡單)當時落地效果還不錯。拋卻架構師的視角,從一線研發同學的角度來看,基于領域驅動編程的核心優勢在于:

    實施面向對象的編程模式,進而實現高內聚、低耦合。 在復雜業務系統的迭代過程中,保證代碼結構不會無限制地變得混亂,因此保證系統可持續維護。

    領域驅動開發最重要的當然是正確地進行領域拆解,這個拆解工作可以在理論的指導下,結合設計者對業務的深入分析和充分理解進行。本文假定開發前已經進行了領域劃分,側重于研究編碼階段具體如何實踐才能體現領域驅動的優勢。

    保險領域知識簡介

    以保險業務為例來進行編程實踐,一個高度抽象的保險領域劃分如圖所示。通過用例分析,我們把整個業務劃分成產品域、承保、核保、理賠等多個領域(Bounded-Context),每個領域又可以根據業務發展情況拆分子域。當然,完備保險業務要比圖中展現的復雜太多,這里我們不作為業務知識介紹的篇章,只是為了方便后續的代碼實踐。

    領域驅動開發的代碼結構

    1、領域驅動的代碼分層

    可以使用不同的 Java 項目發布不同的微服務對領域進行隔離,也可以在同一個 Java 項目中,使用不同 module 進行領域隔離。這里我們使用 module 進行領域隔離的實現。但是無論采用何種方式進行領域隔離,領域之間的交互只能使用對方的二方包或者 API 層提供的 HTTP 服務,而不能直接引入其他領域的其他服務。

    在每個領域內部,相對于 MVC 對應用三層架構的拆分,領域驅動的設計將應用模塊內部分為如圖示的四層。

    用戶接口層

    負責直接面向外部用戶或者系統,接收外部輸入,并返回結果,例如二方包的實現類、Spring MVC 中的 Controller、特定的數據視圖轉換器等通常位于該層。在代碼層面常常使用的包命名可以是 interface, api, facade 等。用戶接口層的入參、出參類定義采用 POJO 風格。

    用戶接口層是輕的一層,不含業務邏輯。安全認證,簡單的入參校驗(例如使用 @Valid 注解),訪問日志記錄,統一的異常處理邏輯,統一返回值封裝應當在這層完成。

    用戶接口層所需要的功能實現是由應用層完成,這里一般不需要進行依賴倒置。編碼時,該層可以直接引入應用層中定義的接口,因而該層依賴應用層。需要注意的是,雖然理論上用戶接口層可以直接使用領域層和基礎設施層的能力,但這里建議大家在對這種用法熟練掌握前,最好采用嚴格的分層架構,即當前層只依賴其下方相鄰的一層。

    應用層

    應用層具體實現接口層中需要功能,但該層并不實現真正的業務規則,而是根據實際的 use case 來協調調用領域層提供的能力。

    消息發送、事件監聽、事務控制等建議在這一層實現。在代碼層面常常使用的包命名可以是 application, service, manager 等。它用來取代 Spring MVC 中 service 層,并把業務邏輯轉移到領域層。

    領域層

    領域層面向對象的,它主要用來體現和實現領域里的對象所具備的固有能力。因此,在領域驅動編程中,領域層的編程實現是不允許依賴其他外部對象的,領域層的編程是在我們對領域內的對象所具備的固有能力和它要在當前業務場景下展現什么樣的能力有一定了解后,可以直接編碼實現的。

    例如我們最開始接觸面向對象的編程的時候,常常會遇到的一個例子是鳥會飛、狗會游泳,假設我們的業務域只關心這些對象的運動,我們可以做如下的實現。

    public interface Moveable {

    void move();

    }

    public abstract class Animal implements Moveable {}

    public class Bird extends Animal {

    public void move(){

    //try to fly

    System.out.println("I"am flying");

    }

    }

    public class Dog extends Animal {

    public void move(){

    //try to swim

    System.out.println("I"am swimming");

    }

    }

    基于領域驅動的編程需要這樣(充血模型)去實現對象的能力,而不是像我們在 MVC 架構中常常使用貧血模型,把業務邏輯寫在 service 中。

    當然,即使采用了這樣的編程方式,距離實現領域驅動還差的遠,一些看似簡單的問題就可能給我們帶來巨大的不安感。例如復雜的對象應當如何初始化和持久化?同樣一個事物在不同領域都存在,但其關注點不同時這個事物應當分別怎么抽象?不同領域的對象需要對方的信息時,應當怎么獲?。?/p>

    這些問題,我們也會在代碼示例部分嘗試給出一些參考的方案。

    基礎設施層

    基礎設施層為上面各層提供通用的技術能力,例如監聽、發送消息的能力,數據庫/緩存/NoSQL數據庫/文件系統等倉儲的 CRUD 能力等。021

    2、小結

    根據對領域驅動設計各層的進一步分析,一個更加具體化的分層結構如下。

    基于上面的分層原則,前述保險領域一個可以參考的代碼結構如下,我們將在下面編碼示例詳細講解每一個分包的理念和作用。

    領域驅動開發的代碼

    理論上,DOMAIN 不依賴其他層次且是業務核心,我們應當先編寫領域層代碼,但是一則由于我們對保險領域知識的欠缺,可能不清楚保單到底有哪些固有能力;二則為了便于講解,因此我們直接借助一個用例來展示代碼。

    1、用例

    用戶在前端頁面選擇保險產品,選擇可選的保障責任,輸入投/被保人信息,選擇支付方式(分期/躉交等)并支付后提交投保請求; 服務端接受投保請求 -> 核保 -> 出單 -> 下發保單權益。 這里用例 1 是用例 2 的前置用例,我們假定用例 1 已經順利完成(用例 1 中完成了費率計算),只來實現用例 2,并且用例 2 也只是大略的實現,只要能把代碼樣式展示即可。

    2、用戶接口層編程實踐

    分包結構

    其中 client 是對 inusurance-client (公共二方包) 部分的實現,web 是 rest 風格接口的實現。

    用例代碼

    @AllArgsConstructor

    @RestController

    @RequestMapping("/insure")

    public class PolicyController {

    private final InsuranceInsureService insuranceInsureService;

    /**

    * 投保出單

    * @param request

    * @return 保單 ID

    */

    @RequestMapping(value = "/issue-policy", method = RequestMethod.POST)

    public String issuePolicy(IssuePolicyRequest request){

    return insuranceInsureService.issuePolicy(request);

    }

    }

    這里用到的入參和返回值的類都在應用層中定義。

    3、應用層編程實踐

    1、分包結構

    其中最外層接口是面向具體業務場景的,可以根據業務發展再進行分包。 pojo 包中定義了應用層用到的各種數據類(上面的 IssuePolicyRequest 就在這里)及其向其他層傳播時需要進行類型轉換的轉化器。 tasks 包中定義了一些定時任務的入口。

    注意,在領域編程實踐中,會需要非常多的類型轉換,我們可以借助一些框架(例如 MapStruct[2])來減少這些類型轉換給我們帶來的繁瑣工作。

    2、用例代碼

    @Service

    @AllArgsConstructor

    public class InsuranceInsureServiceImpl implements InsuranceInsureService {

    private final PolicyFactory policyFactory;

    private final StakeHolderConvertor stakeHolderConvertor;

    private final PolicyService policyService;

    /**

    * 事務控制一般在應用層

    * 但是需要注意底層存儲對事務的支持特性

    * 底層是分庫分表時,可能需要其他手段來保證事務,或者將非核心的操作從事務中剝離(例如數據庫 ID 生成)

    */

    @Override

    @Transactional(rollbackFor = Exception.class)

    public String issuePolicy(IssuePolicyRequest request) {

    Policy policy = policyFactory.createPolicy(request.getProductId(),

    stakeHolderConvertor.convert(request.getStakeHolders()));

    //出單流程控制

    policyService.issue(policy);

    PolicyIssuedMessage message = new PolicyIssuedMessage();

    message.setPolicyId(policy.getId());

    MQPublisher.publish(MQConstants.INSURANCE_TOPIC, MQConstants.POLICY_ISSUED_TAG, message);

    return policy.getId().toString();

    }

    }

    這里代碼展示的是應用層對用例 2 的處理。

    使用領域層的工廠類構建 Policy 聚合。如果需要傳遞復雜對象,需要先用類型轉換器將應用層的數據類轉化為領域層的實體類或者值對象。 使用領域層服務控制出單流程 發送出單成功消息,其他領域監聽到感興趣的消息會進行響應。

    4、領域層編程實踐

    1、分包結構

    這里領域層一共有5個一級分包。

    anticorruption 是領域防腐層,是當前領域需要獲知其他領域或者外部信息時,對其他領域二方包的封裝。防腐層從代碼層面來看,可以避免調用外部客戶端時,在領域內部進行復雜的參數拼裝和結果的轉換。 factory 解決了復雜聚合的初始化問題。我們設計好領域模型供外部調用,但如果外部也必須使用如何裝配這個對象,則必須知道對象的內部結構。對調用方開發來說這是很不友好的。其次,復雜對象或者聚合當中的領域知識(業務規則)需要得到滿足,如果讓外部自己裝配復雜對象或聚合的話,就會將領域知識泄露到調用方代碼中去。需要注意的是,這里主要是把聚合或實體需要的數據填充進來,而不涉及對象的行為。

    因此這里工廠的核心作用是從各處拉取初始化聚合或實體所需要的外部數據。

    @Service

    @AllArgsConstructor

    public class PolicyFactory {

    /**

    * 產品領域防腐層服務

    */

    private final ProductService productService;

    /**

    * 從各種數據來源查詢直接能查到的前置數據,填充到 policy 中

    * @param productId

    * @param stakeHolders

    * @return

    */

    public Policy createPolicy(Long productId, List stakeHolders) {

    PolicyProduct product = productService.getById(productId);

    //其他填充數據,這里調用了聚合自身的靜態工廠方法

    Policy policy = Policy.create(product, stakeHolders);

    return policy;

    }

    }

    model 中是領域對象的定義。其中 vo 包中定義了領域內用到的值對象??梢钥吹竭@里有PolicyProduct 這樣一個保險產品類,在投保領域,我們關注的是和保單相關的某個產品及其快照信息,因此我們在這里定義一個保單保險產品類,防腐層負責把從產品域獲得的保險產品信息轉換為我們關心的保單保險產品類對象。 按照領域驅動設計的最佳實踐,領域對象模型中不允許出現 service、repository 這些用以獲取外部信息的東西,它的核心概念是一個完備的實體初始化完成后,它能做什么,或者它經歷了什么之后狀態會發生怎樣的變化。

    下面是領域內核心的聚合 Policy 的示例代碼。

    @Getter

    public class Policy {

    private Long id;

    private PolicyProduct product;

    private List stakeHolders;

    private Date issueTime;

    /**

    * 工廠方法

    * @param product

    * @param stakeHolders

    * @return

    */

    public static Policy create(PolicyProduct product, List stakeHolders){

    Policy policy = new Policy();

    policy.product = product;

    policy.stakeHolders = stakeHolders;

    return policy;

    }

    /**

    * 保單出單

    */

    public void issue(Long id) {

    this.id = id;

    this.issueTime = new Date();

    }

    }

    repository 是倉儲包,只定義倉儲接口,不關心具體實現,具體的實現交由基礎設施層負責,體現了依賴倒置的思想。 service 是領域服務,它定義一些不屬于領域對象的行為,但是又有必要的操作,比如一些流程控制。

    2、用例代碼

    @Service

    @AllArgsConstructor

    public class PolicyService {

    private final InsureUnderwriteService insureUnderwriteService;

    private final PolicyRepository policyRepository;

    public void issue(Policy policy) {

    if(!insureUnderwriteService.underwrite(policy)){

    throw new BizException("核保失敗");

    }

    policy.issue(IdGenerator.generate());

    //保存信息

    //policyRepository.save(policy);

    policyRepository.create(policy);

    }

    }

    這里注意我們注掉了一行 policyRepository.save(policy);,那么為什么要區別 save 和 create 呢?

    save 是領域驅動設計中最正確的做法:我的聚合或者實體有變動,倉儲不用關心是新建還是更新,幫我保存起來就好了。聽上去很美好,但對關系型數據庫存儲卻是很不友好的。因此,在我們的場景里,需要違背一下書上所謂的最佳實踐,我們告訴倉儲是要新建還是更新,甚至如果是更新的話更新的是哪些列。

    另外領域驅動的最佳實踐是基于事件驅動的,AxonFramework 對其有完美的實現,應用層發出一個 IssuePolicyCommand 指令,領域層接收該指令,完成保單創建后發出PolicyIssuedEvent,該 event 會被監聽并且持久化到 event store 中。這種方式目前看起來在我們這里落地的可能性不大,不做更多介紹。

    5、基礎設施層編程實踐

    1、分包結構

    這里只展示了 repository 的實現,但實際上這里還有 RPC 調用的二方包實現類注入等很多內容。上文說到領域層不關心倉儲的實現,交由基礎設施層負責?;A設施層可以根據需要使用關系型數據庫、緩存或者NoSQL,領域層是無感知的。這里我們以關系型數據庫為例來,dao 和 dataobject 等都可以使用例如 mybatis generator 等工具生成,領域對象 和 dataobject 之間的轉換由 convertor 負責。

    2、用例代碼

    @Repository

    @AllArgsConstructor

    public class PolicyRepositoryImpl implements PolicyRepository {

    private final PolicyDAO policyDAO;

    private final StakeHolderDAO stakeHolderDAO;

    private final PolicyConvertor policyConvertor;

    private final StakeHolderConvertor stakeHolderConvertor;

    @Override

    public String save(Policy policy) {

    throw new UnsupportedOperationException();

    }

    @Override

    public String create(Policy policy) {

    policyDAO.insert(policyConvertor.convert(policy));

    stakeHolderDAO.insertBatch(stakeHolderConvertor.convert(policy));

    //...其它數據入庫

    return policy.getId().toString();

    }

    @Override

    public void updatePolicyStatus(String newStatus) {

    }

    }

    這部分代碼比較簡單,無需贅言。

    結語

    關于領域驅動,筆者仍處于初學者階段,再好的設計,隨著業務的發展,代碼也難免變得混亂,這個過程中,每個參與者都有責任。最后,總結一下我們維持代碼初心的一些原則,和大家分享。

    深入理解業務場景,分析用例,進行正確的領域劃分。 確定好實現方式后,大家盡量按照既定模式/風格編程,有異議的地方可以一起討論后統一改動。 不引入不必要的復雜度。 不斷對系統設計進行優化改進,對繁瑣的代碼,用設計模式進行優化。 寫注釋。

    [1]https://docs.axoniq.io/reference-guide

    [2]https://mapstruct.org

    本文為阿里云原創內容,未經允許不得轉載。

    責任編輯:Rex_08

    關鍵詞:
    推薦閱讀
    欧美国产在线一区,免费看成年视频网页,国产亚洲福利精品一区,亚洲一区二区约美女探花
    <bdo id="ks4iu"><del id="ks4iu"></del></bdo>
  • 
    <pre id="ks4iu"></pre>
  • <bdo id="ks4iu"><del id="ks4iu"></del></bdo>
    <input id="ks4iu"><em id="ks4iu"></em></input>
    
    
  • <center id="ks4iu"><cite id="ks4iu"></cite></center>
  • 主站蜘蛛池模板: 日本不卡在线观看免费v| peeasian人体| 97久久精品亚洲中文字幕无码| 黄在线观看www免费看| 男女一边摸一边做爽爽| 婷婷五月在线视频| 全黄a一级毛片| 一级做a免费视频观看网站| 国产亚洲欧美在在线人成| 狠狠色丁香久久综合五月| 好大好深好猛好爽视频免费| 国产另类ts人妖一区二区| 亚洲欧洲av无码专区| 中文字幕天天躁日日躁狠狠躁免费 | 热99re久久精品天堂vr| 日本口工全彩漫画| 国产自产视频在线观看香蕉| 国产ts最新人妖在线| 亚洲中文精品久久久久久不卡| 中文字幕手机在线播放| 精品欧美一区二区3d动漫| 很黄很污的视频网站| 国产剧情精品在线| 亚洲av福利天堂一区二区三| 99热这里只有精品66| 精品视频一区二区三区在线观看| 成年性生交大片免费看| 免费看无码自慰一区二区| free性满足hd极品| 精品日本一区二区三区在线观看| 少妇无码太爽了不卡视频在线看| 国产zzjjzzjj视频全免费| 三上悠亚电影全集免费| 雏女强破瓜在线观看| 机机对机机的30分钟免费软件| 国精品在亚洲_欧美| 免费精品视频在线| 99久久99久久精品国产片果冻| 欧美性受xxxx狂喷水| 在线播放国产一区二区三区| 伊人色综合视频一区二区三区|