DDD 實戰(zhàn)2 - 集成限界上下文(Rest & Dubbo)

DDD 實戰(zhàn)1 - 基礎(chǔ)代碼模型 介紹了 DDD 的基礎(chǔ)代碼模型锰蓬,本文來實現(xiàn)與第三方上下文的調(diào)用热康。首先介紹 ordercenter 做為消費者引用第三方接口的方式(第三方接口分別提供 Rest 和 Dubbo 兩種形式)悍赢,然后介紹 ordercenter 做為服務(wù)提供者為第三方提供服務(wù)接口的方式情龄。

代碼:https://github.com/zhaojigang/ordercenter

引用第三方接口

設(shè)計原則:

  1. 第三方服務(wù)的接入需要使用防腐層進行包裝谆焊,進行防腐設(shè)計
  2. 第三方服務(wù)由應(yīng)用服務(wù)層進行編排
  3. 第三方服務(wù)的實現(xiàn)由基礎(chǔ)設(shè)施層進行實現(xiàn)
  4. 根據(jù) 外層依賴內(nèi)層 的原則,需要將第三方服務(wù)的防腐接口和防腐模型放置在應(yīng)用服務(wù)層弟灼,其實現(xiàn)放置在基礎(chǔ)設(shè)施層爬凑;應(yīng)用層只關(guān)心業(yè)務(wù)邏輯徙缴,不關(guān)心具體的技術(shù)實現(xiàn)(不關(guān)心是 Rest 服務(wù)還是 Dubbo 服務(wù)),基礎(chǔ)設(shè)施層來關(guān)心技術(shù)細節(jié)贰谣。

應(yīng)用服務(wù)層

image.png

應(yīng)用服務(wù) io.study.order.app.service.OrderAppService

/**
 * 訂單應(yīng)用服務(wù)
 */
@Service
public class OrderAppService {
    @Resource(name = "restInventoryAdaptor")
    private InventoryAdaptor inventoryAdaptor;

    /**
     * 創(chuàng)建一個訂單
     *
     * @param order
     */
    public void createOrder(Order order) {
        /**
         * 獲取商品庫存信息娜搂,進行校驗
         */
        InventoryDTO inventoryDTO = inventoryAdaptor.getRemainQuality(order.getGoodsId());
        if (inventoryDTO.getRemainQuantity() - order.getBuyQuality() < 0) {
            throw new OrderException(400, "商品庫存不足");
        }

        /**
         * 扣減庫存
         */
        inventoryAdaptor.reduceRemainQuality(order.getGoodsId(), order.getBuyQuality());

        /**
         * 存儲訂單
         */
        order.saveOrder(order);
    }
}

第三方服務(wù)防腐接口 io.study.order.rpc.inventory.InventoryAdaptor

/**
 * 庫存第三方服務(wù)接口
 */
public interface InventoryAdaptor {
    /**
     * 獲取商品剩余庫存信息
     * @param goodsId
     * @return
     */
    InventoryDTO getRemainQuality(Long goodsId);

    /**
     * 扣減庫存
     * @param goodsId
     * @param reduceQuality 減少的庫存數(shù)
     * @return
     */
    Boolean reduceRemainQuality(Long goodsId, Integer reduceQuality);
}

第三方服務(wù)防腐模型 io.study.order.rpc.inventory.InventoryDTO

/**
 * 庫存 DTO
 */
@Data
public class InventoryDTO {
    /**
     * 商品 ID
     */
    private Long goodsId;
    /**
     * 剩余庫存
     */
    private Integer remainQuantity;
}

基礎(chǔ)設(shè)施層

image.png

在基礎(chǔ)設(shè)施層,使用了 Rest 和 Dubbo 兩種方式來實現(xiàn)了庫存服務(wù)接口吱抚。分別來看下實現(xiàn)。

第三方服務(wù)實現(xiàn) io.study.order.rpc.impl.RestInventoryAdaptorImpl

/**
 * 庫存服務(wù)(Rest 實現(xiàn))
 */
@Component("restInventoryAdaptor")
public class RestInventoryAdaptorImpl implements InventoryAdaptor {
    private static final CloseableHttpClient HTTP_CLIENT = HttpClientBuilder.create().build();

    @Override
    public InventoryDTO getRemainQuality(Long goodsId) {
        HttpGet httpGet = new HttpGet("http://localhost:8082/inventory/getInventoryInfo?goodsId=" + goodsId);
        try {
            CloseableHttpResponse response = HTTP_CLIENT.execute(httpGet);
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                return RestInventoryTranslator.translateRestResponse2InventoryDTO(EntityUtils.toString(response.getEntity()));
            }
        } catch (IOException e) {
            throw new OrderException(500, "調(diào)用庫存服務(wù)異常, e:" + e);
        }
        return null;
    }

    @Override
    public Boolean reduceRemainQuality(Long goodsId, Integer reduceQuality) {
        HttpPost httpPost = new HttpPost("http://localhost:8082/inventory/reduceRemainInventory?goodsId=" + goodsId + "&reduceQuality=" + reduceQuality);
        try {
            CloseableHttpResponse response = HTTP_CLIENT.execute(httpPost);
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                return RestInventoryTranslator.translateRestResponse2Boolean(EntityUtils.toString(response.getEntity()));
            }
        } catch (IOException e) {
            throw new OrderException(500, "調(diào)用庫存服務(wù)異常, e:" + e);
        }
        return null;
    }
}

第三方服務(wù)防腐對象轉(zhuǎn)換器 io.study.order.rpc.impl.RestInventoryTranslator

/**
 * 庫存服務(wù)類型轉(zhuǎn)換器(Rest)
 */
public class RestInventoryTranslator {
    public static InventoryDTO translateRestResponse2InventoryDTO(String restResponse){
        return JSON.parseObject(restResponse, InventoryDTO.class);
    }

    public static Boolean translateRestResponse2Boolean(String restResponse){
        return JSON.parseObject(restResponse, Boolean.class);
    }
}

庫存服務(wù) Rest 實現(xiàn)

/**
 * 庫存 rest 服務(wù)
 */
@RestController
@RequestMapping("inventory")
public class InventoryController {
    @GetMapping("getInventoryInfo")
    public InventoryInfoDTO getInventoryInfo(@RequestParam("goodsId") Long goodsId) {
        InventoryInfoDTO dto = new InventoryInfoDTO();
        dto.setGoodsId(goodsId);
        if (goodsId == 1L) {
            dto.setRemainQuantity(100);
            dto.setInTransitQuantity(101);
        } else {
            dto.setRemainQuantity(200);
            dto.setInTransitQuantity(202);
        }
        return dto;
    }

    @PostMapping("reduceRemainInventory")
    public Boolean getInventoryInfo(@RequestParam("goodsId") Long goodsId, @RequestParam("reduceQuality") Integer reduceQuality) {
        return true;
    }
}

再來看下 Dubbo 的服務(wù)實現(xiàn)方式考廉,Dubbo 的配置方式通常會有兩種:xml 和注解方式秘豹。這里以注解方式進行演示。首先看庫存服務(wù)提供的 Dubbo 服務(wù)昌粤。

庫存服務(wù)(Dubbo 形式)

/************************* 接口 *************************/
/**
 * 庫存服務(wù)對外接口
 */
public interface InventoryFacade {
    /**
     * 獲取商品庫存信息
     */
    InventoryInfoDTO getRemainQuality(Long goodsId);

    /**
     * 扣減庫存
     */
    Boolean reduceRemainQuality(Long goodsId, Integer reduceQuality);
}

/************************* 實現(xiàn) *************************/
/**
 * 庫存服務(wù)實現(xiàn)
 */
@DubboService(version = "1.0.0", group = "product")
public class InventoryFacadeImpl implements InventoryFacade {
    @Override
    public InventoryInfoDTO getRemainQuality(Long goodsId) {
        InventoryInfoDTO dto = new InventoryInfoDTO();
        dto.setGoodsId(goodsId);
        if (goodsId == 1L) {
            dto.setRemainQuantity(100);
            dto.setInTransitQuantity(101);
        } else {
            dto.setRemainQuantity(200);
            dto.setInTransitQuantity(202);
        }

        return dto;
    }

    @Override
    public Boolean reduceRemainQuality(Long goodsId, Integer reduceQuality) {
        return true;
    }
}

/************************* 庫存服務(wù) Dubbo 接口返回值 *************************/
/**
 * 庫存信息
 */
@Data
public class InventoryInfoDTO implements Serializable {
    private static final long serialVersionUID = -7542780056688475990L;
    /**
     * 商品ID
     */
    private Long goodsId;
    /**
     * 剩余庫存
     */
    private Integer remainQuantity;
    /**
     * 在途庫存
     */
    private Integer inTransitQuantity;
}

/************************* application.properties *************************/
dubbo.application.name=inventory
dubbo.registry.address=multicast://224.5.6.7:1234
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

/************************* pom.xml *************************/
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.8</version>
        </dependency>

來看下在 ordercenter 引用第三方服務(wù)的姿勢

第三方服務(wù)實現(xiàn) io.study.order.rpc.impl.DubboInventoryAdaptorImpl

/**
 * 庫存服務(wù)(Dubbo 實現(xiàn))
 */
@Component("dubboInventoryAdaptor")
public class DubboInventoryAdaptorImpl implements InventoryAdaptor {
    @DubboReference(version = "1.0.0", group = "product")
    private InventoryFacade inventoryFacade;

    @Override
    public InventoryDTO getRemainQuality(Long goodsId) {
        InventoryInfoDTO inventoryInfoDTO = inventoryFacade.getRemainQuality(goodsId);
        return DubboInventoryTranslator.INSTANCE.toInventoryDTO(inventoryInfoDTO);
    }

    @Override
    public Boolean reduceRemainQuality(Long goodsId, Integer reduceQuality) {
        return inventoryFacade.reduceRemainQuality(goodsId, reduceQuality);
    }
}

第三方服務(wù)防腐對象轉(zhuǎn)換器 io.study.order.rpc.impl.DubboInventoryAdaptorImpl

/**
 * 庫存服務(wù)防腐對象轉(zhuǎn)換器
 */
@org.mapstruct.Mapper
public interface DubboInventoryTranslator {
    DubboInventoryTranslator INSTANCE = Mappers.getMapper(DubboInventoryTranslator.class);
    InventoryDTO toInventoryDTO(InventoryInfoDTO inventoryInfoDTO);
}

基礎(chǔ)設(shè)層包依賴

        <!-- dubbo -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>

服務(wù)啟動模塊

/************************* io.study.order.OrderApplication *************************/
/**
 * 應(yīng)用啟動器
 *
 * @author jigang
 */
@EnableOpenApi
@EnableDubbo // 啟動 Dubbo
@SpringBootApplication
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

/************************* application.properties *************************/
# 數(shù)據(jù)庫相關(guān)
...

# dubbo 共享
dubbo.application.name=ordercenter
dubbo.registry.address=multicast://224.5.6.7:1234

# dubbo 消費者
dubbo.consumer.check=false

這里將數(shù)據(jù)庫既绕、dubbo 的配置信息都寫在了啟動模塊中,實際上也可以將這些配置寫在他們各自使用的地方涮坐,比如可以將這些配置都寫在 infrastructure 中凄贩,同時還可以將這些配置根據(jù)功能拆分成不同的配置文件,之后在啟動類使用 @PropertySource 進行加載即可袱讹。

提供服務(wù)給第三方

如果僅提供 Rest 服務(wù)疲扎,那么當(dāng)前的用戶接口層中的 io.study.order.web.OrderController 即可。但是絕大多數(shù)情況下捷雕,需要提供類似 Dubbo 的使用方式椒丧,打成 Jar 包給第三方使用,為了避免內(nèi)部邏輯泄露救巷,以及為了打給第三方的包是一個“干凈”的包壶熏,我們抽出一個單獨的模塊 order-client 來實現(xiàn)這一目的。

設(shè)計原則:

  1. 創(chuàng)建 order-client 模塊:僅存儲提供給第三方的接口和對象模型
  2. 用戶接口層來實現(xiàn) order-client 中的接口
  3. 領(lǐng)域?qū)又行枰褂?order-client 中的查詢對象浦译,所以領(lǐng)域?qū)又苯右蕾?order-client棒假,最終形成如下的依賴關(guān)系。
image.png

client 模塊

image.png

對外接口 io.study.order.facade.OrderFacade

/**
 * 訂單服務(wù)
 */
public interface OrderFacade {
    /**
     * 查詢訂單
     */
    List<OrderDTO> getOrderList(OrderQueryRequest request);

    /**
     * 創(chuàng)建訂單
     */
    void createOrder(OrderDTO orderDTO);
}

對外接口返回模型 io.study.order.dto.OrderDTO

@Data
public class OrderDTO implements Serializable {
    private static final long serialVersionUID = 8642623148247246765L;
    /**
     * 訂單ID
     */
    private Long id;
    /**
     * 訂單名稱
     */
    private String name;
    /**
     * 商品ID
     */
    private Long goodsId;
    /**
     * 購買數(shù)量
     */
    private Integer buyQuality;
}

對外接口請求參數(shù) io.study.order.dto.OrderQueryRequest

/**
 * order 查詢請求參數(shù)
 */
@Data
public class OrderQueryRequest implements Serializable {
    private static final long serialVersionUID = 3330101115728844788L;
    /**
     * 訂單ID
     */
    private Long orderId;
    /**
     * 訂單名稱
     */
    private String orderName;
}

用戶接口層

image.png
/**
 * 訂單服務(wù)實現(xiàn)
 */
@DubboService(version = "1.0.0", group = "product")
public class OrderFacadeImpl implements OrderFacade {
    @Resource
    private OrderAppService orderAppService;
    @Resource
    private OrderRepository orderRepository;

    @Override
    public List<OrderDTO> getOrderList(OrderQueryRequest request) {
        List<Order> orderList = orderRepository.ordersOfCondition(request);
        return OrderDTOAssembler.INSTANCE.toDTOList(orderList);
    }

    @Override
    public void createOrder(OrderDTO orderDTO) {
        orderAppService.createOrder(OrderDTOAssembler.INSTANCE.fromDTO(orderDTO));
    }
}

Dubbo 服務(wù)的配置和啟動與上述的庫存服務(wù)相同精盅,不在贅述帽哑,接下來著重看一下 OrderQueryRequest 對象的傳輸路徑。

類:OrderFacade -> OrderFacadeImpl -> OrderRepository -> OrderRepositoryImpl
層:client      -> interfaces      -> domain          -> infrastructure

由于 domain 中要使用到 client 定義的對象渤弛,那么 domain 要依賴 client祝拯,乍一看,不符合 外層依賴內(nèi)層 的原則,實際上佳头,在 DDD 分層模型中鹰贵,是沒有 client 這個模塊的;另外康嘉, 外層依賴內(nèi)層 原則的目的是為了保證內(nèi)層的穩(wěn)定性碉输,這個穩(wěn)定怎么理解?個人理解為亭珍,模塊內(nèi)的代碼不隨外界技術(shù)的變動而變動敷钾,例如,將存儲從 mysql 換成了 oracle肄梨,我們僅需要處理 infrastructure 層即可阻荒,其他內(nèi)層不動;在比如众羡,當(dāng)前的都是直接穿數(shù)據(jù)庫的侨赡,想使用 Cache Aside Pattern 加一層緩存,那么僅需要在 infrastructure 資源庫的實現(xiàn)中進行修改即可粱侣,內(nèi)層邏輯不應(yīng)該動羊壹。但是現(xiàn)在如果是業(yè)務(wù)本身就發(fā)生了變化,那么內(nèi)部的模型除了部分可以使用開閉設(shè)計避免變動時齐婴,大部分情況下還是要動的油猫,不管是 application 還是 domain 層,client 被 domain 依賴就是這個道理柠偶,假設(shè) domain 不依賴 client情妖,那么我們需要在 domain 層也一模一樣的設(shè)計一個查詢模型,然后在用戶接口層進行轉(zhuǎn)換即可嚣州,這樣也是可以實現(xiàn)的鲫售,但是必要性是否有,可以考慮一下该肴。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末情竹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子匀哄,更是在濱河造成了極大的恐慌秦效,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涎嚼,死亡現(xiàn)場離奇詭異阱州,居然都是意外死亡,警方通過查閱死者的電腦和手機法梯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門苔货,熙熙樓的掌柜王于貴愁眉苦臉地迎上來犀概,“玉大人,你說我怎么就攤上這事夜惭⊥炊猓” “怎么了曼氛?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長查排。 經(jīng)常有香客問我惕稻,道長夹姥,這世上最難降的妖魔是什么蔑穴? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任陷谱,我火速辦了婚禮,結(jié)果婚禮上鸥昏,老公的妹妹穿的比我還像新娘塞俱。我一直安慰自己,他們只是感情好吏垮,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布敛腌。 她就那樣靜靜地躺著,像睡著了一般惫皱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尤莺,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天旅敷,我揣著相機與錄音,去河邊找鬼颤霎。 笑死媳谁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的友酱。 我是一名探鬼主播晴音,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缔杉!你這毒婦竟也來了锤躁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤或详,失蹤者是張志新(化名)和其女友劉穎系羞,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體霸琴,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡椒振,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了梧乘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片澎迎。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出夹供,到底是詐尸還是另有隱情灵份,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布罩引,位于F島的核電站各吨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏袁铐。R本人自食惡果不足惜揭蜒,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望剔桨。 院中可真熱鬧屉更,春花似錦、人聲如沸洒缀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽树绩。三九已至萨脑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饺饭,已是汗流浹背渤早。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瘫俊,地道東北人鹊杖。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像扛芽,于是被迫代替她去往敵國和親骂蓖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容