分佈式架構設計之Rest API版本管理

分佈式架構設計之RestAPI版本管理

 

隨着互聯網發展腳步的加快,產品項目的迭代也隨之加快,所以就需要我們對產品的穩定提供一定的保障。而直接與用戶接觸的前端應用一般都是通過接口API與後臺交互,一旦相關的API需求改版後,原來的API就不能使用,需要重新發布更新,如果前端產品是移動APP應用,比如:android/ios,那麼就必須重新提交應用審覈,等待若干天的審覈發佈是很不好的,嚴重影響用戶的使用,所以建立API版本,使新改版的接口API不影響老版本的API使用,就顯得很有必要了,那麼接下來就介紹下。

 

l   版本形式

l   版本實現

l   版本方案

 

 

一、版本形式

API版本形式主要指的是API版本接口地址的形式,目前比較常見的形式如下:

1、HTPP地址參數

地址形式:http://server:port/api/xxx?v=yyy

 

xxx代表具體的接口名字,?號後面跟着參數v,該參數爲具體的API版本號,需要客戶端傳遞過來,此中版本形式比較傳統。

 

 

2、HTTP請求頭部

地址形式:http://server:port/api/xxx

請求頭部:

即在API請求頭部添加Accept屬性,用於指定響應接收的媒體類型(Media Type),常見Accept形式如下:

application/vnd.api-v1+json

 

 

3、HTTP Rest風格

地址形式:http://server:port/api/{v}/xxx

 

上面的{v}爲動態替換的參數,其作爲接口地址API的一部分,是REST風格的地址形式,所以在靈活度及前後端通信協議的耦合度上都有優勢。而在下面的版本實現時,就是採用這種方式實現驗證的。

 

 

二、版本實現

在這裏,我會使用Java語言的SSM框架來演示API版本的實現。並且全文中僅羅列實現的核心部分,其它內容需要讀者具備一定的基礎。

 

1、ApiVer.java

/**

 * 自定義接口版本標註註解,該註解可標註在

 * 類及方法函數上,並且在運行時環境起作用,

 * 並且可以被潛入到javadoc中,同時指定該

 * 標籤爲web映射註解.

 */

@Target({ElementType.TYPE,ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Mapping

public @interface ApiVer {

    intvalue(); 

}

 

2、ApiRequestMapping.java

/**

 * 首先,需要在Servlet配置中註冊該類

 * 其次,動態編譯時匹配ApiVersion標籤,

 * 獲取控制器中對應類或方法的版本值.

 */

public class ApiRequestMapping extends RequestMappingHandlerMapping {

 

    // 映射匹配自定義註解(ApiVer標註在類級別)

    protected RequestCondition<ApiVerCondition> getCustomTypeCondition(Class<?>handlerType) {

       ApiVer apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVer.class);

       returncreateCondition(apiVersion);

    }

 

    // 映射匹配自定義註解(ApiVer標註在方法級別)

    protected RequestCondition<ApiVerCondition>  getCustomMethodCondition(Method method){

       ApiVer apiVersion = AnnotationUtils.findAnnotation(method, ApiVer.class);

       return createCondition(apiVersion);

    }

 

    // 獲取當前控制器標註的版本,初始化Api版本條件

    // 參考 @ApiVerCondition註釋說明.

    private RequestCondition<ApiVerCondition>  createCondition(ApiVer apiVersion) {

       return apiVersion ==null ?null :newApiVerCondition(apiVersion.value());

    }

}

 

3、ApiVerCondition.java

/**

* 首先,匹配過濾出當前訪問接口中是否存在v(1-9)

 * 如果存在,繼續判斷格式是否對,否則報錯返回。

 * 其次,比較請求地址中版本與控制器中版本,如果

 * 請求的版本值大於控制器中版本值,則取控制器版本

 * 值繼續接口後續訪問操作。

 */

public class ApiVerCondition implements RequestCondition<ApiVerCondition>{ 

    private final static PatternVERSION_PREFIX_PATTERN =Pattern.compile("v(\\d+)/"); 

      

    private int apiVersion; 

      

    public ApiVerCondition(int apiVersion){ 

       this.apiVersion = apiVersion; 

   } 

      

    public ApiVerCondition combine(ApiVerConditionother) { 

       return new ApiVerCondition(other.getApiVersion()); 

   } 

  

    public ApiVerCondition getMatchingCondition(HttpServletRequest request) { 

       Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI()); 

       if(m.find()){ 

           Integer version = Integer.valueOf(m.group(1)); 

           if(version >=this.apiVersion)

                return this

       } 

       return null

   } 

  

    public int compareTo(ApiVerCondition other, HttpServletRequest request) { 

       return other.getApiVersion() -this.apiVersion; 

   } 

  

    public int getApiVersion() { 

       return apiVersion; 

   } 

}

 

這裏值得說明下,當前端訪問的接口地址版本值大於控制器中配置的接口值時,則就會自動訪問當前接口版本最大的接口方法,只做了向上最大兼容。

 

4、ServletConfig.java

@Configuration

@EnableWebMvc

@ComponentScan("com.cwteam.demo.action")

public class ServletConfig extends WebMvcConfigurerAdapter {

    @Bean 

    public RequestMapping HandlerMapping requestMappingHandlerMapping() { 

       RequestMappingHandlerMapping handlerMapping =new ApiRequestMapping(); 

       return handlerMapping; 

    }

}

 

這裏主要是在SpringMVC中註冊請求映射攔截轉發。

 

5、ApiVerAction.java

/**

 * API控制器入口:

 * 首先,根控制器爲動態版本{v},供前端傳遞;

 * 其次,在接口方法上標註@ApiVer自定義註解;

 * 最後,訪問同一個地址時,如:http://server:port/v1/hello

 * 結果返回hello world v1

 */

@RestController

@RequestMapping("/{v}")

public class ApiVerAction {

   

    @ApiVer(1)

    @RequestMapping(value="/hello",method=RequestMethod.GET)

    public String helloV1() throws Exception {

       return"hello world v1";

    }

   

    @ApiVer(2)

    @RequestMapping(value="/hello",method=RequestMethod.GET)

    public String helloV2() throws Exception {

       return"hello world v2";

    }

   

    @ApiVer(3)

    @RequestMapping(value="/hello",method=RequestMethod.GET)

    public String helloV3() throws Exception {

       return"hello world v3";

    }

   

}

 

 

測試結果:

 

訪問地址:

http://localhost:8080/restapi-version/v1/hello

 

 

訪問地址:

http://localhost:8080/restapi-version/v2/hello

 

 

訪問地址:

http://localhost:8080/restapi-version/v3/hello

 

 

訪問地址:

http://localhost:8080/restapi-version/v4/hello

 

 

 

 

三、版本方案

我們知道,實際項目中API會存在很多,如果爲每個API添加3~5個版本控制,相對維護成本還好(但也很人肉),如果需要支撐所有或幾十個API版本兼容暱?如果完全按照上面的實現方式,答案是維護成本非常之高。另外,如果老用戶已經很多版本沒更新,我們也應該支持其API正常使用,這就需要對老版本進行兼容支持,所以需要在下面討論下API的實際需要。

 

1、支持向上兼容

如上圖,我們可以對老版本API兼容支持,也就是最新的API版本邏輯,能夠同時滿足新老用戶需要,也就是我們常見到的單一API版本接口。此種版本方式優缺點都很明顯,優勢就是提供客戶端統一一個接口,劣勢就是隨着版本功能的升級,該接口API內部功能會臃腫,不便於後續的維護開展。

 

 

2、不做向上兼容

 

如上圖,與上面的API方式對比,這裏對每一次的API功能改版升級都提供一個版本,也就是功能API會存在很多子版本。它的好處就是每個API的功能內部簡潔突出當前版本功能,不兼容老版本的功能,如果是老用戶的API,就直接客戶端傳遞版本號,訪問對應的版本API即可。劣勢就是,日後會有成千上萬的版本接口,後續維護升級的成本相當高。

 

 

3、用戶版本升級

就長遠角度考慮,我建議上面第2種實現方式,但又不能讓API的版本無限遞增下去,所以我們需要結合實際情況,只保留一定數量的API支持,比如:僅支持10個版本,如果低於這個版本,則強制老用戶升級API,保證正常使用。

 

實際上,軟件做得再完美,如果偏離了實際,那麼一文不值,所以這裏的API需要判斷處理:如果老用戶版本低於當前最新版本10個版本時,我們提示其強制更新,否則不能正常使用。當然,總有那麼一些需求人提出,要支持所有的老版本API,雖然不建議這樣實現,但也是可以做到的,比如:將API的版本功能實現以字節碼形式,存放在數據庫或文件或中間組件中,以方便對所有API版本進行管理,在加載編譯之前,將所有的API版本加載到接口API實現邏輯中,帶用戶提交對應版本號時,自動定位到指定API,這樣做可以支持更多歷史版本,實際上一些API平臺就是採用這種方式(是支持了更多版本,但也是有數量限制的,如果是合理設計的化)。

 

 

 

 項目代碼:

 http://download.csdn.net/download/why_2012_gogo/10122734


 

 

 

好了,由於作者水平有限,如有不正確或是誤導的地方,請不吝指出討論(技術交流羣:497552060(新))

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章