良好的RPC接口設(shè)計(jì),需要注意這些方面

RPC 框架的討論一直是各個(gè)技術(shù)交流群中的熱點(diǎn)話(huà)題锨用,阿里的 dubbo,新浪微博的 motan隘谣,谷歌的 grpc增拥,以及不久前螞蟻金服開(kāi)源的 sofa,都是比較出名的 RPC 框架寻歧。RPC 框架掌栅,或者一部分人習(xí)慣稱(chēng)之為服務(wù)治理框架,更多的討論是存在于其技術(shù)架構(gòu)码泛,比如 RPC 的實(shí)現(xiàn)原理猾封,RPC 各個(gè)分層的意義,具體 RPC 框架的源碼分析…但卻并沒(méi)有太多話(huà)題和“如何設(shè)計(jì) RPC 接口”這樣的業(yè)務(wù)架構(gòu)相關(guān)噪珊。

可能很多小公司程序員還是比較關(guān)心這個(gè)問(wèn)題的晌缘,這篇文章主要分享下一些個(gè)人眼中 RPC 接口設(shè)計(jì)的最佳實(shí)踐。

初識(shí) RPC 接口設(shè)計(jì)

由于 RPC 中的術(shù)語(yǔ)每個(gè)程序員的理解可能不同痢站,所以文章開(kāi)始磷箕,先統(tǒng)一下 RPC 術(shù)語(yǔ),方便后續(xù)闡述阵难。

大家都知道共享接口是 RPC 最典型的一個(gè)特點(diǎn)岳枷,每個(gè)服務(wù)對(duì)外暴露自己的接口,該模塊一般稱(chēng)之為 api呜叫;外部模塊想要實(shí)現(xiàn)對(duì)該模塊的遠(yuǎn)程調(diào)用空繁,則需要依賴(lài)其 api;每個(gè)服務(wù)都需要有一個(gè)應(yīng)用來(lái)負(fù)責(zé)實(shí)現(xiàn)自己的 api朱庆,一般體現(xiàn)為一個(gè)獨(dú)立的進(jìn)程盛泡,該模塊一般稱(chēng)之為 app。

api 和 app 是構(gòu)建微服務(wù)項(xiàng)目的最簡(jiǎn)單組成部分椎工,如果使用 maven 的多 module 組織代碼饭于,則體現(xiàn)為如下的形式蜀踏。

serviceA 服務(wù)

serviceA/pom.xml 定義父 pom 文件

<modules>    
  <module>serviceA-api</module>    
  <module>serviceA-app</module>
</modules>
<packaging>pom</packaging>
<groupId>moe.cnkirito</groupId>
<artifactId>serviceA</artifactId>
<version>1.0.0-SNAPSHOT</version>

serviceA/serviceA-api/pom.xml 定義對(duì)外暴露的接口,最終會(huì)被打成 jar 包供外部服務(wù)依賴(lài)

<parent>    
  <artifactId>serviceA</artifactId>    
  <groupId>moe.cnkirito</groupId>    
  <version>1.0.0-SNAPSHOT</version>
</parent>
<packaging>jar</packaging>
<artifactId>serviceA-api</artifactId>

serviceA/serviceA-app/pom.xml 定義了服務(wù)的實(shí)現(xiàn)掰吕,一般是 springboot 應(yīng)用果覆,所以下面的配置文件中,我配置了 springboot 應(yīng)用打包的插件殖熟,最終會(huì)被打成 jar 包局待,作為獨(dú)立的進(jìn)程運(yùn)行。

<parent>
    <artifactId>serviceA</artifactId>
    <groupId>moe.cnkirito</groupId>
    <version>1.0.0-SNAPSHOT</version>
</parent>
<packaging>jar</packaging>
<artifactId>serviceA-app</artifactId>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

麻雀雖小菱属,五臟俱全钳榨,這樣一個(gè)微服務(wù)模塊就實(shí)現(xiàn)了。

舊 RPC 接口的痛點(diǎn)

統(tǒng)一好術(shù)語(yǔ)纽门,這一節(jié)來(lái)描述下我曾經(jīng)遭遇過(guò)的 RPC 接口設(shè)計(jì)的痛點(diǎn)薛耻,相信不少人有過(guò)相同的遭遇。

查詢(xún)接口過(guò)多
各種 findBy 方法赏陵,加上各自的重載饼齿,幾乎占據(jù)了一個(gè)接口 80% 的代碼量。這也符合一般人的開(kāi)發(fā)習(xí)慣蝙搔,因?yàn)轫?yè)面需要各式各樣的數(shù)據(jù)格式缕溉,加上查詢(xún)條件差異很大,便造成了:一個(gè)查詢(xún)條件吃型,一個(gè)方法的尷尬場(chǎng)景证鸥。這樣會(huì)導(dǎo)致另外一個(gè)問(wèn)題,需要使用某個(gè)查詢(xún)方法時(shí)勤晚,直接新增了方法枉层,但實(shí)際上可能這個(gè)方法已經(jīng)出現(xiàn)過(guò)了,隱藏在了令人眼花繚亂的方法中运翼。

難以擴(kuò)展
接口的任何改動(dòng)返干,比如新增一個(gè)入?yún)ⅲ紩?huì)導(dǎo)致調(diào)用者被迫升級(jí)血淌,這也通常是 RPC 設(shè)計(jì)被詬病的一點(diǎn)矩欠,不合理的 RPC 接口設(shè)計(jì)會(huì)放大這個(gè)缺點(diǎn)。

升級(jí)困難
在之前的 “初識(shí) RPC 接口設(shè)計(jì)”一節(jié)中悠夯,版本管理的粒度是 project癌淮,而不是 module,這意味著:api 即使沒(méi)有發(fā)生變化沦补,app 版本演進(jìn)乳蓄,也會(huì)造成 api 的被迫升級(jí),因?yàn)?project 是一個(gè)整體夕膀。問(wèn)題又和上一條一樣了虚倒,api 一旦發(fā)生變化美侦,調(diào)用者也得被迫升級(jí),牽一發(fā)而動(dòng)全身魂奥。

難以測(cè)試
接口一多菠剩,職責(zé)隨之變得繁雜,業(yè)務(wù)場(chǎng)景各異耻煤,測(cè)試用例難以維護(hù)具壮。特別是對(duì)于那些有良好習(xí)慣編寫(xiě)單元測(cè)試的程序員而言,簡(jiǎn)直是噩夢(mèng)哈蝇,用例也得跟著改棺妓。

異常設(shè)計(jì)不合理
在既往的工作經(jīng)歷中曾經(jīng)有一次會(huì)議,就 RPC 調(diào)用中的異常設(shè)計(jì)引發(fā)了爭(zhēng)議炮赦,一派人覺(jué)得需要有一個(gè)業(yè)務(wù) CommonResponse怜跑,封裝異常,每次調(diào)用后吠勘,優(yōu)先判斷調(diào)用結(jié)果是否 success妆艘,在進(jìn)行業(yè)務(wù)邏輯處理;另一派人覺(jué)得這比較麻煩看幼,由于 RPC 框架是可以封裝異常調(diào)用的,所以應(yīng)當(dāng)直接 try catch 異常幌陕,不需要進(jìn)行業(yè)務(wù)包裹诵姜。在沒(méi)有明確規(guī)范時(shí),這兩種風(fēng)格的代碼同時(shí)存在于項(xiàng)目中搏熄,十分難看棚唆!

單參數(shù)接口

如果你使用過(guò) springcloud ,可能會(huì)不適應(yīng) http 通信的限制心例,因?yàn)?@RequestBody 只能使用單一的參數(shù)宵凌,也就意味著,springcloud 構(gòu)建的微服務(wù)架構(gòu)下止后,接口天然是單參數(shù)的瞎惫。而 RPC 方法入?yún)⒌膫€(gè)數(shù)在語(yǔ)法層面是不會(huì)受到限制的,但如果強(qiáng)制要求入?yún)閱螀?shù)译株,會(huì)解決一部分的痛點(diǎn)瓜喇。

使用 Specification 模式解決查詢(xún)接口過(guò)多的問(wèn)題

public interface StudentApi{
    Student findByName(String name);
    List<Student> findAllByName(String name);
    Student findByNameAndNo(String name,String no);
    Student findByIdcard(String Idcard);
}

如上的多個(gè)查詢(xún)方法目的都是同一個(gè):根據(jù)條件查詢(xún)出 Student,只不過(guò)查詢(xún)條件有所差異歉糜。試想一下乘寒,Student 對(duì)象假設(shè)有 10 個(gè)屬性,最壞的情況下它們的排列組合都可能作為查詢(xún)條件匪补,這便是查詢(xún)接口過(guò)多的根源伞辛。

public interface StudentApi{
    Student findBySpec(StudentSpec spec);
    List<Student> findListBySpec(StudentListSpec spec);
    Page<Student> findPageBySpec(StudentPageSpec spec);
}

上述接口便是最通用的單參接口烂翰,三個(gè)方法幾乎囊括了 99% 的查詢(xún)條件。所有的查詢(xún)條件都被封裝在了 StudentSpec,StudentListSpec,StudentPageSpec 之中蚤氏,分別滿(mǎn)足了單對(duì)象查詢(xún)甘耿,批量查詢(xún),分頁(yè)查詢(xún)的需求瞧捌。如果你了解領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)棵里,會(huì)發(fā)現(xiàn)這里借鑒了其中 Specification 模式的思想。

單參數(shù)易于做統(tǒng)一管理

public interface SomeProvider {
    void opA(ARequest request);
    void opB(BRequest request);
    CommonResponse<C> opC(CRequest request);
}

入?yún)⒅械娜雲(yún)㈦m然形態(tài)各異姐呐,但由于是單個(gè)入?yún)⒌盍钥梢越y(tǒng)一繼承 AbstractBaseRequest,即上述的 ARequest曙砂,BRequest头谜,CRequest 都是 AbstractBaseRequest 的子類(lèi)。在千米內(nèi)部項(xiàng)目中鸠澈,AbstractBaseRequest 定義了 traceId柱告、clientIp、clientType笑陈、operationType 等公共入?yún)⒓识龋瑴p少了重復(fù)命名,我們一致認(rèn)為涵妥,這更加的 OO乖菱。

有了 AbstractBaseRequest,我們可以更加輕松地在其之上做 AOP蓬网,千米的實(shí)踐中窒所,大概做了如下的操作:

  • 請(qǐng)求入?yún)⒔y(tǒng)一校驗(yàn)(request.checkParam(); param.checkParam();)
  • 實(shí)體變更統(tǒng)一加鎖,降低鎖粒度
  • 請(qǐng)求分類(lèi)統(tǒng)一處理(if (request instanceof XxxRequest))
  • 請(qǐng)求報(bào)文統(tǒng)一記日志(log.setRequest(JsonUtil.getJsonString(request)))
  • 操作成功統(tǒng)一發(fā)消息

如果不遵守單參數(shù)的約定帆锋,上述這些功能也并不是無(wú)法實(shí)現(xiàn)吵取,但所需花費(fèi)的精力遠(yuǎn)大于單參數(shù),一個(gè)簡(jiǎn)單的約定帶來(lái)的優(yōu)勢(shì)锯厢,我們認(rèn)為是值得的皮官。

單參數(shù)入?yún)⒓嫒菪詮?qiáng)

還記得前面的小節(jié)中,我提到了 SpringCloud哲鸳,在 SpringCloud Feign 中臣疑,接口的入?yún)⑼ǔ?huì)被 @RequestBody 修飾,強(qiáng)制做單參數(shù)的限制徙菠。千米內(nèi)部使用了 Dubbo 作為 Rpc 框架讯沈,一般而言,為 Dubbo 服務(wù)設(shè)計(jì)的接口是不能直接用作 Feign 接口的(主要是因?yàn)?@RequestBody 的限制),但有了單參數(shù)的限制缺狠,便使之成為了可能问慎。為什么我好端端的 Dubbo 接口需要兼容 Feign 接口?可能會(huì)有人發(fā)出這樣的疑問(wèn)挤茄,莫急如叼,這樣做的初衷當(dāng)然不是為了單純做接口兼容,而是想充分利用 HTTP 豐富的技術(shù)棧以及一些自動(dòng)化工具穷劈。

  • 自動(dòng)生成 HTTP 接口實(shí)現(xiàn)(讓服務(wù)端同時(shí)支持 Dubbo 和 HTTP 兩種服務(wù)接口)

看過(guò)我之前文章的朋友應(yīng)該了解過(guò)一個(gè)設(shè)計(jì):千米內(nèi)部支持的是 Dubbo 協(xié)議和 HTTP 協(xié)議族(如 JSON RPC 協(xié)議笼恰,Restful 協(xié)議),這并不意味著程序員需要寫(xiě)兩份代碼歇终,我們可以通過(guò) Dubbo 接口自動(dòng)生成 HTTP 接口社证,體現(xiàn)了單參數(shù)設(shè)計(jì)的兼容性之強(qiáng)。

  • 通過(guò) Swagger UI 實(shí)現(xiàn)對(duì) Dubbo 接口的可視化便捷測(cè)試

又是一個(gè)兼容 HTTP 技術(shù)棧帶來(lái)的便利评凝,在 Restful 接口的測(cè)試中追葡,Swagger 一直是備受青睞的一個(gè)工具,但可惜的是其無(wú)法對(duì) Dubbo 接口進(jìn)行測(cè)試奕短。兼容 HTTP 后宜肉,我們只需要做一些微小的工作,便可以實(shí)現(xiàn) Swagger 對(duì) Dubbo 接口的可視化測(cè)試翎碑。

  • 有利于 TestNg 集成測(cè)試

自動(dòng)生成 TestNG 集成測(cè)試代碼和缺省測(cè)試用例谬返,這使得服務(wù)端接口集成測(cè)試變得異常簡(jiǎn)單,程序員更能集中精力設(shè)計(jì)業(yè)務(wù)用例日杈,結(jié)合缺省用例朱浴、JPA 自動(dòng)建表和 PowerMock 模擬外部依賴(lài)接口實(shí)現(xiàn)本機(jī)環(huán)境。

這塊涉及到了公司內(nèi)部的代碼达椰,只做下簡(jiǎn)單介紹,我們一般通過(guò)內(nèi)部項(xiàng)目 com.qianmi.codegenerator:api-dubbo-2-restful 项乒,com.qianmi.codegenerator:api-request-json 生成自動(dòng)化的測(cè)試用例啰劲,方便測(cè)試。而這些自動(dòng)化工具中大量使用了反射檀何,而由于單參數(shù)的設(shè)計(jì)蝇裤,反射用起來(lái)比較方便。

接口異常設(shè)計(jì)

首先肯定一點(diǎn)频鉴,RPC 框架是可以封裝異常的栓辜,Exception 也是返回值的一部分。在 go 語(yǔ)言中可能更習(xí)慣于返回 err,res 的組合垛孔,但 JAVA 中我個(gè)人更偏向于 try catch 的方法捕獲異常藕甩。RPC 接口設(shè)計(jì)中的異常設(shè)計(jì)也是一個(gè)注意點(diǎn)。

初始方案

public interface ModuleAProvider {
    void opA(ARequest request);
    void opB(BRequest request); 
   CommonResponse<C> opC(CRequest request);
}

我們假設(shè)模塊 A 存在上述的 ModuleAProvider 接口周荐,ModuleAProvider 的實(shí)現(xiàn)中或多或少都會(huì)出現(xiàn)異常狭莱,例如可能存在的異常 ModuleAException僵娃,調(diào)用者實(shí)際上并不知道 ModuleAException 的存在,只有當(dāng)出現(xiàn)異常時(shí)腋妙,才會(huì)知曉默怨。對(duì)于 ModuleAException 這種業(yè)務(wù)異常,我們更希望調(diào)用方能夠顯示的處理骤素,所以 ModuleAException 應(yīng)該被設(shè)計(jì)成 Checked Excepition匙睹。

正確的異常設(shè)計(jì)姿勢(shì)

public interface ModuleAProvider {
    void opA(ARequest request) throws ModuleAException;
    void opB(BRequest request) throws ModuleAException;
    CommonResponse<C> opC(CRequest request) throws ModuleAException;
}

上述接口中定義的異常實(shí)際上也是一種契約,契約的好處便是不需要敘述济竹,調(diào)用方自然會(huì)想到要去處理 Checked Exception痕檬,否則連編譯都過(guò)不了。

調(diào)用方的處理方式

在 ModuleB 中规辱,應(yīng)當(dāng)如下處理異常:

public class ModuleBService implements ModuleBProvider {
    @Reference
    ModuleAProvider moduleAProvider;

    @Override
    public void someOp() throws ModuleBexception{
        try{
            moduleAProvider.opA(...);
        }catch(ModuleAException e){
            throw new ModuleBException(e.getMessage());
        }
    }

    @Override
    public void anotherOp(){
        try{
            moduleAProvider.opB(...);
        }catch(ModuleAException e){
            // 業(yè)務(wù)邏輯處理
        }
    }
}

someOp 演示了一個(gè)異常流的傳遞谆棺,ModuleB 暴露出去的異常應(yīng)當(dāng)是 ModuleB 的 api 模塊中異常類(lèi),雖然其依賴(lài)了 ModuleA 罕袋,但需要將異常進(jìn)行轉(zhuǎn)換改淑,或者對(duì)于那些意料之中的業(yè)務(wù)異常可以像 anotherOp() 一樣進(jìn)行處理浴讯,不再傳遞朵夏。這時(shí)如果新增 ModuleC 依賴(lài) ModuleB,那么 ModuleC 完全不需要關(guān)心 ModuleA 的異常榆纽。

異常與熔斷

作為系統(tǒng)設(shè)計(jì)者仰猖,我們應(yīng)該認(rèn)識(shí)到一點(diǎn): RPC 調(diào)用,失敗是常態(tài)奈籽。通常我們需要對(duì) RPC 接口做熔斷處理饥侵,比如千米內(nèi)部便集成了 Netflix 提供的熔斷組件 Hystrix。Hystrix 需要知道什么樣的異常需要進(jìn)行熔斷衣屏,什么樣的異常不能夠進(jìn)行熔斷躏升。在沒(méi)有上述的異常設(shè)計(jì)之前,回答這個(gè)問(wèn)題可能還有些難度狼忱,但有了 Checked Exception 的契約膨疏,一切都變得明了清晰了。

public class ModuleAProviderProxy {

    @Reference
    private ModuleAProvider moduleAProvider;

    @HystrixCommand(ignoreExceptions = {ModuleAException.class})
    public void opA(ARequest request) throws ModuleAException {
        moduleAProvider.opA(request);
    }

    @HystrixCommand(ignoreExceptions = {ModuleAException.class})
    public void opB(BRequest request) throws ModuleAException {
        moduleAProvider.oBB(request);
    }

    @HystrixCommand(ignoreExceptions = {ModuleAException.class})
    public CommonResponse<C> opC(CRequest request) throws ModuleAException {
        return moduleAProvider.opC(request);
    }
}

如服務(wù)不可用等原因引發(fā)的多次接口調(diào)用超時(shí)異常钻弄,會(huì)觸發(fā) Hystrix 的熔斷佃却;而對(duì)于業(yè)務(wù)異常,我們則認(rèn)為不需要進(jìn)行熔斷窘俺,因?yàn)閷?duì)于接口 throws 出的業(yè)務(wù)異常饲帅,我們也認(rèn)為是正常響應(yīng)的一部分,只不過(guò)借助于 JAVA 的異常機(jī)制來(lái)表達(dá)。實(shí)際上洒闸,和生成自動(dòng)化測(cè)試類(lèi)的工具一樣染坯,我們使用了另一套自動(dòng)化的工具,可以由 Dubbo 接口自動(dòng)生成對(duì)應(yīng)的 Hystrix Proxy丘逸。我們堅(jiān)定的認(rèn)為開(kāi)發(fā)體驗(yàn)和用戶(hù)體驗(yàn)一樣重要单鹿,所以公司內(nèi)部會(huì)有非常多的自動(dòng)化工具。

API 版本單獨(dú)演進(jìn)

引用一段公司內(nèi)部的真實(shí)對(duì)話(huà):

A:我下載了你們的代碼庫(kù)怎么編譯不通過(guò)啊深纲,依賴(lài)中 xxx-api-1.1.3 版本的 jar 包找不到了仲锄,那可都是 RELEASE 版本啊。

B:你不知道我們 nexus 容量有限湃鹊,只能保存最新的 20 個(gè) RELEASE 版本嗎儒喊?那個(gè) API 現(xiàn)在最新的版本是 1.1.31 啦。

A:啊币呵,這才幾個(gè)月就幾十個(gè) RELEASE 版本啦怀愧?這接口太不穩(wěn)定啦。

B: 其實(shí)接口一行代碼沒(méi)改余赢,我們業(yè)務(wù)分析是很牛逼的芯义,一直很穩(wěn)定。但是這個(gè) API 是和我們項(xiàng)目一起打包的妻柒,我們需求更新一次扛拨,就發(fā)布一次,API 就被迫一起升級(jí)版本举塔。發(fā)生這種事绑警,大家都不想的。

在單體式架構(gòu)中央渣,版本演進(jìn)的單位是整個(gè)項(xiàng)目计盒。微服務(wù)解決的一個(gè)關(guān)鍵的痛點(diǎn)便是其做到了每個(gè)服務(wù)的單獨(dú)演進(jìn),這大大降低了服務(wù)間的耦合芽丹。正如我文章開(kāi)始時(shí)舉得那個(gè)例子一樣:serviceA 是一個(gè)演進(jìn)的單位章郁,serviceA-api 和 serviceA-app 這兩個(gè) Module 從屬于 serviceA,這意味著 app 的一次升級(jí)志衍,將會(huì)引發(fā) api 的升級(jí),因?yàn)樗麄兪枪采牧奶妫《鴱奈⒎?wù)的使用角度來(lái)看楼肪,調(diào)用者關(guān)心的是 api 的結(jié)構(gòu),而對(duì)其實(shí)現(xiàn)壓根不在乎惹悄。所以對(duì)于 api 定義未發(fā)生變化春叫,其 app 發(fā)生變化的那些升級(jí),其實(shí)可以做到對(duì)調(diào)用者無(wú)感知。在實(shí)踐中也是如此

api 版本的演進(jìn)應(yīng)該是緩慢的暂殖,而 app 版本的演進(jìn)應(yīng)該是頻繁的价匠。

所以,對(duì)于這兩個(gè)演進(jìn)速度不一致的模塊呛每,我們應(yīng)該單獨(dú)做版本管理踩窖,他們有自己的版本號(hào)。

問(wèn)題回歸

**查詢(xún)接口過(guò)多
**各種 findBy 方法晨横,加上各自的重載洋腮,幾乎占據(jù)了一個(gè)接口 80% 的代碼量。這也符合一般人的開(kāi)發(fā)習(xí)慣手形,因?yàn)轫?yè)面需要各式各樣的數(shù)據(jù)格式啥供,加上查詢(xún)條件差異很大,便造成了:一個(gè)查詢(xún)條件库糠,一個(gè)方法的尷尬場(chǎng)景伙狐。這樣會(huì)導(dǎo)致另外一個(gè)問(wèn)題,需要使用某個(gè)查詢(xún)方法時(shí)瞬欧,直接新增了方法贷屎,但實(shí)際上可能這個(gè)方法已經(jīng)出現(xiàn)過(guò)了净响,隱藏在了令人眼花繚亂的方法中及志。

解決方案:使用單參+Specification 模式丈秩,降低重復(fù)的查詢(xún)方法菱魔,大大降低接口中的方法數(shù)量待逞。

難以擴(kuò)展
接口的任何改動(dòng)锻离,比如新增一個(gè)入?yún)⒒炒螅紩?huì)導(dǎo)致調(diào)用者被迫升級(jí)渔肩,這也通常是 RPC 設(shè)計(jì)被詬病的一點(diǎn)贬墩,不合理的 RPC 接口設(shè)計(jì)會(huì)放大這個(gè)缺點(diǎn)榴嗅。

解決方案:?jiǎn)螀⒃O(shè)計(jì)其實(shí)無(wú)形中包含了所有的查詢(xún)條件的排列組合,可以直接在 app 實(shí)現(xiàn)邏輯的新增陶舞,而不需要對(duì) api 進(jìn)行改動(dòng)(如果是參數(shù)的新增則必須進(jìn)行 api 的升級(jí)嗽测,參數(shù)的廢棄可以用 @Deprecated 標(biāo)準(zhǔn))。

升級(jí)困難
在之前的 “初識(shí) RPC 接口設(shè)計(jì)”一節(jié)中肿孵,版本管理的粒度是 project唠粥,而不是 module,這意味著:api 即使沒(méi)有發(fā)生變化停做,app 版本演進(jìn)晤愧,也會(huì)造成 api 的被迫升級(jí),因?yàn)?project 是一個(gè)整體蛉腌。問(wèn)題又和上一條一樣了官份,api 一旦發(fā)生變化只厘,調(diào)用者也得被迫升級(jí),牽一發(fā)而動(dòng)全身舅巷。

解決方案:以 module 為版本演進(jìn)的粒度羔味。api 和 app 單獨(dú)演進(jìn),減少調(diào)用者的不必要升級(jí)次數(shù)钠右。

難以測(cè)試
接口一多赋元,職責(zé)隨之變得繁雜,業(yè)務(wù)場(chǎng)景各異爬舰,測(cè)試用例難以維護(hù)们陆。特別是對(duì)于那些有良好習(xí)慣編寫(xiě)單元測(cè)試的程序員而言,簡(jiǎn)直是噩夢(mèng)情屹,用例也得跟著改坪仇。

解決方案:?jiǎn)螀?shù)設(shè)計(jì)+自動(dòng)化測(cè)試工具,打造良好的開(kāi)發(fā)體驗(yàn)垃你。

異常設(shè)計(jì)不合理
在既往的工作經(jīng)歷中曾經(jīng)有一次會(huì)議椅文,就 RPC 調(diào)用中的異常設(shè)計(jì)引發(fā)了爭(zhēng)議,一派人覺(jué)得需要有一個(gè)業(yè)務(wù) CommonResponse惜颇,封裝異常皆刺,每次調(diào)用后,優(yōu)先判斷調(diào)用結(jié)果是否 success凌摄,在進(jìn)行業(yè)務(wù)邏輯處理羡蛾;另一派人覺(jué)得這比較麻煩,由于 RPC 框架是可以封裝異常調(diào)用的锨亏,所以應(yīng)當(dāng)直接 try catch 異常痴怨,不需要進(jìn)行業(yè)務(wù)包裹。在沒(méi)有明確規(guī)范時(shí)器予,這兩種風(fēng)格的代碼同時(shí)存在于項(xiàng)目中浪藻,十分難看!

解決方案:Checked Exception+正確異常處理姿勢(shì)乾翔,使得代碼更加優(yōu)雅爱葵,降低了調(diào)用方不處理異常帶來(lái)的風(fēng)險(xiǎn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末反浓,一起剝皮案震驚了整個(gè)濱河市萌丈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雷则,老刑警劉巖辆雾,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異巧婶,居然都是意外死亡乾颁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)艺栈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)英岭,“玉大人,你說(shuō)我怎么就攤上這事湿右∽缑茫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵毅人,是天一觀(guān)的道長(zhǎng)吭狡。 經(jīng)常有香客問(wèn)我,道長(zhǎng)丈莺,這世上最難降的妖魔是什么划煮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮缔俄,結(jié)果婚禮上弛秋,老公的妹妹穿的比我還像新娘。我一直安慰自己俐载,他們只是感情好蟹略,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著遏佣,像睡著了一般挖炬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上状婶,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天意敛,我揣著相機(jī)與錄音,去河邊找鬼太抓。 笑死空闲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的走敌。 我是一名探鬼主播碴倾,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼掉丽!你這毒婦竟也來(lái)了跌榔?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捶障,失蹤者是張志新(化名)和其女友劉穎僧须,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體项炼,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡担平,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年示绊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暂论。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡面褐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出取胎,到底是詐尸還是另有隱情展哭,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布闻蛀,位于F島的核電站匪傍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏觉痛。R本人自食惡果不足惜役衡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秧饮。 院中可真熱鬧映挂,春花似錦、人聲如沸盗尸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)泼各。三九已至鞍时,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扣蜻,已是汗流浹背逆巍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留莽使,地道東北人锐极。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像芳肌,于是被迫代替她去往敵國(guó)和親灵再。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容