助力Java系統元件化:Navi框架簡介

愛奇藝技術產品團隊發表於2018-12-17

一,導航是什麼

Navi是一個用來實現元件路由功能的開源框架.Navi是導航的縮寫,意為導航。透過Navi,開發人員能夠輕鬆實現元件化的系統。實現根據配置,使元件動態生效的目的。

Navi源於愛奇藝會員後臺系統團隊對系統可擴充套件性和元件化設計方面的探索,並最終透過將相關技術成果通用化輸出而得。

專案地址:https://github.com/iqiyi/navi

二,導航的目的

在系統開發中,尤其是業務系統開發中,由於業務需求的複雜性,導致開發人員需要編寫程式碼處理不同場景下的業務需求。久而久之,便會導致程式碼複雜,混亂,進而便的難以維護和擴充套件。

解決這一問題不能靠簡單的拆分,因為拆分之後,仍然需要考慮如何將業務執行流程路由到拆分之後的元件或服務中。如果使用傳統的方式,即簡單的if ... else,那最終的系統依舊是難以擴充套件。

Navi的目的便在於解決此類問題。

總結歸納來說,Navi可以幫助解決如下問題:

  • 程式碼複雜度高

  • 程式碼擴充套件性差

  • 核心與分支難以區分

  • 程式碼難以複用

三,如何使用導航

下面用一個例子介紹一下Navi的用法:

下面展示了一段用以實現建立訂單功能的程式碼:

public class OrderService {    public OrderCreateResult createOrder(OrderCreateRequest req) {        Order order = Order.create(req);                if (req.getClientType().equals(ANDROID)) {            // Do for the common android client.            if (Version.create("1.0.0", "2.0.0").within(req.getClientVersion()) {                 // Do for android client v1.            } else if (Version.create("2.0.0", "3.0.0").within(req.getClientVersion()) {                 // DO for android client v2.            } else { // Do for other versions android client}            if (req.getClientType().equals(H5)) {            // Do for H5.}        orderRepository.save(order);            return OrderCreateResponse.create(order, req);}}

這段程式碼的主要功能是建立並且儲存訂單資料。同時,這段程式碼還實現了一些分支功能,有安卓客戶端的需求,H5客戶端的需求。此外,安卓客戶端的不同版本還有這各自不同的需求。

上面形式的程式碼實現需求是沒有問題的,但是卻存在著很多影響系統可讀性,可擴充套件性,甚至可用性的問題。

首先,上面程式碼中的那一段條件語句,並不是主功能,而是不同客戶端下的分支功能。分支功能和主功能纏在一起,會使得主功能難以理解。

一些簡單的程式碼重構手段,例如抽取方法可以一定程度上解決可讀性的問題:

public class OrderService {    public OrderCreateResult createOrder(OrderCreateRequest req) {        Order order = Order.create(req);                 customize(req, order);        orderRepository.save(order);        return OrderCreateResponse.create(order, req);    }        private void customize(OrderCreateRequest req, Order order) {        if (req.getClientType().equals(ANDROID)) {            // Do for the common android client.            if (Version.create("1.0.0", "2.0.0").within(req.getClientVersion()) {                 // Do for android client v1.            } else if (Version.create("2.0.0", "3.0.0").within(req.getClientVersion()) {                 // DO for android client v2.            } else {   // Do for other versions android client.}}        if (req.getClientType().equals(H5)) {            // Do for H5. }    }}

但這麼做並不能解決可擴充套件性的問題,因為有新功能時,原有程式碼必然會被修改。

使用導航之後

在使用Navi之後,上面這個例子會變成下面的樣子:

interface OrderCreateHandler {    void handle(Order order, OrderCreateRequest request); }@EqualMatcher(property = "clientType", value = "android")@VersionMatcher(range = "[1.0.0,2.0.0)")@Componentclass AndroidV1Handler implments OrderCreateHandler {    void handle(Order order, OrderCreateRequest request) {    } }@EqualMatcher(property = "clientType", value = "android")@VersionMatcher(range = "[2.0.0,3.0.0)")@Componentclass AndroidV2Handler implments OrderCreateHandler {    void handle(Order order, OrderCreateRequest request) {    } }@EqualMatcher(property = "clientType", value = "android")@VersionMatcher(range = "[3.0.0,*)")@Componentclass AndroidLatestHandler implments OrderCreateHandler {    void handle(Order order, OrderCreateRequest request) {    } }@EqualMatcher(property = "clientType", value = "h5")@Componentclass H5Handler implments OrderCreateHandler {    void handle(Order order, OrderCreateRequest request) {    } }public class OrderService {    @AutoWired     private Selector selector; // SpringBasedSelector    public OrderCreateResult createOrder(OrderCreateRequest req) {        Order order = Order.create(req);        customize(req, order);            orderRepository.save(order);        return OrderCreateResponse.create(order, req);    }        private void customize(OrderCreateRequest req, Order order) {        OrderCreateHandler handler = selector.select(req, OrderCreateHandler.class);        if (handler != null) {            handler.handle(order, req); } }}

上面的程式碼行數總體上雖然增加了,但是卻能實現新功能的增加不引起原有程式碼修改的目標,即對擴充套件開發,對修改封閉的開閉原則。

當需要增加新的分支功能時,可以透過增加新的實現OrderCreateHander的類,透過增加Matcher註釋來定義這個類在什麼情況下被執行。而這一切都不需要對已有程式碼進行修改。

自定義組合註解

除了直接使用Navi所提供的內建註解以外,開發人員還可以透過組合實現自定義的Matcher註解。除

@Retention(RetentionPolicy.RUNTIME)@EqualMatcher(property = "name")@VersionMatcher(property = "clientVersion")@CompositeMatcherType@interface ClientRequestMapping {    @AliasFor(annotationFor = EqualMatcher.class, attributeFor = "value")    String name();    @AliasFor(annotationFor = VersionMatcher.class, attributeFor = "range")    String clientVersionRange(); }

如上所示,ClientRequestMapping註解將EqualMatcher和VersionMatcher這兩個註解組合為一個。使用起來就比原來的方式更加簡單易懂。

@ClientRequestMapping(name = "hulk", clientVersionRange = "[1.0.0,2.0.0)")class AliasAllHandler implements Handler { }

四,導航的實現原理

下面簡單介紹一下Navi的實現原理:

selector.select(req,OrderCreateHandler.class)當這條語句被第一次執行時,Selector首先會載入OrderCreateHandler.class型別所表示所有候選元件。當使用SpringBasedSelector作為Selector實現類時,它會從Spring的ApplicationContext中載入所有型別為OrderCreateHandler的Bean。

接下來會對所有候選元件依次進行匹配,來決定它們是否與輸入條件匹配。

輸入條件使用selector.select(req,OrderCreateHandler.class)中的第一個入參req來表示。它可以是任意型別,透過反射的方式獲取其中的屬性。

在對每個候選元件進行匹配時,Selector會解析每個候選元件上的Matcher註解:

@EqualMatcher(property = "clientType", value = "android")@VersionMatcher(range = "[1.0.0,2.0.0)")class AndroidV1Handler implments OrderCreateHandler { }

每個Matcher會被解析為一個MatcherDefinition,所以一個候選元件會產生一組MatcherDefinition,之後這組MatcherDefinition會被快取,方便後續的呼叫。

生成MatcherDefinition之後,會一次對其進行匹配。如果匹配結果為REJECT則終止本輪匹配,否則繼續。

選擇策略上,預設為返回首箇中中的(沒有被REJECT的),也可選擇返回匹配程度最高的。後續會增加返回多個候選元件的方式。

五,最後

後續Navi會繼續改進功能:增加更多的內建匹配器,增加更多的元件選擇模式,增加配置方式(配置檔案,DSL),透過AOP使得使用方式更加透明,支援熱載入,以及繼續改進效能。

Navi的目標是使元件化設計更加簡單,使得業務系統變得容易擴充套件。促進系統朝著通用化和平臺化的方向發展。

希望更多的開發者使用Navi,提出意見,提交PR,共同改進。也歡迎討論複雜系統的架構和設計之道。

相關文章