助力Java系统组件化:Navi框架简介

一,导航是什么

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,共同改进。也欢迎讨论复杂系统的架构和设计之道。

爱奇艺技术产品团队
爱奇艺技术产品团队

爱奇艺做一家以科技创新为驱动的伟大娱乐公司,用大数据指导内容的制作、生产、运营、消费。并通过强大的云计算能力、带宽储备以及全球性的视频分发网络,为用户提供更好的视频服务。

工程开源爱奇艺Java
1
暂无评论
暂无评论~