自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

為什么需要CQRS,它能解決什么問題?

開發(fā) 架構(gòu)
本文詳細(xì)介紹了CQRS模式的基本概念及其實(shí)現(xiàn)方式,重點(diǎn)分析了在DailyMart項(xiàng)目中如何通過實(shí)踐CQRS架構(gòu)對(duì)訂單模塊進(jìn)行改造。希望通過本文的講解,能夠幫助你更好地理解CQRS模式的應(yīng)用場(chǎng)景、優(yōu)勢(shì)及實(shí)施細(xì)節(jié),提升系統(tǒng)架構(gòu)的可維護(hù)性和擴(kuò)展性。

為什么需要CQRS?

圖片圖片

在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)中,業(yè)務(wù)邏輯的基本處理流程通常如下:接口層接收業(yè)務(wù)請(qǐng)求,進(jìn)行參數(shù)校驗(yàn)后,調(diào)用應(yīng)用服務(wù)執(zhí)行業(yè)務(wù)編排。在應(yīng)用服務(wù)中,加載聚合根,接著由領(lǐng)域?qū)ο筇幚順I(yè)務(wù)邏輯,最后通過基礎(chǔ)設(shè)施層更新領(lǐng)域?qū)ο蟆?/p>

然而,在實(shí)際開發(fā)中,我們經(jīng)常遇到一些復(fù)雜的查詢需求,比如分頁查詢、非業(yè)務(wù)標(biāo)識(shí)符的條件查詢以及多表關(guān)聯(lián)查詢。這些需求往往涉及到多個(gè)聚合根,并且在查詢時(shí)不一定需要加載完整的聚合根。

例如,在之前的章節(jié)中,我通過擴(kuò)展倉儲(chǔ)接口來支持條件查詢,如訂單服務(wù)的倉儲(chǔ)接口定義:

public interface OrderRepository extends Repository<TradeOrder, OrderId> {
    
    /**
     * 根據(jù)訂單編號(hào)查詢訂單
     * @param orderSn 訂單號(hào)
     * @return 訂單聚合
     */
    TradeOrder findOrderByTransaction(String customerId);
    
    /**
     * 修改訂單狀態(tài)
     * @author jam
     * @date 2023/12/19 9:07
     * @param orderSn 訂單編號(hào)
     * @param status 訂單狀態(tài)
     */
    void changeStatus(String orderSn, OrderStatusEnum status);
    
    /**
     * 分頁查詢
     * @param customerId 用戶ID
     * @param pageRequest 分頁請(qǐng)求體
     */
    PageResponse<TradeOrder> pageQuery(Long customerId, PageRequest pageRequest);
}

這個(gè)設(shè)計(jì)存在一定問題:倉儲(chǔ)接口的職責(zé)變得不再單一。根據(jù)DDD的設(shè)計(jì)理念,Repository主要負(fù)責(zé)維護(hù)聚合根的生命周期,然而在這里,它同時(shí)承擔(dān)了分頁查詢職能,這與其單一職責(zé)原則相悖。每當(dāng)我們需要新增查詢功能時(shí),都需要在領(lǐng)域?qū)拥膫}儲(chǔ)接口中增加新方法,導(dǎo)致接口變得越來越復(fù)雜。

為了保持倉儲(chǔ)接口的單一職責(zé),我們需要將查詢操作與聚合根的生命周期管理分離。CQRS(命令查詢職責(zé)分離) 就是為了解決這個(gè)問題。

查詢與聚合根的關(guān)系

聚合根代表了事務(wù)的一致性邊界,倉儲(chǔ)接口需要確保在加載時(shí)獲取聚合根的完整狀態(tài)以保證數(shù)據(jù)的準(zhǔn)確性。然而,許多查詢操作,如分頁查詢和條件查詢,往往只需要讀取聚合根的一部分?jǐn)?shù)據(jù),而不需要修改它的狀態(tài)。在這種情況下,加載整個(gè)聚合根的狀態(tài)不僅會(huì)導(dǎo)致不必要的性能開銷,還可能使查詢變得更復(fù)雜和低效。

因此,在只查詢而不修改的場(chǎng)景下,其實(shí)沒必要完整的加載聚合根。接下來,我們將引入CQRS來解決這個(gè)問題。

什么是CQRS?

CQRS(Command Query Responsibility Segregation,命令查詢職責(zé)分離)是一種架構(gòu)模式,它通過將修改操作(命令,Command)與查詢操作(查詢,Query)分開,使用不同的模型來分別處理這兩類操作,從而實(shí)現(xiàn)命令與查詢的分離。

在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)中引入CQRS后,應(yīng)用層的職責(zé)被明確分為兩個(gè)部分:

  • 命令應(yīng)用服務(wù)(Command Application Service):負(fù)責(zé)處理寫操作,如創(chuàng)建、更新和刪除。
  • 查詢應(yīng)用服務(wù)(Query Application Service):負(fù)責(zé)處理讀操作,包括數(shù)據(jù)查詢和展示。

引入CQRS后,命令和查詢操作在應(yīng)用層使用不同的模型進(jìn)行處理:

  • 在命令應(yīng)用服務(wù)中,依舊使用領(lǐng)域模型來執(zhí)行業(yè)務(wù)操作。通過倉儲(chǔ)(Repository)加載完整的聚合根,并由聚合根修改其內(nèi)部狀態(tài)來實(shí)現(xiàn)業(yè)務(wù)邏輯。
  • 在查詢應(yīng)用服務(wù)中,使用專門的數(shù)據(jù)模型來處理查詢操作。這些數(shù)據(jù)模型直接從數(shù)據(jù)庫讀取數(shù)據(jù),并將結(jié)果展示給用戶。查詢操作不涉及領(lǐng)域邏輯,只關(guān)注高效的數(shù)據(jù)檢索和展示,可以直接使用基礎(chǔ)設(shè)施層的額數(shù)據(jù)模型和ORM接口來完成操作。

實(shí)際上,CQRS并非DDD獨(dú)有的概念,無論是否使用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),都會(huì)推薦采用CQRS架構(gòu)。具體而言,在三層架構(gòu)中,可以將Service層拆分為不同職責(zé)的模型;在DDD的四層架構(gòu)中,則將應(yīng)用服務(wù)(Application Service)拆分為命令服務(wù)和查詢服務(wù)。

CQRS的實(shí)現(xiàn)

CQRS的實(shí)現(xiàn)通常分為相同數(shù)據(jù)源模式和異構(gòu)數(shù)據(jù)源模式,兩者適用于不同的業(yè)務(wù)場(chǎng)景。

相同數(shù)據(jù)源的CQRS

在這種模式下,命令服務(wù)和查詢服務(wù)共用同一套數(shù)據(jù)源。命令操作通過領(lǐng)域模型完成,查詢操作則通過數(shù)據(jù)模型實(shí)現(xiàn)。由于數(shù)據(jù)源相同,CQRS的實(shí)現(xiàn)相對(duì)簡(jiǎn)單,且能夠滿足大部分業(yè)務(wù)場(chǎng)景需求。

如下圖所示:

圖片圖片

異構(gòu)數(shù)據(jù)源的CQRS

雖然相同數(shù)據(jù)源模式可以滿足大多數(shù)業(yè)務(wù)需求,但在某些場(chǎng)景下,為了優(yōu)化性能、解決特定問題,可能會(huì)引入其他數(shù)據(jù)存儲(chǔ)中間件,將業(yè)務(wù)數(shù)據(jù)的副本存儲(chǔ)在新的數(shù)據(jù)源中,從而形成異構(gòu)數(shù)據(jù)源。這時(shí),命令操作和查詢操作將分別由不同的數(shù)據(jù)源承接。

示例:

以訂單查詢?yōu)槔?,為了提高查詢性能,訂單領(lǐng)域在創(chuàng)建訂單后,可以通過 binlog 將 MySQL 數(shù)據(jù)同步到 Elasticsearch,查詢操作則直接從 Elasticsearch 獲取數(shù)據(jù)。這就是典型的異構(gòu)數(shù)據(jù)源 CQRS 模式。

圖片圖片

注意:異構(gòu)數(shù)據(jù)源不一定是兩種不同的數(shù)據(jù)中間件。例如,即使兩個(gè)數(shù)據(jù)源都是 MySQL,只要表結(jié)構(gòu)不同,也可以被視為異構(gòu)數(shù)據(jù)源。

部署方式

在實(shí)際應(yīng)用中,CQRS 架構(gòu)可以根據(jù)項(xiàng)目需求靈活部署:

  1. 同一應(yīng)用內(nèi)實(shí)現(xiàn):命令服務(wù)和查詢服務(wù)共存于同一個(gè)應(yīng)用中,適用于簡(jiǎn)單場(chǎng)景。
  2. 物理隔離部署:將命令服務(wù)和查詢服務(wù)拆分為不同的應(yīng)用,獨(dú)立部署,適用于高并發(fā)、大規(guī)模業(yè)務(wù)場(chǎng)景。

在Dailymart改造CQRS模式

以訂單模塊為例,看看如何實(shí)踐CQRS模式,以下為實(shí)踐步驟:

1、拆分應(yīng)用服務(wù)

將原應(yīng)用服務(wù)接口OrderService拆成兩個(gè)服務(wù),分別是OrderCommandService 和 OrderQueryService,將分頁接口定義遷移到OrderQueryService中,OrderCommandService 中只包含聚合的加載和更新操作。

public interface OrderQueryService {

    /**
     * 分頁查詢
     * @author jam
     * @date 2024/12/17 14:56
     */
    PageResponse<OrderRespDTO> findListByUserId(OrderPageQueryDTO pageRequest);

}

public interface OrderCommandService {
    /**
     * 創(chuàng)建訂單
     * @param orderCreateRequest 創(chuàng)建訂單參數(shù)
     */
    String createOrder(OrderCreateRequest orderCreateRequest);

    /**
     * 訂單發(fā)貨
     */
    String ship(String orderSn);

    /**
     * 加載訂單詳情
     */
    OrderRespDTO getOrderBySn(String orderSn);
}

2、實(shí)現(xiàn)查詢服務(wù): 使用 MyBatis-Plus 進(jìn)行分頁查詢并轉(zhuǎn)換 DO 到 DTO 

在查詢服務(wù)中,我們直接使用 MyBatis-Plus 提供的 selectPage 方法進(jìn)行分頁查詢,并通過 OrikaUtils.convertList 方法將數(shù)據(jù)庫對(duì)象DO轉(zhuǎn)換為DTO。

@Service
@Slf4j
public class OrderQueryServiceImpl implements OrderQueryService {

    @Resource
    private OrderMapper orderMapper;


    @Override
    public PageResponse<OrderRespDTO> findListByUserId(OrderPageQueryDTO pageRequest) {

        Page<OrderDO> page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());

        LambdaQueryWrapper<OrderDO> queryWrapper = Wrappers.lambdaQuery(OrderDO.class).eq(OrderDO::getCustomerId, pageRequest.getCustomerId());

        Page<OrderDO> selectedPage = orderMapper.selectPage(page, queryWrapper);

        List<OrderDO> records = selectedPage.getRecords();

        Map<String,String> refMap = new HashMap<>(1);
        //map key 放置 源屬性,value 放置 目標(biāo)屬性
        refMap.put("orderId","id");

        //Do -> Dto
        List<OrderRespDTO> pageList = OrikaUtils.convertList(records, OrderRespDTO.class,refMap);

        return new PageResponse<>(pageRequest.getCurrent(), pageRequest.getSize(), selectedPage.getTotal(), pageList);

    }

}

3、刪除倉儲(chǔ)接口中關(guān)于分頁查詢的接口

Repository的職責(zé)應(yīng)集中在持久化和聚合的加載上。分頁查詢不應(yīng)包含在倉儲(chǔ)接口中。通過移除分頁查詢方法來簡(jiǎn)化倉儲(chǔ)接口的設(shè)計(jì),使其專注于聚合根的生命周期管理。

public interface OrderRepository extends Repository<TradeOrder, OrderId> {
    
    /**
     * 根據(jù)訂單編號(hào)查詢訂單
     * @param orderSn 訂單號(hào)
     * @return 訂單聚合
     */
    TradeOrder load(String orderSn);
    

}

4、修改訂單接口層的調(diào)用方式,分別使用不同的應(yīng)用服務(wù)完成業(yè)務(wù)操作。

@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Tag(name = "OrderController", description = "C端訂單模塊")
public class OrderController {
    private final OrderCommandService orderCommandService;

    private final OrderQueryService orderQueryService;

 ...
    
}

小結(jié)

本文詳細(xì)介紹了CQRS模式的基本概念及其實(shí)現(xiàn)方式,重點(diǎn)分析了在DailyMart項(xiàng)目中如何通過實(shí)踐CQRS架構(gòu)對(duì)訂單模塊進(jìn)行改造。希望通過本文的講解,能夠幫助你更好地理解CQRS模式的應(yīng)用場(chǎng)景、優(yōu)勢(shì)及實(shí)施細(xì)節(jié),提升系統(tǒng)架構(gòu)的可維護(hù)性和擴(kuò)展性。

責(zé)任編輯:武曉燕 來源: JAVA日知錄
相關(guān)推薦

2022-04-04 07:51:32

Web框架

2022-06-29 07:49:42

云存儲(chǔ)架構(gòu)DevOps

2020-06-15 08:06:25

ES數(shù)據(jù)

2019-04-26 13:01:16

ServiceMesh微服務(wù)架構(gòu)

2020-03-23 07:15:35

物聯(lián)網(wǎng)IOT物聯(lián)網(wǎng)技術(shù)

2021-05-06 07:53:21

MySQLFigure 8Raft

2021-07-16 06:56:50

邊緣計(jì)算分布式

2021-10-16 12:52:17

Builder模式生成器

2020-05-22 10:02:43

Python語言編程

2021-08-02 15:23:16

Windows 10Windows微軟

2024-12-26 09:20:51

2024-11-04 10:28:08

2021-05-11 10:56:07

DevOps開發(fā)工具

2011-11-30 15:28:32

在線協(xié)作系統(tǒng)

2019-04-03 16:24:02

電腦重啟Windows 10

2024-12-09 09:30:00

適配器模式設(shè)計(jì)模式代碼

2019-04-03 09:44:37

技術(shù)研發(fā)開發(fā)

2011-08-30 10:54:48

遠(yuǎn)程服務(wù)器服務(wù)器管理工具服務(wù)器虛擬化

2023-11-08 14:03:47

數(shù)據(jù)可視化數(shù)字化轉(zhuǎn)型

2014-09-28 10:28:59

Docker云計(jì)算
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)