日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
DDD 對(duì)決:事務(wù)腳本 vs. 領(lǐng)域模型,哪個(gè)才是業(yè)務(wù)優(yōu)化的終極方案?

在 CQRS 架構(gòu)篇提到,由于 Command 和 Query 內(nèi)部驅(qū)動(dòng)力完全不同,需要在架構(gòu)層就進(jìn)行分離,但其中有個(gè)一個(gè)原則極為重要:

站在用戶的角度思考問題,與客戶深入溝通,找到通城網(wǎng)站設(shè)計(jì)與通城網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:網(wǎng)站建設(shè)、成都網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、國際域名空間、雅安服務(wù)器托管、企業(yè)郵箱。業(yè)務(wù)覆蓋通城地區(qū)。

  1. “讀”再復(fù)雜也是簡單;
  2. “寫”再簡單也是復(fù)雜;

可見 Command 遠(yuǎn)比 Query 棘手的多,其中最關(guān)鍵的便是使用哪種模式來承載業(yè)務(wù)?

最常見的業(yè)務(wù)承載模式有:

  1. 事務(wù)腳本。
  2. 領(lǐng)域模型。

1. 事務(wù)腳本 與 領(lǐng)域模型

事務(wù)腳本 和 領(lǐng)域模型 都是承載業(yè)務(wù)的不同模型,都有其適合的場(chǎng)景,沒有絕對(duì)的對(duì)和錯(cuò)。核心的決策依據(jù)只有一個(gè):選擇最合適的業(yè)務(wù)場(chǎng)景即可。

簡單且直觀的對(duì)兩者進(jìn)行區(qū)分:

  1. 事務(wù)腳本,門檻低上手快,適合簡單的業(yè)務(wù)場(chǎng)景,比如資訊、博客等;
  2. 領(lǐng)域模型,門檻很高,適合處理復(fù)雜的業(yè)務(wù)場(chǎng)景,比如電商、銀行、電信等;

大家最常聽說也是最反感的便是:被別人稱為 CRUD boy,更多時(shí)候說的便是 事務(wù)腳本。

1.1. 事務(wù)腳本

事務(wù)腳本(Transaction Script)是一種應(yīng)用程序架構(gòu)模式,主要用于處理簡單的業(yè)務(wù)場(chǎng)景。它將業(yè)務(wù)邏輯和數(shù)據(jù)庫訪問緊密耦合在一起,以便實(shí)現(xiàn)對(duì)數(shù)據(jù)的操作。

事務(wù)腳本,將整個(gè)業(yè)務(wù)邏輯封裝在一個(gè)事務(wù)中,借助數(shù)據(jù)庫事務(wù)來滿足業(yè)務(wù)操作的 ACID 特性。通過將邏輯和事務(wù)封裝在一起,從而簡化應(yīng)用程序的處理和開發(fā)。

下圖是基于事務(wù)腳本的生單流程:

圖片

簡單描述就是:將“腳本”(SQL)進(jìn)行打包,然后放在一個(gè)“事務(wù)”中運(yùn)行。這也就是“事務(wù)腳本”命名的由來。

接下來,看一個(gè)訂單改價(jià)流程:

圖片

和生單流程基本一致,在此不做過多介紹。

1.2. 領(lǐng)域模型

領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain-Driven Design,DDD)是應(yīng)對(duì)復(fù)雜業(yè)務(wù)場(chǎng)景的利器,它是對(duì)業(yè)務(wù)領(lǐng)域中的關(guān)鍵概念和業(yè)務(wù)規(guī)則的抽象。領(lǐng)域模型是一個(gè)對(duì)象模型,它主要描述各領(lǐng)域?qū)ο笾g的關(guān)系和行為。

和事務(wù)腳本不同,領(lǐng)域模型使用對(duì)象來承載業(yè)務(wù)邏輯,領(lǐng)域模型的設(shè)計(jì)基于業(yè)務(wù)領(lǐng)域知識(shí),強(qiáng)調(diào)領(lǐng)域?qū)<业膮⑴c,以提高軟件系統(tǒng)的質(zhì)量和開發(fā)效率。

下圖是基于領(lǐng)域模型的生單流程:

圖片

簡單描述就是:核心業(yè)務(wù)邏輯全部由對(duì)象實(shí)現(xiàn)(addItems方法),數(shù)據(jù)庫僅做數(shù)據(jù)存儲(chǔ)。

接下來,看下基于DDD的訂單改價(jià)流程:

圖片

和生單流程基本一致,核心邏輯由 Order 的 modify price 實(shí)現(xiàn)。

相比之下,領(lǐng)域模型就復(fù)雜太多,它由多個(gè)實(shí)體 (Entity)、值對(duì)象 (Value Object)、聚合 (Aggregate)、領(lǐng)域服務(wù) (Domain Service)、工廠 (Factory) 等組成,它們共同構(gòu)成了領(lǐng)域?qū)ο竽P?。在模型中,?shí)體和值對(duì)象表示業(yè)務(wù)中的實(shí)際對(duì)象,聚合是由多個(gè)高內(nèi)聚實(shí)體和值對(duì)象形成的組合提,領(lǐng)域服務(wù)表示不屬于任何一個(gè)實(shí)體或值對(duì)象的操作,工廠則用于創(chuàng)建復(fù)雜的對(duì)象,比如實(shí)體和值對(duì)象等。

1.3. 區(qū)別

兩者都是承載業(yè)務(wù)邏輯的架構(gòu),但區(qū)別巨大:

  1. 事務(wù)腳本是以流程為中心的設(shè)計(jì)方法,在數(shù)據(jù)庫層面執(zhí)行指令,簡化數(shù)據(jù)處理的過程;DDD 是以領(lǐng)域?qū)ο鬄橹行牡脑O(shè)計(jì)方法,旨在更好地理解和解決業(yè)務(wù)問題。
  2. 事務(wù)腳本以技術(shù)和流程為重點(diǎn),以技術(shù)為中心,以代碼實(shí)現(xiàn)為核心,關(guān)注數(shù)據(jù)處理問題;DDD 則強(qiáng)調(diào)模型驅(qū)動(dòng)開發(fā),以業(yè)務(wù)為中心,以領(lǐng)域模型為核心,關(guān)注業(yè)務(wù)邏輯,并以此為基礎(chǔ)進(jìn)行技術(shù)實(shí)現(xiàn)。
  3. 事務(wù)腳本很容易造成代碼的累積,難以維護(hù);DDD 能夠幫助開發(fā)人員找到領(lǐng)域的本質(zhì)(深層模型),并以此為核心,從而形成統(tǒng)一的、易于維護(hù)的架構(gòu)。

除此之外,DDD 還有很多的特點(diǎn),比如:

  1. 標(biāo)準(zhǔn)化。DDD 由一組嚴(yán)謹(jǐn)?shù)囊?guī)范組成,有完整的理論基礎(chǔ),可以實(shí)現(xiàn)落地過程的標(biāo)準(zhǔn)化;
  2. 設(shè)計(jì)模型。大家在日常工作中很少使用設(shè)計(jì)模型的根因在于:缺乏應(yīng)用場(chǎng)景。當(dāng)你處于“過程式”的開發(fā)模式下,只能產(chǎn)出面條代碼;只有面對(duì)“面對(duì)對(duì)象”場(chǎng)景,才能落地設(shè)計(jì)模式,提升抽象能力;
  3. 降維打擊。DDD 是從業(yè)務(wù)需求出發(fā),將業(yè)務(wù)概念轉(zhuǎn)化為對(duì)象模型,最后通過技術(shù)進(jìn)行落地。這本身就是一種自上而下的設(shè)計(jì)方式,聚焦于業(yè)務(wù),解決真實(shí)問題;

2. 實(shí)戰(zhàn)體驗(yàn)

對(duì)于程序員來說,文字顯得不夠直觀,在此我們通過代碼來體驗(yàn)下兩者的不同。

為了更好的體現(xiàn)兩者的區(qū)別,將會(huì)從兩個(gè)場(chǎng)景進(jìn)行對(duì)比:

  1. 創(chuàng)建場(chǎng)景。圍繞電商下單流程進(jìn)行說明。
  2. 更新場(chǎng)景。以電商訂單改價(jià)流程為基礎(chǔ)進(jìn)行說明。

在日常開發(fā)中,物理刪除場(chǎng)景用的非常少,甚至很多公司都明令禁止使用“delete”語句。通常使用 “邏輯刪除” 替代,它可歸屬為標(biāo)準(zhǔn)的更新場(chǎng)景,在此暫不對(duì)比 物理刪除場(chǎng)景。

2.1. 創(chuàng)建場(chǎng)景:下單

在電商中,一個(gè)標(biāo)準(zhǔn)的下單需求主要包括:

  1. 對(duì)商品庫存進(jìn)行校驗(yàn),避免出現(xiàn)超賣的情況;
  2. 對(duì)商品庫存進(jìn)行鎖定,如果支付成功則直接扣減鎖定的庫存;如果支付失敗,則對(duì)鎖定的庫存進(jìn)行歸還;
  3. 為每一種購買的商品生成一個(gè)訂單項(xiàng)(OrderItem),記錄商品單價(jià)、購買數(shù)量、需付總價(jià)、應(yīng)付金額等;
  4. 為每一筆下單生成一個(gè)訂單(Order),記錄用戶、地址、支付金額、訂單狀態(tài)等;
2.1.1. 基于事務(wù)腳本的下單

核心代碼如下:

@Transactional
public void createOrder(CreateOrderCommand createOrderCommand) {
    // 1. 庫存校驗(yàn)
    for (OrderItemDTO itemDTO : createOrderCommand.getItems()) {
        Integer stock = inventoryMapper.getStock(itemDTO.getProductId());
        if (stock < itemDTO.getQuantity()) {
            throw new IllegalStateException("庫存不足");
        }
    }
    // 2. 鎖定庫存
    for (OrderItemDTO itemDTO : createOrderCommand.getItems()) {
        inventoryMapper.lockStock(itemDTO.getProductId(), itemDTO.getQuantity());
    }

    // 3. 生成訂單項(xiàng)
    List items = createOrderCommand.getItems().stream()
            .map(OrderItem::create)
            .collect(Collectors.toList());
    orderItemMapper.createOrderItems(items);

    // 4. 生成訂單
    Long totalPrice = items.stream()
            .mapToLong(OrderItem::getPrice)
            .sum();

    Order order = new Order(createOrderCommand.getUserId(),  totalPrice, OrderStatus.CREATED);
    orderMapper.createOrder(order);

}

事務(wù)腳本與需求所需操作流程完全一致,簡單來說就是使用“編程語言”對(duì)需求進(jìn)行了翻譯。

2.1.2. 基于 DDD 的下單

核心代碼如下:

public void createOrder(CreateOrderCommand createOrderCommand) {
    // 1. 檢查庫存,如果足夠則進(jìn)行鎖定;如果不夠,則拋出異常
    this.inventoryService.checkIsEnoughAndLock(createOrderCommand.getItems());

    // 2. 創(chuàng)建 Order 聚合,此處使用靜態(tài)工廠創(chuàng)建復(fù)雜的 Order 對(duì)象
    Order order = Order.create(createOrderCommand);

    // 3. 保存 Order 聚合, @Transactional 在OrderRepository上
    this.orderRepository.save(order);
}

public class Order {
    private Long id;
    private Long userId;
    private Long totalSellingPrice = 0L;
    private Long totalPrice = 0L;
    private OrderStatus status;
    private List orderItems = new ArrayList<>();

    // 避免外部調(diào)用
    private Order(Long userId) {
        this.userId = userId;
    }

    // 靜態(tài)工廠,封裝復(fù)雜的 Order 創(chuàng)建邏輯,并保障創(chuàng)建的 Order 對(duì)象是有效的
    public static Order create(CreateOrderCommand createOrderCommand) {
        Order order = new Order(createOrderCommand.getUserId());
        order.addItems(createOrderCommand.getItems());
        order.init();
        return order;
    }

    // 添加 OrderItem,并計(jì)算總金額
    private void addItems(List items) {
        if (!CollectionUtils.isEmpty(items)){
            items.forEach(item ->{
                OrderItem orderItem = OrderItem.create(item);
                this.orderItems.add(orderItem);
                this.totalPrice += item.getPrice();
            });
        }
        this.totalPrice = totalSellingPrice;
    }

    // 設(shè)置狀態(tài)完成對(duì)象的初始化
    private void init() {
        this.status = OrderStatus.CREATED;
    }
}

和事務(wù)腳本相比,由以下幾點(diǎn)不同:

  1. 應(yīng)用服務(wù)中的 createOrder 方法內(nèi)容非常簡單,可以看做是模版代碼,變化的可能性非常小,可以對(duì)其進(jìn)行進(jìn)一步的封裝;
  2. 核心邏輯全部在 Order 聚合根中,通過靜態(tài)方法 create 完成 Order 對(duì)象的創(chuàng)建,業(yè)務(wù)邏輯非常集中,形成了擁有屬性和行為的“富對(duì)象”;
  3. 數(shù)據(jù)操作與邏輯解耦,最后一步操作 orderRepository#save 方法 完成內(nèi)存對(duì)象向DB數(shù)據(jù)的同步,其他部分均不涉及基礎(chǔ)設(shè)施;

2.2. 更新場(chǎng)景:訂單改價(jià)

在電商中,訂單改價(jià)主要包括:

  1. 修改訂單項(xiàng)價(jià)格(OrderItem),根據(jù)商品要支付金額對(duì)新價(jià)格按比例進(jìn)行均攤;
  2. 修改訂單價(jià)格(Order),修改訂單的支付金額;
2.2.1. 基于事務(wù)腳本的訂單改價(jià)

核心代碼如下:

@Transactional
public void changeOrderPrice(Long orderId, Long newPrice) {
    // 1. 校驗(yàn)金額
    if (newPrice <= 0) {
        throw new IllegalArgumentException("金額必須大于0");
    }

    // 校驗(yàn)訂單有效性
    Order order = orderMapper.getOrderById(orderId);
    if (order == null) {
        throw new IllegalArgumentException("訂單不存在");
    }

    // 2. 對(duì)訂單項(xiàng)價(jià)格進(jìn)行均攤
    allocateDiscount(order, order.getTotalPrice() - newPrice);

    // 3. 修改訂單價(jià)格
    order.setTotalPrice(newPrice);
    orderMapper.updateOrder(order);
}

public void allocateDiscount(Order order, Long discount) {
    if (discount == 0){
        return;
    }

    List items = this.orderItemMapper.getByOrderId(order.getId());
    Long totalAmount = order.getTotalPrice();
    Long allocatedDiscount = 0L;

    for (int i = 0; i < items.size(); i++) {
        OrderItem item = items.get(i);
        Long itemAmount = item.getSellingPrice();
        if (i != items.size() - 1) {
            // 按比例進(jìn)行均攤
            Long itemDiscount = itemAmount / totalAmount * discount;
            // 重新設(shè)置金額
            item.setPrice(item.getPrice() - itemDiscount);
            // 記錄累加金額
            allocatedDiscount += itemDiscount;
        }else {
            // 分?jǐn)傆嘞碌膬?yōu)惠金額到最后一個(gè)訂單
            Long lastItemDiscount = discount - allocatedDiscount;
            item.setPrice(item.getPrice() - lastItemDiscount);
        }
        // 更新數(shù)據(jù)庫
        this.orderItemMapper.update(item);
    }
}

和所描述的操作流程完全一致,成功使用“編程語言”完成了對(duì)需求的翻譯。

2.2.2. 基于 DDD 的訂單改價(jià)

核心代碼如下:

@Transactional
public void changeOrderPrice(Long orderId, Long newPrice) {
    // 1. 校驗(yàn)金額
    if (newPrice <= 0) {
        throw new IllegalArgumentException("金額必須大于0");
    }

    // 2. 獲取訂單聚合根
    Optional orderOpt = this.orderRepository.getById(orderId);

    Order order = orderOpt.orElseThrow(() -> new IllegalArgumentException("訂單不存在"));

    // 3. 修改價(jià)格
    order.changePrice(newPrice);

    // 4. 保存 Order 聚合
    this.orderRepository.save(order);

}

// Order 聚合根內(nèi)方法
public void changePrice(Long newPrice) {
    if (newPrice <= 0) {
        throw new IllegalArgumentException("金額必須大于0");
    }

    long discount = getTotalPrice() - newPrice;
    if (discount == 0){
        return;
    }
    // Item 均攤折扣
    discountForItem(discount);
    // Order 折扣
    discountForOrder(discount);
}

// Item 均攤
private void discountForItem(long discount) {
    Long totalAmount = getTotalPrice();
    Long allocatedDiscount = 0L;

    for (int i = 0; i < getOrderItems().size(); i++) {
        OrderItem item = getOrderItems().get(i);
        Long itemAmount = item.getSellingPrice();
        if (i != getOrderItems().size() - 1) {
            // 按比例進(jìn)行均攤
            Long itemDiscount = itemAmount / totalAmount * discount;
            // 重新設(shè)置金額
            item.setPrice(item.getPrice() - itemDiscount);
            // 記錄累加金額
            allocatedDiscount += itemDiscount;
        }else {
            // 分?jǐn)傆嘞碌膬?yōu)惠金額到最后一個(gè)訂單
            Long lastItemDiscount = discount - allocatedDiscount;
            item.setPrice(item.getPrice() - lastItemDiscount);
        }
    }
}
// Order 折扣
private void discountForOrder(long discount) {
    Long newTotalPrice = getTotalPrice() - discount;
    setTotalPrice(newTotalPrice);
}

和生單流程一樣:

  1. 應(yīng)用服務(wù)中的 changeOrderPrice 方法內(nèi)容非常簡單,標(biāo)準(zhǔn)的模版代碼,變化可能性非常小,需要對(duì)其進(jìn)行封裝;
  2. 核心邏輯全部在 Order 聚合根中,通過 changePrice 方法 完成改價(jià)邏輯,業(yè)務(wù)邏輯非常集中,形成擁有屬性和行為的“富對(duì)象”;
  3. 數(shù)據(jù)操作與邏輯解耦,最后一步操作 orderRepository#save 方法 完成內(nèi)存對(duì)象向DB數(shù)據(jù)的同步,其他部分均不涉及基礎(chǔ)設(shè)施;

2.3. 對(duì)比

看過這兩種風(fēng)格代碼有什么感覺?你可能會(huì)說代碼也沒少些什么,只是組織方式發(fā)生了變化。

確實(shí)是,只是組織方式發(fā)生變化,代碼一行都沒少。這是這點(diǎn)變化,帶來了革命的創(chuàng)新。

來看個(gè)新的場(chǎng)景:業(yè)務(wù)改價(jià)過于隨意,產(chǎn)品想增加一個(gè)環(huán)節(jié):填入改價(jià)金額后,先把每個(gè)訂單項(xiàng)的均攤價(jià)格展示出來,確認(rèn)無誤后在提交改價(jià)請(qǐng)求。

在不同的模式下,又該怎么解呢?

  1. 事務(wù)腳本模型下,大概率會(huì) copy 一個(gè)新的 changePrice,并在其基礎(chǔ)上進(jìn)行修改。這將產(chǎn)生代碼的冗余,比如原來 changePrice 方法存在bug,在修復(fù)時(shí)你需要修改多處,但往往只會(huì)想起一處;
  2. DDD模型下,你只需獲取 Order 聚合,然后調(diào)用 changePrice 方法,把均攤結(jié)果進(jìn)行返回,便可實(shí)現(xiàn)想要的結(jié)果;

3. 小節(jié)

事務(wù)腳本 和 領(lǐng)域模型 是承載業(yè)務(wù)的不同模式,都有各自適用的場(chǎng)景,需要根據(jù)自己的需求進(jìn)行選擇。

事務(wù)腳本:流程 + 數(shù)據(jù),在操作流程中對(duì)數(shù)據(jù)進(jìn)行操作;

領(lǐng)域模型:編排 + 模型 + 數(shù)據(jù),基于模型能力進(jìn)行編排,以完成業(yè)務(wù)操作;操作結(jié)果暫存于對(duì)象中,最后將其同步到數(shù)據(jù)庫;

DDD 靈活性還體現(xiàn)在:

  1. 流程組合,添加一個(gè)新功能,一次性完成生單和改價(jià)操作;
  2. 封裝不變,創(chuàng)建和更新主流程基本一致,可以對(duì)其進(jìn)行封裝,以統(tǒng)一操作;
  3. 應(yīng)用模式,邏輯由聚合對(duì)象承接,各種模式都可以拿來使用,比如設(shè)計(jì)模式、架構(gòu)模式、領(lǐng)域模式等;
  4. 局部標(biāo)準(zhǔn)化,基于 DDD 戰(zhàn)術(shù)體系,構(gòu)建標(biāo)準(zhǔn)的編程模型;

想了解 DDD 的精髓,讓我們進(jìn)入下一篇:初識(shí)極簡DDD。


文章名稱:DDD 對(duì)決:事務(wù)腳本 vs. 領(lǐng)域模型,哪個(gè)才是業(yè)務(wù)優(yōu)化的終極方案?
分享網(wǎng)址:http://www.dlmjj.cn/article/djshhph.html