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

微服務(wù)中,OpenFeign應(yīng)該這樣用才好!

開(kāi)發(fā) 架構(gòu)
本文深入研究了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)和微服務(wù)架構(gòu)中的兩個(gè)關(guān)鍵概念:防腐層(ACL)和遠(yuǎn)程調(diào)用的最佳實(shí)踐。在DDD中,我們學(xué)習(xí)了如何使用ACL來(lái)隔離外部依賴(lài),降低系統(tǒng)耦合度。

大家好,我是飄渺。在今天的DDD與微服務(wù)系列文章中,讓我們探討如何在DDD的分層架構(gòu)中調(diào)用第三方服務(wù)以及在微服務(wù)中使用OpenFeign的最佳實(shí)踐。

1. DDD中的防腐層

在應(yīng)用服務(wù)中,經(jīng)常需要調(diào)用外部服務(wù)接口來(lái)實(shí)現(xiàn)某些業(yè)務(wù)功能,這就在代碼層面引入了對(duì)外部系統(tǒng)的依賴(lài)。例如,下面這段轉(zhuǎn)賬的代碼邏輯需要調(diào)用外部接口服務(wù)RemoteService來(lái)獲取匯率。

public class TransferServiceImpl implements TransferService{
 private RemoteService remoteService;
 @Override
  public void transfer(Long sourceUserId, String targetUserId, BigDecimal targetAmount){
  //...
  ExchangeRateRemote exchangeRate = remoteService.getExchangeRate(
sourceAccount.getCurrency(), targetCurrency);
  BigDecimal rate = exchangeRate.getRate();
  }
   //...
}

這里可以看到,TransferService強(qiáng)烈依賴(lài)于RemoteService和ExchangeRateRemote對(duì)象。如果外部服務(wù)的方法或ExchangeRateRemote字段發(fā)生變化,都會(huì)影響到ApplicationService的代碼。當(dāng)有多個(gè)服務(wù)依賴(lài)此外部接口時(shí),遷移和改造的成本將會(huì)巨大。同時(shí),外部依賴(lài)的兜底、限流和熔斷策略也會(huì)受到影響。

在復(fù)雜系統(tǒng)中,我們應(yīng)該盡量避免自己的代碼因?yàn)橥獠肯到y(tǒng)的變化而修改。那么如何實(shí)現(xiàn)對(duì)外部系統(tǒng)的隔離呢?答案就是引入防腐層(Anti-Corruption Layer,簡(jiǎn)稱(chēng)ACL)。

1.1 什么是防腐層

在許多情況下,我們的系統(tǒng)需要依賴(lài)其他系統(tǒng),但被依賴(lài)的系統(tǒng)可能具有不合理的數(shù)據(jù)結(jié)構(gòu)、API、協(xié)議或技術(shù)實(shí)現(xiàn)。如果我們強(qiáng)烈依賴(lài)外部系統(tǒng),就會(huì)導(dǎo)致我們的系統(tǒng)受到“腐蝕”。在這種情況下,通過(guò)引入防腐層,可以有效地隔離外部依賴(lài)和內(nèi)部邏輯,無(wú)論外部如何變化,內(nèi)部代碼盡可能保持不變。

圖片圖片

防腐層不僅僅是一層簡(jiǎn)單的調(diào)用封裝,在實(shí)際開(kāi)發(fā)中,ACL可以提供更多強(qiáng)大的功能:

  • 適配器: 很多時(shí)候外部依賴(lài)的數(shù)據(jù)、接口和協(xié)議并不符合內(nèi)部規(guī)范,通過(guò)適配器模式,可以將數(shù)據(jù)轉(zhuǎn)化邏輯封裝到ACL內(nèi)部,降低對(duì)業(yè)務(wù)代碼的侵入。
  • 緩存: 對(duì)于頻繁調(diào)用且數(shù)據(jù)變更不頻繁的外部依賴(lài),通過(guò)在ACL里嵌入緩存邏輯,能夠有效的降低對(duì)于外部依賴(lài)的請(qǐng)求壓力。同時(shí),很多時(shí)候緩存邏輯是寫(xiě)在業(yè)務(wù)代碼里的,通過(guò)將緩存邏輯嵌入ACL,能夠降低業(yè)務(wù)代碼的復(fù)雜度。
  • 兜底: 如果外部依賴(lài)的穩(wěn)定性較差,提高系統(tǒng)穩(wěn)定性的策略之一是通過(guò)ACL充當(dāng)兜底,例如在外部依賴(lài)出問(wèn)題時(shí),返回最近一次成功的緩存或業(yè)務(wù)兜底數(shù)據(jù)。這種兜底邏輯通常復(fù)雜,如果散布在核心業(yè)務(wù)代碼中,會(huì)難以維護(hù)。通過(guò)集中在ACL中,更容易進(jìn)行測(cè)試和修改。
  • 易于測(cè)試: ACL的接口類(lèi)能夠很容易的實(shí)現(xiàn)Mock或Stub,以便于單元測(cè)試。
  • 功能開(kāi)關(guān): 有時(shí)候,我們希望在某些場(chǎng)景下啟用或禁用某個(gè)接口的功能,或者讓某個(gè)接口返回特定值。我們可以在ACL中配置功能開(kāi)關(guān),而不會(huì)影響真實(shí)的業(yè)務(wù)代碼。

1.2 如何實(shí)現(xiàn)防腐層

實(shí)現(xiàn)ACL防腐層的步驟如下:

  • 對(duì)于依賴(lài)的外部對(duì)象,我們提取所需的字段,并創(chuàng)建一個(gè)內(nèi)部所需的DTO類(lèi)。
  • 構(gòu)建一個(gè)新的Facade,在Facade中封裝調(diào)用鏈路,將外部類(lèi)轉(zhuǎn)化為內(nèi)部類(lèi)。Facade可以參考Repository的實(shí)現(xiàn)模式,將接口定義在領(lǐng)域?qū)?,而將?shí)現(xiàn)放在基礎(chǔ)設(shè)施層。
  • 在ApplicationService中依賴(lài)內(nèi)部的Facade對(duì)象。

具體實(shí)現(xiàn)如下:

// 自定義的內(nèi)部值類(lèi)
@Data
public class ExchangeRateDTO {
  ...
}

// 稅率Facade接口
public interface ExchangeRateFacade {
    ExchangeRateDTO getExchangeRate(String sourceCurrency, String targetCurrency);
}

// 稅率facade實(shí)現(xiàn)
@Service
public class ExchangeRateFacadeImpl implements ExchangeRateFacade {

    @Resource
    private RemoteService remoteService;

    @Override
    public ExchangeRateDTO getExchangeRate(String sourceCurrency, String targetCurrency) {
        ExchangeRateRemote exchangeRemote = remoteService.getExchangeRate(sourceCurrency, targetCurrency);
        if (exchangeRemote != null) {
            ExchangeRateDTO dto = new ExchangeRateDTO();
            dto.setXXX(exchangeRemote.getXXX());
            return dto;
        }
        return null;
    }
}

通過(guò)ACL改造后,我們的ApplicationService代碼如下:

public class TransferServiceImpl implements TransferService{
 private ExchangeRateFacade exchangeRateFacade;
 @Override
  public void transfer(Long sourceUserId, String targetUserId, BigDecimal targetAmount){
  ...
  ExchangeRateDTO exchangeRate = exchangeRateFacade.getExchangeRate(
sourceAccount.getCurrency(), targetCurrency);
  BigDecimal rate = exchangeRate.getRate();
   }
  ...
}

這樣,經(jīng)過(guò)ACL改造后,ApplicationService的代碼已不再直接依賴(lài)外部的類(lèi)和方法,而是依賴(lài)我們自己內(nèi)部定義的值類(lèi)和接口。如果未來(lái)外部服務(wù)發(fā)生任何變化,只需修改Facade類(lèi)和數(shù)據(jù)轉(zhuǎn)換邏輯,而不需要修改ApplicationService的邏輯。

1.3 小結(jié)

在沒(méi)有防腐層ACL的情況下,系統(tǒng)需要直接依賴(lài)外部對(duì)象和外部調(diào)用接口,調(diào)用邏輯如下:

圖片圖片

而有了防腐層ACL后,系統(tǒng)只需要依賴(lài)內(nèi)部的值類(lèi)和接口,調(diào)用邏輯如下:

圖片圖片

2. 微服務(wù)中的遠(yuǎn)程調(diào)用

在構(gòu)建微服務(wù)時(shí),我們經(jīng)常需要跨服務(wù)調(diào)用,例如在DailyMart系統(tǒng)中,購(gòu)物車(chē)服務(wù)需要調(diào)用商品服務(wù)以獲取商品詳細(xì)信息。理論上,我們可以遵循上述ACL的實(shí)現(xiàn)邏輯,在購(gòu)物車(chē)模塊創(chuàng)建Facade接口和內(nèi)部轉(zhuǎn)換類(lèi)。然而,在實(shí)際開(kāi)發(fā)中,由于是內(nèi)部系統(tǒng),差異性不太明顯,通常可以直接使用OpenFeign進(jìn)行遠(yuǎn)程調(diào)用,忽略Facade定義和內(nèi)部類(lèi)轉(zhuǎn)換的過(guò)程。

以下是在微服務(wù)中使用OpenFeign實(shí)現(xiàn)跨服務(wù)調(diào)用的過(guò)程:

  • 首先,在購(gòu)物車(chē)模塊的基礎(chǔ)設(shè)施層創(chuàng)建一個(gè)接口,并使用@FeignClient注解進(jìn)行標(biāo)注。
@FeignClient("product-service")
public interface ProductRemoteFacade {

    @GetMapping("/api/product/spu/{spuId}")
    Result<ProductRespDTO> getProductBySpuId(@PathVariable("spuId") Long spuId);

}

需要注意的是,我們?cè)谏唐贩?wù)中對(duì)外提供的商品詳情接口定義返回的是ProductRespDTO對(duì)象,但通過(guò)OpenFeign調(diào)用時(shí)返回的是Result對(duì)象。

@Operation(summary = "查詢(xún)商品詳情")
@Parameter(name = "spuId", description = "商品spuId")
@GetMapping("/api/product/spu/{spuId}")
public ProductRespDTO getProductBySpuId(@PathVariable("spuId") Long spuId) {
 return productRemoteFacade.getProductBySpuId(spuId);
}

這是因?yàn)樵谇拔闹校覀兌x了一個(gè)全局的包裝類(lèi)GlobalResponseBodyAdvice,會(huì)自動(dòng)給所有接口封裝返回對(duì)象Result。因此,在定義Feign接口時(shí),也需要使用Result對(duì)象來(lái)接收。如果對(duì)此邏輯不太清晰,建議參考第七章的內(nèi)容。

  • 在啟動(dòng)類(lèi)上添加@EnableFeignClient注解
@SpringBootApplication
@EnableFeignClients("com.jianzh5.dailymart.module.cart.infrastructure.acl")
public class CartApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(CartApplication.class, args);
    }
    
}
  • 在應(yīng)用服務(wù)中注入Feign接口并使用
@Override
public void getShoppingCartDetail(Long cartId) {
 ShoppingCart shoppingCart = shoppingCartRepository.find(new CartId(cartId));
  
 Result<ProductRespDTO> productRespResult = productRemoteFacade.getProductBySpuId(1L);
  
  // 從Result對(duì)象中獲取真實(shí)的業(yè)務(wù)對(duì)象
 if(productRespResult.getCode().equals("OK")){
  ProductRespDTO data = productRespResult.getData();
 }

}

如上所示,我們可以看到,每次調(diào)用Feign接口都需要解析Result對(duì)象以獲取真正的業(yè)務(wù)對(duì)象。這種代碼看起來(lái)有些冗余,是否有辦法去除呢?

2.1 自定義Feign的解碼器

這時(shí),我們可以通過(guò)重寫(xiě)Feign的解碼器來(lái)實(shí)現(xiàn),在解碼器中完成封裝對(duì)象的拆解。

@RequiredArgsConstructor
public class DailyMartResponseDecoder implements Decoder {

    private final ObjectMapper objectMapper;
    @Override
    public Object decode(Response response, Type type) throws IOException, FeignException {
        Result<?> result = objectMapper.readValue(response.body().asInputStream(), objectMapper.constructType(Result.class));
        if(result.getCode().equals("OK")){
            Object data = result.getData();
            JavaType javaType = TypeFactory.defaultInstance().constructType(type);
            return objectMapper.convertValue(data, javaType);
        }else{
            throw new RemoteException(result.getCode(), result.getMessage());
        }
    }
}

同時(shí),創(chuàng)建一個(gè)配置類(lèi),替換原生的解碼器。

@Bean
public Decoder feignDecoder(){
 return new DailyMartResponseDecoder(objectMapper);
}

這樣,在定義或調(diào)用OpenFeign接口時(shí),直接使用原生對(duì)象ProductRespDTO即可。

@FeignClient("product-service")
public interface ProductRemoteFacade {

    @GetMapping("/api/product/spu/{spuId}")
    ProductRespDTO getProductBySpuId(@PathVariable("spuId") Long spuId);

}

...

@Override
public void getShoppingCartDetail(Long cartId) {
 ShoppingCart shoppingCart = shoppingCartRepository.find(new CartId(cartId));

 ProductRespDTO productRespResult = productRemoteClient.getProductBySpuId(1L);

}

2.2 上游異常統(tǒng)一處理

在使用OpenFeign進(jìn)行遠(yuǎn)程調(diào)用時(shí),如果HTTP狀態(tài)碼為非200,OpenFeign會(huì)觸發(fā)異常解析并進(jìn)入默認(rèn)的異常解碼器feign.codec.ErrorDecoder,將業(yè)務(wù)異常包裝成FeignException。此時(shí),如果不做任何處理,調(diào)用時(shí)可以返回的消息會(huì)變成FeignException的消息體,如下所示:

圖片圖片

顯然,這個(gè)包裝后的異常我們不需要,應(yīng)該直接將捕獲到的生產(chǎn)者的業(yè)務(wù)異常拋給前端。那么,如何解決這個(gè)問(wèn)題呢?

可以通過(guò)重寫(xiě)OpenFeign的默認(rèn)異常解碼器來(lái)實(shí)現(xiàn),代碼如下:

@RequiredArgsConstructor
@Slf4j
public class DailyMartFeignErrorDecoder implements ErrorDecoder {

    private final ObjectMapper objectMapper;

    /**
     * OpenFeign的異常解析
     * @author Java日知錄
     * @param methodKey 方法名
     * @param response 響應(yīng)體
     */
    @Override
    public Exception decode(String methodKey, Response response) {
        try {
            Reader reader = response.body().asReader(Charset.defaultCharset());
            Result<?> result = objectMapper.readValue(reader, objectMapper.constructType(Result.class));
            return new RemoteException(result.getCode(),result.getMessage());
        } catch (IOException e) {
            log.error("Response轉(zhuǎn)換異常",e);
            throw new RemoteException(ErrorCode.FEIGN_ERROR);
        }

    }
}

此異常解碼器直接將異常轉(zhuǎn)化為自定義的RemoteException,表示遠(yuǎn)程調(diào)用異常。

當(dāng)然,還需要在配置類(lèi)中注入此異常解碼器。

2.3 Feign全局異常處理

在2.2小節(jié)中,我們拋出了自定義的業(yè)務(wù)異常,然而OpenFeign處理響應(yīng)時(shí)會(huì)捕獲到業(yè)務(wù)異常并將其轉(zhuǎn)換成DecodeException。

圖片圖片

由于DailyMart中的全局異常處理器沒(méi)有單獨(dú)處理DecodeException,它會(huì)被兜底異常處理器攔截,并返回類(lèi)似“系統(tǒng)異常,請(qǐng)聯(lián)系管理員”的錯(cuò)誤提示。

因此,要完全使用上游系統(tǒng)的業(yè)務(wù)異常,還需要定義一個(gè)單獨(dú)的異常處理器來(lái)處理DecodeException。這個(gè)處理器可以與全局異常處理器分開(kāi),代碼如下:

/**
 * Feign的全局異常處理,與常規(guī)的全局異常處理類(lèi)分開(kāi)
 * @author Java日知錄
 */
@RestControllerAdvice
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE) // 優(yōu)先級(jí)
@ResponseStatus(code = HttpStatus.BAD_REQUEST) // 統(tǒng)一 HTTP 狀態(tài)碼
public class DailyMartFeignExceptionHandler {
    
    @ExceptionHandler(FeignException.class)
    public Result<?> handleFeignException(FeignException e) {
        return new Result<Void>()
                .setCode(ErrorCode.REMOTE_ERROR.getCode())
                .setMessage(e.getMessage())
                .setTimestamp(System.currentTimeMillis());
    }
    
    @ExceptionHandler(DecodeException.class)
    public Result<?> handleDecodeException(DecodeException e) {
        Throwable cause = e.getCause();
        if (cause instanceof AbstractException) {
            RemoteException remoteException = (RemoteException) cause;
            // 上游符合全局響應(yīng)包裝約定的再次拋出即可
            return new Result<Void>()
                    .setCode(remoteException.getCode())
                    .setMessage(remoteException.getMessage())
                    .setTimestamp(System.currentTimeMillis());
        }
        // 全部轉(zhuǎn)換成RemoteException
        return new Result<Void>()
                .setCode(ErrorCode.REMOTE_ERROR.getCode())
                .setMessage(e.getMessage())
                .setTimestamp(System.currentTimeMillis());
    }
    
}

如此一來(lái),框架會(huì)自動(dòng)將業(yè)務(wù)異常傳遞給調(diào)用服務(wù),業(yè)務(wù)中也無(wú)需關(guān)心全局包裝的拆解問(wèn)題,這就是OpenFeign遠(yuǎn)程調(diào)用的最佳實(shí)踐。當(dāng)然,在DailyMart中可能有許多服務(wù)都需要遠(yuǎn)程調(diào)用,我們可以將上述內(nèi)容構(gòu)建成一個(gè)通用的Starter模塊,以便其他業(yè)務(wù)模塊共享。

圖片圖片

小結(jié)

本文深入研究了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)和微服務(wù)架構(gòu)中的兩個(gè)關(guān)鍵概念:防腐層(ACL)和遠(yuǎn)程調(diào)用的最佳實(shí)踐。在DDD中,我們學(xué)習(xí)了如何使用ACL來(lái)隔離外部依賴(lài),降低系統(tǒng)耦合度。在微服務(wù)架構(gòu)中,我們探討了如何通過(guò)OpenFeign來(lái)實(shí)現(xiàn)跨服務(wù)調(diào)用,并解決了全局包裝和異常處理的問(wèn)題,希望本文的內(nèi)容對(duì)您在軟件開(kāi)發(fā)項(xiàng)目中有所幫助。

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

2023-09-22 16:22:13

IntegerJava

2023-03-29 15:01:43

微服務(wù)開(kāi)發(fā)

2022-09-27 15:06:07

微服務(wù)架構(gòu)開(kāi)發(fā)

2022-12-05 09:08:12

微服務(wù)灰度發(fā)布

2019-08-29 08:00:00

微服務(wù)架構(gòu)服務(wù)網(wǎng)格

2017-08-16 09:03:33

云計(jì)算微服務(wù)用例

2024-04-03 12:14:15

微服務(wù)架構(gòu)監(jiān)控

2019-01-10 13:17:15

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

2024-12-17 08:20:50

2025-03-21 08:55:36

SpringOpenFeignAPI

2022-07-26 09:48:55

微服務(wù)服務(wù)AKF

2019-07-28 20:38:33

2020-09-16 09:08:49

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

2023-04-03 17:43:47

gRPCOpenFeign微服務(wù)

2019-09-29 10:29:02

緩存模式微服務(wù)架構(gòu)

2022-06-05 13:51:47

SentinelOpenFeign服務(wù)熔斷

2018-07-10 15:05:33

數(shù)據(jù)中心

2019-07-26 08:00:00

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

2024-11-05 13:23:51

2019-01-21 10:50:07

微服務(wù)架構(gòu)開(kāi)發(fā)
點(diǎn)贊
收藏

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