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

DDD架構(gòu)下的防御式編程:五大關(guān)卡共同保障業(yè)務(wù)數(shù)據(jù)的有效性

開(kāi)發(fā) 架構(gòu)
一般情況下,在流程達(dá)到存儲(chǔ)引擎前,所有的驗(yàn)證規(guī)則必須全部通過(guò),盡量不要使用存儲(chǔ)引擎作為兜底方案。但有一種情況極為特殊,也就只有存儲(chǔ)引擎能夠優(yōu)雅的完成,那就是唯一鍵保護(hù)。

1. 規(guī)則驗(yàn)證是準(zhǔn)確性的基礎(chǔ)

規(guī)則驗(yàn)證是業(yè)務(wù)穩(wěn)定性的重要保障手段,通過(guò)規(guī)則驗(yàn)證,可以驗(yàn)證和確保系統(tǒng)或業(yè)務(wù)邏輯的正確性和合規(guī)性,避免潛在的錯(cuò)誤和問(wèn)題。而規(guī)則的遺漏往往會(huì)伴隨著線上bug的出現(xiàn)。

相信每個(gè)開(kāi)發(fā)人員都曾面對(duì)過(guò)以下情況:

  1. 未對(duì)入?yún)⑦M(jìn)行非空判斷,在執(zhí)行邏輯時(shí)導(dǎo)致空指針異常(NullPointerException,簡(jiǎn)稱NPE);
  2. 未正確驗(yàn)證用戶權(quán)限,導(dǎo)致未授權(quán)操作發(fā)生,普通用戶也能執(zhí)行該操作,最終產(chǎn)生安全問(wèn)題;
  3. 在數(shù)據(jù)被存儲(chǔ)到數(shù)據(jù)庫(kù)時(shí),沒(méi)有進(jìn)行完整性驗(yàn)證,導(dǎo)致無(wú)效數(shù)據(jù)被存儲(chǔ);
  4. 在業(yè)務(wù)邏輯中,未對(duì)可能拋出的異常進(jìn)行適當(dāng)?shù)奶幚?,?dǎo)致系統(tǒng)無(wú)法正常運(yùn)行;

可見(jiàn),驗(yàn)證對(duì)流程極為重要,不合理的輸入會(huì)導(dǎo)致嚴(yán)重的業(yè)務(wù)問(wèn)題。同時(shí)錯(cuò)誤數(shù)據(jù)的影響也比想象中的大得多:

  1. 可能會(huì)導(dǎo)致整個(gè)寫流程異常中斷;
  2. 錯(cuò)誤數(shù)據(jù)入庫(kù)后,會(huì)對(duì)所有的讀操作造成致命的傷害;
  3. 上游系統(tǒng)數(shù)據(jù)錯(cuò)誤,下游系統(tǒng)紛紛“崩潰”;

2. 防御式編程

如何避免上述情況的發(fā)生,答案就在 防御式編程。

防御式編程(Defensive Programming)是一種軟件開(kāi)發(fā)方法,目的是在代碼中預(yù)測(cè)可能出現(xiàn)的異常和錯(cuò)誤情況,并用適當(dāng)?shù)拇胧?duì)其進(jìn)行處理,從而提高軟件的健壯性和穩(wěn)定性。通過(guò)防御式編程,軟件開(kāi)發(fā)人員可以在軟件功能相對(duì)復(fù)雜的情況下,避免和減少由于程序錯(cuò)誤所導(dǎo)致的不可預(yù)測(cè)的行為和不良影響,并保障軟件在部署和運(yùn)行時(shí)的正確性和穩(wěn)定性,提高軟件可靠性和安全性。

防御式編程的核心思想是在代碼中盡量考慮一切可能出現(xiàn)的異常和錯(cuò)誤情況,并在代碼中針對(duì)這些異常和錯(cuò)誤情況做出相應(yīng)的處理。例如,可以使用異常捕獲機(jī)制處理可能出現(xiàn)的異常,充分利用代碼注釋和約束條件來(lái)規(guī)范輸入數(shù)據(jù),使用斷言(assert)來(lái)檢查代碼中的前置條件和后置條件等。

概念過(guò)于繁瑣,簡(jiǎn)單理解:防御式編程就是:

  1. 不要相信任何輸入,在正式使用前,必須保證參數(shù)的有效性;
  2. 不相信任何處理邏輯,在流程處理后,必須保證業(yè)務(wù)規(guī)則仍舊有效;

對(duì)輸入?yún)?shù)保持懷疑,對(duì)業(yè)務(wù)執(zhí)行的前提條件保存懷疑,對(duì)業(yè)務(wù)執(zhí)行結(jié)果保持懷疑,將極大的提升系統(tǒng)的準(zhǔn)確性!

3. 異常中斷還是返回值中斷?

在規(guī)則校驗(yàn)場(chǎng)景,優(yōu)先使用異常進(jìn)行流程中斷。

3.1. 異常中斷才是標(biāo)配

在沒(méi)有提供異常的編程語(yǔ)言中,我們只能使用特殊返回值來(lái)表示異常,這樣的設(shè)計(jì)會(huì)將正常流程和異常處理流程混在一起,讓語(yǔ)言失去了可讀性。比如在 C 中,通用會(huì)使用 -1 或 NULL 來(lái)表示異常情況,所以在調(diào)用函數(shù)第一件事便是判斷 result 是 NULL 或 -1,比如以下代碼:

void readFileAndPrintContent(const char* filename) {
    FILE* file = fopen(filename, "r");
    if (file == NULL) {
        // 文件無(wú)法打開(kāi),返回異常狀態(tài)
        fprintf(stderr, "Failed to open the file.\n");
        return;  // 直接返回,表示發(fā)生異常
    }

    char line[256];
    while (fgets(line, sizeof(line), file) != NULL) {
        printf("%s", line);
    }

    fclose(file);
}

在 Java 語(yǔ)言中,引入了完整的異常機(jī)制,以更好的處理異常情況,該機(jī)制有如下特點(diǎn):

  1. 邏輯分離,將正常處理和異常處理進(jìn)行分離。異常機(jī)制可以將錯(cuò)誤處理代碼從正常業(yè)務(wù)邏輯中分離出來(lái),使得代碼更加清晰和易讀。同時(shí),可以將異常處理代碼集中在一起,便于理解和維護(hù);
  2. 異常傳播和捕獲。當(dāng)異常在方法中被拋出時(shí),可以選擇在當(dāng)前方法中捕獲并處理異常,或者將異常繼續(xù)傳播給調(diào)用者,直到找到合適的異常處理器。這種靈活的異常傳播機(jī)制使得錯(cuò)誤可以被適當(dāng)?shù)靥幚恚粫?huì)造成程序的中斷;
  3. 異常信息傳遞。在異常對(duì)象中,Java 提供了豐富的信息用于描述異常的發(fā)生原因和上下文。包括異常類別、異常消息、異常發(fā)生的位置等。這些信息可以幫助開(kāi)發(fā)人員快速定位和修復(fù)異常,提高代碼的調(diào)試和維護(hù)效率;

在 Java 中異常處理變得簡(jiǎn)單且嚴(yán)謹(jǐn):

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileReadExample {
    public static void readFileAndPrintContent(String filename) throws IOException {
        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } 
    }

    public static void main(String[] args) {
        try {
            readFileAndPrintContent("example.txt");
        } catch (IOException e) {
            System.err.println("Exception occurred: " + e.getMessage());
            System.exit(-1);  // 返回錯(cuò)誤代碼 -1 表示發(fā)生異常
        }
    }
}

在日常業(yè)務(wù)開(kāi)發(fā)中,當(dāng)出現(xiàn)不符合業(yè)務(wù)預(yù)期時(shí),直接通過(guò)異常對(duì)流程進(jìn)行中斷即可。

3.2. 立即中斷還是階段中斷?

當(dāng)出現(xiàn)不符合預(yù)期情況時(shí),是直接拋出異常,還是完成整個(gè)階段在拋出異常?這個(gè)需要看業(yè)務(wù)場(chǎng)景!

參數(shù)驗(yàn)證場(chǎng)景,需要對(duì)所有不合法信息進(jìn)行收集,然后統(tǒng)一拋出異常,從而能夠讓用戶一目了然的看到所有問(wèn)題信息,以方便進(jìn)行統(tǒng)一修改。

而在業(yè)務(wù)場(chǎng)景,不符合規(guī)則時(shí),需要直接進(jìn)行異常中斷,避免對(duì)后續(xù)流程造成破壞。

4. 標(biāo)準(zhǔn)寫流程中的規(guī)則驗(yàn)證

使用 DDD 進(jìn)行開(kāi)發(fā)時(shí),一個(gè)標(biāo)準(zhǔn)的寫流程包括:

圖片圖片

其中,涉及5大類規(guī)則驗(yàn)證,如:

  1. 參數(shù)校驗(yàn)。對(duì)入?yún)⑦M(jìn)行基本校驗(yàn),比如是否為null、類型是否匹配等;
  2. 業(yè)務(wù)校驗(yàn)。特指對(duì)前置業(yè)務(wù)規(guī)則或資源的校驗(yàn),比如庫(kù)存是否足夠、商品狀態(tài)是否可售等;
  3. 狀態(tài)校驗(yàn)。特指聚合內(nèi)狀態(tài)校驗(yàn),核心為業(yè)務(wù)狀態(tài)機(jī),比如只有訂單狀態(tài)為待支付時(shí),才能進(jìn)行支付成功操作;
  4. 固定規(guī)則校驗(yàn)。如果聚合內(nèi)有固定規(guī)則,在進(jìn)行持久化操作前,需對(duì)規(guī)則進(jìn)行驗(yàn)證,比如 訂單支付金額 = 所有商品售賣價(jià)格總和 - 各類優(yōu)惠總和;
  5. 存儲(chǔ)引擎校驗(yàn)?;诖鎯?chǔ)引擎特性進(jìn)行的最后保障,比如在表中創(chuàng)建唯一索引,從而避免用戶的多次提交(冪等保護(hù));

4.1. 參數(shù)校驗(yàn)

這是最基礎(chǔ)的校驗(yàn),沒(méi)有太多的業(yè)務(wù)概念,只有簡(jiǎn)單的參數(shù)。其目的是 對(duì)數(shù)據(jù)格式進(jìn)行驗(yàn)證。

針對(duì)這種通用能力,優(yōu)先借助框架來(lái)完成,常用框架主要有:

  1. validation 框架。主要用于解決簡(jiǎn)單屬性的驗(yàn)證;
  2. Verifiable + AOP。主要用于多個(gè)屬性的驗(yàn)證;
4.1.1. Validation 框架

對(duì)于單屬性的驗(yàn)證,可以使用 hibernate validation 框架來(lái)實(shí)現(xiàn)。Hibernate Validation 是一個(gè)基于 Java Bean 驗(yàn)證規(guī)范(Java Bean Validation)的驗(yàn)證框架,它提供了一系列的特性來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)模型的驗(yàn)證和約束,其特性主要包括:

  1. 提供了一組驗(yàn)證注解,用于在數(shù)據(jù)模型的字段、方法參數(shù)、返回值等地方添加驗(yàn)證約束。例如,@NotNull 用于驗(yàn)證字段不能為空,@Email 用于驗(yàn)證郵箱格式,@Size 用于驗(yàn)證字符串長(zhǎng)度等;
  2. 提供了許多內(nèi)置的驗(yàn)證注解,用于實(shí)現(xiàn)常見(jiàn)的驗(yàn)證需求。例如,@NotBlank 用于驗(yàn)證字符串非空且必須包含至少一個(gè)非空白字符,@Pattern 用于驗(yàn)證字符串匹配指定的正則表達(dá)式,@Min  @Max 用于驗(yàn)證數(shù)字的最小值和最大值等;
  3. 除了使用內(nèi)置的驗(yàn)證注解外,Hibernate Validation 還允許開(kāi)發(fā)者通過(guò)自定義約束注解來(lái)定義和應(yīng)用自定義的驗(yàn)證規(guī)則。通過(guò)創(chuàng)建自定義的約束注解,可以實(shí)現(xiàn)更加靈活和符合業(yè)務(wù)需求的驗(yàn)證規(guī)則;
  4. 允許將多個(gè)驗(yàn)證約束分組,并在需要時(shí)對(duì)特定的驗(yàn)證組進(jìn)行驗(yàn)證。這樣可以根據(jù)具體的場(chǎng)景選擇性地執(zhí)行驗(yàn)證操作,從而實(shí)現(xiàn)更加細(xì)粒度的驗(yàn)證控制;
  5. 提供了驗(yàn)證器和驗(yàn)證上下文等核心類,用于執(zhí)行驗(yàn)證操作和獲取驗(yàn)證結(jié)果。驗(yàn)證器負(fù)責(zé)執(zhí)行驗(yàn)證操作,而驗(yàn)證上下文提供了豐富的方法來(lái)獲取驗(yàn)證結(jié)果、獲取驗(yàn)證錯(cuò)誤信息等;
  6. 允許通過(guò)指定驗(yàn)證注解的驗(yàn)證順序,來(lái)控制驗(yàn)證的執(zhí)行順序。這樣可以確保在需要按順序執(zhí)行驗(yàn)證約束時(shí),每個(gè)約束都會(huì)按照指定的順序進(jìn)行驗(yàn)證;
  7. 支持國(guó)際化,可以根據(jù)不同的語(yǔ)言環(huán)境為驗(yàn)證錯(cuò)誤消息提供多語(yǔ)言支持。開(kāi)發(fā)者可以定義并配置驗(yàn)證錯(cuò)誤消息的資源文件,從而實(shí)現(xiàn)跨語(yǔ)言的驗(yàn)證錯(cuò)誤消息;

特性非常多,我們最常用的就是在模型字段、方法參數(shù)、返回值增加相應(yīng)功能的注解,比如在 CreateOrderCmd 中增加相關(guān)驗(yàn)證注解,從而避免手寫代碼:

@Data
public class CreateOrderCmd {
    @NotNull
    private Long userId;

    @NotNull
    private Long productId;

    @NotNull
    @Min(1)
    private int count;
}
4.1.2. Verifiable +AOP

有些參數(shù)驗(yàn)證可能會(huì)比較復(fù)雜,需要對(duì)多個(gè)屬性進(jìn)行判斷,此時(shí) Validation 框架會(huì)顯得無(wú)能為力。

當(dāng)然,可以制定相應(yīng)規(guī)范,在參數(shù)封裝的類上統(tǒng)一提供一個(gè) validate 方法,并在進(jìn)入方法后使用參數(shù)前調(diào)用該方法。但,規(guī)范由人執(zhí)行難免發(fā)生遺留。所以,最佳方案是將其內(nèi)化到框架。如下圖所示:

圖片圖片

image

  1. 首先,定義一個(gè)接口 Verifiable,該接口只有一個(gè) validate 方法;
  2. 其次,定義一個(gè) ValidateIntercepter 類,基于前置攔截器對(duì)入?yún)⑦M(jìn)行判斷,如果實(shí)現(xiàn) Verifiable 接口,則自動(dòng)調(diào)用 validate 方法;
  3. 最后,基于 AOP 技術(shù)生成 Proxy,從而完成統(tǒng)一的參數(shù)校驗(yàn);

當(dāng)需要對(duì)多個(gè)參數(shù)進(jìn)行校驗(yàn)時(shí),只需要實(shí)現(xiàn) Verifiable 接口的 validate 方法即可,無(wú)需手工對(duì) validate 進(jìn)行調(diào)用。

4.2. 業(yè)務(wù)校驗(yàn)

業(yè)務(wù)校驗(yàn)是業(yè)務(wù)邏輯執(zhí)行的前置條件驗(yàn)證,包括外部校驗(yàn)和控制條件校驗(yàn)。

通常情況下,業(yè)務(wù)校驗(yàn)比較復(fù)雜,變化頻次也比較高,所以對(duì)擴(kuò)展性要求很高。但,業(yè)務(wù)規(guī)則本身比較獨(dú)立,相互之間沒(méi)有太多的依賴關(guān)系。為了更好的應(yīng)對(duì)邏輯擴(kuò)展,可以使用策略模型進(jìn)行設(shè)計(jì)。如下圖所示:

圖片圖片

4.2.1. 業(yè)務(wù)驗(yàn)證器

業(yè)務(wù)驗(yàn)證器就是策略模型中的策略接口。

核心代碼如下:

public interface BaseValidator<A> extends SmartComponent<A> {
    void validate(A a, ValidateErrorHandler validateErrorHandler);

    default void validate(A a){
        validate(a, ((name, code, msg) -> {
            throw new ValidateException(name, code, msg);
        }));
    }
}

該接口非常簡(jiǎn)單:

  1. 提供統(tǒng)一的 validate 方法定義;
  2. 繼承自 SmartComponent,可以通過(guò) boolean support(A a) 來(lái)驗(yàn)證該組件是否能被處理;
4.2.2. 共享數(shù)據(jù) Context

有了統(tǒng)一的策略接口后,需要使用 Context 模式對(duì)入?yún)⑦M(jìn)行管理。Context 可以是簡(jiǎn)單的數(shù)據(jù)容器,也可以是一個(gè)具有 LazyLoad 能力的加強(qiáng)容器,其核心功能就是在多個(gè)策略間實(shí)現(xiàn)數(shù)據(jù)的共享。

比如,在生單流程中的 CreateOrderContext 定義如下:

@Data
public class CreateOrderContext implements CreateOrderContext{

    private CreateOrderCmd cmd;

    @LazyLoadBy("#{@userRepository.getById(cmd.userId)}")
    private User user;

    @LazyLoadBy("#{@productRepository.getById(cmd.productId)}")
    private Product product;

    @LazyLoadBy("#{@addressRepository.getDefaultAddressByUserId(user.id)}")
    private Address defAddress;

    @LazyLoadBy("#{@stockRepository.getByProductId(product.id)}")
    private Stock stock;

    @LazyLoadBy("#{@priceService.getByUserAndProduct(user.id, product.id)}")
    private Price price;
}

其中 @LazyLoadBy 是一個(gè)功能加強(qiáng)注解,在第一次訪問(wèn)屬性的 getter 方法時(shí),將自動(dòng)觸發(fā)數(shù)據(jù)加載,并將加載的數(shù)據(jù)設(shè)置到屬性上,再第二次訪問(wèn)時(shí),直接從屬性上獲取所需數(shù)據(jù)。

【注】對(duì)該部分感興趣,可以學(xué)習(xí) 《Command\&Query Object 與 Context 模式》

4.2.3. 策略類管理

在有了策略接口 和 共享數(shù)據(jù) Context 后,接下來(lái)便是按照業(yè)務(wù)需求實(shí)現(xiàn)高內(nèi)聚低耦合的各種實(shí)現(xiàn)類。如下圖所示:

圖片圖片

這些組件如何進(jìn)行管理,詳見(jiàn)下圖:

圖片圖片

  1. 首先,在啟動(dòng)時(shí),所有的 BusinessValidator 實(shí)例會(huì)被 Spring 注入到 ValidateService 中的 validators 集合;
  2. 在調(diào)用 validateBusiness 函數(shù)時(shí),依次遍歷 validators 集合,找到能處理該 Context 的 validator 實(shí)例并執(zhí)行對(duì)應(yīng)的 validate 方法;

這樣做最大的好處便是,在驗(yàn)證組件中徹底實(shí)現(xiàn)“開(kāi)閉原則”:

  1. 增加新的業(yè)務(wù)邏輯時(shí),只需增加一個(gè)新的 Spring 組件,系統(tǒng)將自動(dòng)完成集成;
  2. 在修改某個(gè)業(yè)務(wù)校驗(yàn)時(shí),只會(huì)更改一個(gè)類,對(duì)其他校驗(yàn)沒(méi)有影響;

認(rèn)真思考后,可能會(huì)發(fā)現(xiàn):這其實(shí)是責(zé)任鏈模式的一種變形。但,由于實(shí)現(xiàn)非常簡(jiǎn)單,在 Spring 框架中多次使用。

4.3. 狀態(tài)校驗(yàn)

狀態(tài)校驗(yàn)又成前置狀態(tài)驗(yàn)證,是業(yè)務(wù)規(guī)則中最重要的一部分。

核心實(shí)體通常會(huì)有一個(gè)狀態(tài)屬性,狀態(tài)屬性的這些值共同組成一個(gè)標(biāo)準(zhǔn)的狀態(tài)機(jī)。如下圖所示:

圖片圖片

這是一個(gè)訂單實(shí)體的狀態(tài)機(jī),定義了各狀態(tài)間的轉(zhuǎn)換關(guān)系,這是領(lǐng)域設(shè)計(jì)中最為重要的一部分。當(dāng)發(fā)生業(yè)務(wù)動(dòng)作時(shí),第一件事不是修改業(yè)務(wù)狀態(tài),而是判斷當(dāng)前狀態(tài)下是否可以進(jìn)行該操作。

比如,支付成功的核心業(yè)務(wù):

public void paySuccess(PayByIdSuccessCommand paySuccessCommand){
  if (getStatus() != OrderStatus.CREATED){
       throw new OrderStatusNotMatch();
   }

   this.setStatus(OrderStatus.PAID);

   PayRecord payRecord = PayRecord.create(paySuccessCommand.getChanel(), paySuccessCommand.getPrice());
   this.payRecords.add(payRecord);

   OrderPaySuccessEvent event = new OrderPaySuccessEvent(this);
   this.events.add(event);
}

在進(jìn)入邏輯處理前,先對(duì)狀態(tài)進(jìn)行判斷,只有 “已創(chuàng)建” 才能接收 支付成功操作,并將狀態(tài)轉(zhuǎn)換為 “已支付”。

4.4. 固定規(guī)則校驗(yàn)

固定規(guī)則校驗(yàn)使用場(chǎng)景不多,但其威力巨大,可以從根源上解決邏輯錯(cuò)誤。

在訂單實(shí)體上存在大量的金額操作,比如:

  1. 優(yōu)惠券。用戶使用優(yōu)惠券后,用戶支付金額需減去優(yōu)惠金額,同時(shí)優(yōu)惠金額也會(huì)均攤到不同的訂單項(xiàng)上;
  2. 優(yōu)惠活動(dòng)。和優(yōu)惠券對(duì)訂單的影響基本一致,但場(chǎng)景會(huì)更加復(fù)雜;
  3. 優(yōu)惠疊加。優(yōu)惠券和優(yōu)惠活動(dòng)一起使用,共同對(duì)訂單進(jìn)行修改;
  4. 手工改價(jià)。商家與用戶協(xié)商一致后,商家可以在后臺(tái)對(duì)訂單的金額進(jìn)行修改;

訂單金額發(fā)生變化后,更新字段很多,但無(wú)論如何變化都需要滿足一個(gè)公式:支付金額 = 售賣金額總和 - 優(yōu)惠金額總和。

我們可以基于這個(gè)公式,在業(yè)務(wù)操作之后、數(shù)據(jù)庫(kù)更新之前對(duì)規(guī)則進(jìn)行校驗(yàn),一旦規(guī)則不滿足則說(shuō)明處理邏輯出現(xiàn)問(wèn)題,直接拋出異常中斷處理流程。

4.4.1. JPA 支持

JPA 支持在數(shù)據(jù)保存或更新前對(duì)業(yè)務(wù)方法進(jìn)行回調(diào)。

我們可以使用 回調(diào)注解 或 實(shí)體監(jiān)聽(tīng)器 完成業(yè)務(wù)回調(diào)。

@PreUpdate
@PrePersist
public void checkBizRule(){
    // 進(jìn)行業(yè)務(wù)校驗(yàn)
}

checkBizRule 方法上增加 @PreUpdate 和 @PrePersist,在保存數(shù)據(jù)庫(kù)或更新數(shù)據(jù)庫(kù)之前,框架自動(dòng)對(duì) chekBizRule 方法進(jìn)行回調(diào),當(dāng)方法拋出異常,處理流程被強(qiáng)制中斷。

也可以使用 實(shí)體監(jiān)聽(tīng)器 進(jìn)行處理,如下例所示:

// 首先,定義一個(gè) OrderListenrer
public class OrderListener {
    @PrePersist
    public void preCreate(Order order) {
        order.checkBiz();
    }

    @PostPersist
    public void postCreate(Order order) {
        order.checkBiz();
    }
}

// 在 Order 實(shí)體上添加相關(guān)配置
@Data
@Entity
@Table
@Setter(AccessLevel.PRIVATE)
// 配置 OrderListener
@EntityListeners(OrderListener.class)
public class Order implements AggRoot<Long> {
    // 省略部分非關(guān)鍵代碼
    public void checkBizRule(){
        // 進(jìn)行業(yè)務(wù)校驗(yàn)
    }
}
4.4.2. MyBatis 支持

MyBatis 對(duì)實(shí)體的生命周期支持并沒(méi)有 JPA 中那么強(qiáng)大,但通過(guò) Intercepter 仍舊能實(shí)現(xiàn)該功能。具體操作如下:

首先,自定義 Intercepter,判斷參數(shù)并調(diào)用規(guī)則校驗(yàn)方法:

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class EntityInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement) args[0];
        Object parameter = args[1];

        // 在這里可以對(duì)參數(shù)進(jìn)行判斷,并執(zhí)行相應(yīng)的操作
        if (parameter instanceof Order) {
            Order order = (Order) parameter;
            order.checkBizRule();
        }

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以在這里設(shè)置一些配置參數(shù)
    }
}

然后,在 mybatis-config.xml 配置文件中增加 Intercepter 的配置,具體如下:

<configuration>
    <!-- 其他配置 -->

    <plugins>
        <plugin interceptor="com.example.EntityInterceptor"/>
    </plugins>
</configuration>
4.4.3. 業(yè)務(wù)框架擴(kuò)展

Lego 框架對(duì)標(biāo)準(zhǔn) Command 處理流程進(jìn)行封裝,流程中對(duì)固定規(guī)則校驗(yàn)進(jìn)行了支持。如下圖所示:

圖片圖片

image

在標(biāo)準(zhǔn)寫流程中的固定規(guī)則校驗(yàn)階段會(huì)自動(dòng)調(diào)用 ValidateService 中的 validateRule,整體結(jié)構(gòu)和 業(yè)務(wù)校驗(yàn)基本一致,在這里就不在贅述。其中:

  1. 存在一個(gè)默認(rèn)實(shí)現(xiàn) AggBasedRuleValidator,可以通過(guò)重寫聚合根上的 validate 方法來(lái)實(shí)現(xiàn) JPA 和 MyBatis 同樣的效果;
  2. 也可以自定義自己的 RuleValidator,將實(shí)現(xiàn)類注入到 Spring 容器即可完成與業(yè)務(wù)流程的集成;

4.5. 存儲(chǔ)引擎校驗(yàn)

存儲(chǔ)引擎提供了非常豐富的數(shù)據(jù)校驗(yàn),比如 Not Null,Length、Unique 等;

一般情況下,在流程達(dá)到存儲(chǔ)引擎前,所有的驗(yàn)證規(guī)則必須全部通過(guò),盡量不要使用存儲(chǔ)引擎作為兜底方案。但有一種情況極為特殊,也就只有存儲(chǔ)引擎能夠優(yōu)雅的完成,那就是唯一鍵保護(hù)。

比如,在需要冪等保護(hù)時(shí),我們通常將冪等鍵設(shè)置為唯一索引,從而保證不會(huì)出現(xiàn)重復(fù)提交的情況。

5. 小結(jié)

為了保證臟數(shù)據(jù)(不符合業(yè)務(wù)預(yù)期的數(shù)據(jù))不會(huì)進(jìn)入到系統(tǒng),我們將“防御式編程”思想用到了極致,在一個(gè)標(biāo)準(zhǔn)的寫流程中共設(shè)立了5項(xiàng)關(guān)卡,從多維度多視角對(duì)數(shù)據(jù)進(jìn)行保障:

  1. 參數(shù)校驗(yàn)。不要相信任何的輸入信息,需要對(duì)系統(tǒng)輸入進(jìn)行嚴(yán)格校驗(yàn);
  2. 業(yè)務(wù)校驗(yàn)。業(yè)務(wù)操作往往會(huì)依賴一些前置條件,這些前置條件累加在一起甚至比核心操作還要復(fù)雜,如何提供相互隔離且可擴(kuò)展的設(shè)計(jì)便成了這個(gè)階段的核心;
  3. 狀態(tài)校驗(yàn)。對(duì)狀態(tài)機(jī)進(jìn)行保護(hù),任何操作都存在前置狀態(tài),在非法狀態(tài)下不允許執(zhí)行,這就是這個(gè)階段要解決的問(wèn)題;
  4. 固定規(guī)則校驗(yàn)。當(dāng)上層業(yè)務(wù)變化多且復(fù)雜時(shí),可能會(huì)對(duì)某些固定規(guī)則造成破壞,所以,在業(yè)務(wù)操作完成后、數(shù)據(jù)操作之前,可以再次對(duì)固定規(guī)則進(jìn)行驗(yàn)證;
  5. 存儲(chǔ)引擎校驗(yàn)。校驗(yàn)優(yōu)先在Java 代碼中完成,不要將存儲(chǔ)引擎校驗(yàn)作為常態(tài)校驗(yàn)。但,在唯一性保障方面,存儲(chǔ)引擎是最簡(jiǎn)單最有效的策略;

5大關(guān)卡共同發(fā)力才能真正保障業(yè)務(wù)數(shù)據(jù)的有效性。

責(zé)任編輯:武曉燕 來(lái)源: geekhalo
相關(guān)推薦

2023-12-04 07:41:58

架構(gòu)防御式編程

2021-02-09 09:48:43

AR技術(shù)人工智能

2016-02-24 16:52:46

企業(yè)IT方案

2016-07-26 11:21:53

2017-10-12 13:07:36

數(shù)據(jù)中心企業(yè)網(wǎng)絡(luò)

2023-09-20 17:20:46

增強(qiáng)現(xiàn)實(shí)ARVR

2020-05-13 13:09:21

微隔離網(wǎng)絡(luò)攻擊網(wǎng)絡(luò)安全

2015-12-18 16:57:06

2017-01-15 18:15:23

2011-05-27 15:16:37

網(wǎng)站跳出率

2014-12-18 09:43:50

云計(jì)算云計(jì)算技術(shù)

2022-01-24 08:00:00

元宇宙數(shù)字環(huán)境技術(shù)

2012-12-26 14:40:05

IT運(yùn)維企業(yè)管理

2012-12-27 08:57:30

CA TechnoloIT管理IT運(yùn)營(yíng)

2010-07-14 17:03:52

編程語(yǔ)言

2019-09-18 20:28:26

大數(shù)據(jù)數(shù)據(jù)處理數(shù)據(jù)采集

2023-07-03 09:49:49

2022-04-15 10:02:42

網(wǎng)絡(luò)安全

2021-03-08 10:48:04

AI

2013-06-04 09:17:11

云計(jì)算過(guò)渡關(guān)鍵步驟
點(diǎn)贊
收藏

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