成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

微服務中,OpenFeign應該這樣用才好!

開發 架構
本文深入研究了領域驅動設計(DDD)和微服務架構中的兩個關鍵概念:防腐層(ACL)和遠程調用的最佳實踐。在DDD中,我們學習了如何使用ACL來隔離外部依賴,降低系統耦合度。

大家好,我是飄渺。在今天的DDD與微服務系列文章中,讓我們探討如何在DDD的分層架構中調用第三方服務以及在微服務中使用OpenFeign的最佳實踐。

1. DDD中的防腐層

在應用服務中,經常需要調用外部服務接口來實現某些業務功能,這就在代碼層面引入了對外部系統的依賴。例如,下面這段轉賬的代碼邏輯需要調用外部接口服務RemoteService來獲取匯率。

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強烈依賴于RemoteService和ExchangeRateRemote對象。如果外部服務的方法或ExchangeRateRemote字段發生變化,都會影響到ApplicationService的代碼。當有多個服務依賴此外部接口時,遷移和改造的成本將會巨大。同時,外部依賴的兜底、限流和熔斷策略也會受到影響。

在復雜系統中,我們應該盡量避免自己的代碼因為外部系統的變化而修改。那么如何實現對外部系統的隔離呢?答案就是引入防腐層(Anti-Corruption Layer,簡稱ACL)。

1.1 什么是防腐層

在許多情況下,我們的系統需要依賴其他系統,但被依賴的系統可能具有不合理的數據結構、API、協議或技術實現。如果我們強烈依賴外部系統,就會導致我們的系統受到“腐蝕”。在這種情況下,通過引入防腐層,可以有效地隔離外部依賴和內部邏輯,無論外部如何變化,內部代碼盡可能保持不變。

圖片圖片

防腐層不僅僅是一層簡單的調用封裝,在實際開發中,ACL可以提供更多強大的功能:

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

1.2 如何實現防腐層

實現ACL防腐層的步驟如下:

  • 對于依賴的外部對象,我們提取所需的字段,并創建一個內部所需的DTO類。
  • 構建一個新的Facade,在Facade中封裝調用鏈路,將外部類轉化為內部類。Facade可以參考Repository的實現模式,將接口定義在領域層,而將實現放在基礎設施層。
  • 在ApplicationService中依賴內部的Facade對象。

具體實現如下:

// 自定義的內部值類
@Data
public class ExchangeRateDTO {
  ...
}

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

// 稅率facade實現
@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;
    }
}

通過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();
   }
  ...
}

這樣,經過ACL改造后,ApplicationService的代碼已不再直接依賴外部的類和方法,而是依賴我們自己內部定義的值類和接口。如果未來外部服務發生任何變化,只需修改Facade類和數據轉換邏輯,而不需要修改ApplicationService的邏輯。

1.3 小結

在沒有防腐層ACL的情況下,系統需要直接依賴外部對象和外部調用接口,調用邏輯如下:

圖片圖片

而有了防腐層ACL后,系統只需要依賴內部的值類和接口,調用邏輯如下:

圖片圖片

2. 微服務中的遠程調用

在構建微服務時,我們經常需要跨服務調用,例如在DailyMart系統中,購物車服務需要調用商品服務以獲取商品詳細信息。理論上,我們可以遵循上述ACL的實現邏輯,在購物車模塊創建Facade接口和內部轉換類。然而,在實際開發中,由于是內部系統,差異性不太明顯,通??梢灾苯邮褂肙penFeign進行遠程調用,忽略Facade定義和內部類轉換的過程。

以下是在微服務中使用OpenFeign實現跨服務調用的過程:

  • 首先,在購物車模塊的基礎設施層創建一個接口,并使用@FeignClient注解進行標注。
@FeignClient("product-service")
public interface ProductRemoteFacade {

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

}

需要注意的是,我們在商品服務中對外提供的商品詳情接口定義返回的是ProductRespDTO對象,但通過OpenFeign調用時返回的是Result對象。

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

這是因為在前文中,我們定義了一個全局的包裝類GlobalResponseBodyAdvice,會自動給所有接口封裝返回對象Result。因此,在定義Feign接口時,也需要使用Result對象來接收。如果對此邏輯不太清晰,建議參考第七章的內容。

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

}

如上所示,我們可以看到,每次調用Feign接口都需要解析Result對象以獲取真正的業務對象。這種代碼看起來有些冗余,是否有辦法去除呢?

2.1 自定義Feign的解碼器

這時,我們可以通過重寫Feign的解碼器來實現,在解碼器中完成封裝對象的拆解。

@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());
        }
    }
}

同時,創建一個配置類,替換原生的解碼器。

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

這樣,在定義或調用OpenFeign接口時,直接使用原生對象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 上游異常統一處理

在使用OpenFeign進行遠程調用時,如果HTTP狀態碼為非200,OpenFeign會觸發異常解析并進入默認的異常解碼器feign.codec.ErrorDecoder,將業務異常包裝成FeignException。此時,如果不做任何處理,調用時可以返回的消息會變成FeignException的消息體,如下所示:

圖片圖片

顯然,這個包裝后的異常我們不需要,應該直接將捕獲到的生產者的業務異常拋給前端。那么,如何解決這個問題呢?

可以通過重寫OpenFeign的默認異常解碼器來實現,代碼如下:

@RequiredArgsConstructor
@Slf4j
public class DailyMartFeignErrorDecoder implements ErrorDecoder {

    private final ObjectMapper objectMapper;

    /**
     * OpenFeign的異常解析
     * @author Java日知錄
     * @param methodKey 方法名
     * @param response 響應體
     */
    @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轉換異常",e);
            throw new RemoteException(ErrorCode.FEIGN_ERROR);
        }

    }
}

此異常解碼器直接將異常轉化為自定義的RemoteException,表示遠程調用異常。

當然,還需要在配置類中注入此異常解碼器。

2.3 Feign全局異常處理

在2.2小節中,我們拋出了自定義的業務異常,然而OpenFeign處理響應時會捕獲到業務異常并將其轉換成DecodeException。

圖片圖片

由于DailyMart中的全局異常處理器沒有單獨處理DecodeException,它會被兜底異常處理器攔截,并返回類似“系統異常,請聯系管理員”的錯誤提示。

因此,要完全使用上游系統的業務異常,還需要定義一個單獨的異常處理器來處理DecodeException。這個處理器可以與全局異常處理器分開,代碼如下:

/**
 * Feign的全局異常處理,與常規的全局異常處理類分開
 * @author Java日知錄
 */
@RestControllerAdvice
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE) // 優先級
@ResponseStatus(code = HttpStatus.BAD_REQUEST) // 統一 HTTP 狀態碼
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;
            // 上游符合全局響應包裝約定的再次拋出即可
            return new Result<Void>()
                    .setCode(remoteException.getCode())
                    .setMessage(remoteException.getMessage())
                    .setTimestamp(System.currentTimeMillis());
        }
        // 全部轉換成RemoteException
        return new Result<Void>()
                .setCode(ErrorCode.REMOTE_ERROR.getCode())
                .setMessage(e.getMessage())
                .setTimestamp(System.currentTimeMillis());
    }
    
}

如此一來,框架會自動將業務異常傳遞給調用服務,業務中也無需關心全局包裝的拆解問題,這就是OpenFeign遠程調用的最佳實踐。當然,在DailyMart中可能有許多服務都需要遠程調用,我們可以將上述內容構建成一個通用的Starter模塊,以便其他業務模塊共享。

圖片圖片

小結

本文深入研究了領域驅動設計(DDD)和微服務架構中的兩個關鍵概念:防腐層(ACL)和遠程調用的最佳實踐。在DDD中,我們學習了如何使用ACL來隔離外部依賴,降低系統耦合度。在微服務架構中,我們探討了如何通過OpenFeign來實現跨服務調用,并解決了全局包裝和異常處理的問題,希望本文的內容對您在軟件開發項目中有所幫助。

責任編輯:武曉燕 來源: JAVA日知錄
相關推薦

2023-09-22 16:22:13

IntegerJava

2023-03-29 15:01:43

微服務開發

2022-09-27 15:06:07

微服務架構開發

2022-12-05 09:08:12

微服務灰度發布

2019-08-29 08:00:00

微服務架構服務網格

2019-01-10 13:17:15

微服務容器微服務架構

2017-08-16 09:03:33

云計算微服務用例

2024-04-03 12:14:15

微服務架構監控

2024-12-17 08:20:50

2022-07-26 09:48:55

微服務服務AKF

2025-03-21 08:55:36

SpringOpenFeignAPI

2019-07-28 20:38:33

2020-09-16 09:08:49

訂單微服務架構

2019-09-29 10:29:02

緩存模式微服務架構

2022-06-05 13:51:47

SentinelOpenFeign服務熔斷

2023-04-03 17:43:47

gRPCOpenFeign微服務

2018-07-10 15:05:33

數據中心

2024-05-17 13:48:19

2019-01-21 10:50:07

微服務架構開發

2024-01-02 08:35:17

微服務版本管理
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩国产一区二区三区 | 婷婷激情综合 | 中文字幕第5页 | 精品国产99 | 久久久免费电影 | 亚洲美女视频 | 一区二区精品 | 久久久久亚洲视频 | 亚洲高清免费 | 精品无码久久久久久久动漫 | 美女激情av | 日本福利在线观看 | 天天草视频 | 国产成人精品一区二 | 久久久久久久久久一区二区 | 久久亚洲精品国产精品紫薇 | 久久爱一区 | 中文字幕福利视频 | 亚洲国产一区二区三区 | 国产精品美女一区二区 | 国产精品一区二区三区久久久 | 亚洲精品乱码久久久久久按摩 | 亚洲天天干 | 性高湖久久久久久久久aaaaa | 国产在线一区二区三区 | 狠狠的干| 国产精品久久久久久高潮 | 欧美 日韩 国产 成人 在线 | 国产一区中文 | 99日韩| 欧美无乱码久久久免费午夜一区 | 国产精品久久亚洲 | 亚洲精品在线免费观看视频 | 蜜桃一区二区三区 | www.亚洲.com | 日韩免费电影 | 午夜播放器在线观看 | 91精品国产91久久综合桃花 | 欧美天堂在线观看 | 久久综合久久自在自线精品自 | 一区二区三区四区在线视频 |