最近没有在写新的文章,Kolin 系列和 DDD 系列都暂停了。原因是最近正在造轮子。
先送上轮子的项目地址:https://github.com/yanglifan/navi
起因
这个轮子名为 Navi,意为导航。之所以叫这个名字,是因为这个轮子的主要功能是提供了基于注解的组件选择功能。好比是为方法调用导航,选择合适的组件。故起名 Navi。
什么要造这个轮子?因为最近本人一直在做业务核心系统的重构,这些系统的代码历史都比较久远,复杂难懂。每个功能都有很多的逻辑分支,用来处理不同的业务场景。导致代码里充斥着各种条件语句。
这些代码虽然被频频吐槽,但因为它们承载了重要的业务功能,所以不能抛弃。同时,还有大量的新需求不断加入进来,所以这些代码还在不断修改。
这样的局面导致在现有系统上实现新业务的开发、测试速度越来越慢,成本越来越高。
思考
很多开发团队在面对这个问题的时候会选择推倒重来。这么做不是不行,但暂且不谈成本,首先要考虑的一个问题是,如何使用新的设计解决现有系统所存在的问题。
在我看来,解决很多复杂业务系统上述问题的一些通用方法有:
- 明确核心流程
- 组件(插件)化
- 强调组合大于继承的原则
上面三点的核心是组件化设计。即将复杂的业务系统拆分为不同的组件,以实现单一职责和开闭原则。这里要强调的是不要期望微服务能够解决上述问题。在没有将业务系统做组件化查分之前,强推微服务不仅不会解决上述问题,还会产生新的问题。
将业务系统组件化的最大问题是如何让一次请求走到正确的业务组件中,同时能够避免大量的条件语句。解决这一问题的选择有很多,比如各种设计模式。但设计模式不是开箱即用的,恰当地使用设计模式并不是一件简单的事情。
那有没有一种开箱即用的技术能够解决上述问题呢?在我十多年的工作中并没有发现很合适的工具。常见的如规则引擎、状态机等设计,虽然有可选的实现,但都显得过于复杂,难以使用。
解决
所以,为了解决上述问题,我设计开发了 Navi 这个项目。
Navi 的原理并不复杂:使用注解声明组件的匹配规则,使用时根据入参匹配规则,最终找出最合适的组件。
基本使用
一个简单的代码示例:
interface OrderCreateHandler { // 1
void handle(Order order, OrderCreateRequest request);
}
@EqualMatcher(property = "clientType", value = "android") // 2
@VersionMatcher(range = "[1.0.0,2.0.0)") // 2
class AndroidV1Handler implments OrderCreateHandler { // 2
void handle(Order order, OrderCreateRequest request) {
}
}
public class OrderService {
public OrderCreateResult createOrder(OrderCreateRequest req) {
Order order = Order.create(req);
OrderCreateHandler handler = selector.select(req, OrderCreateHandler.class); // 3
if (handler != null) {
handler.handle(order, req);
}
orderRepository.save(order);
return OrderCreateResponse.create(order, req);
}
}
第1点:定义一个接口。目前组件选择是以类为最小粒度。因为 Java 8 引入了 Functional Interface 和 Lambda,使得类级别的粒度也可以实现方法粒度的选择。
第2点:实现这个接口,并定义匹配规则。本例中,如果入参的 clientType
字段为 android
,并且版本(默认 version
)字段大于等于 1.0.0,小于 2.0.0 时,使用 AndroidV1Handler
这个组件。你可以定义多个实现相同接口的组件,以实现不同场景下的需求。
第3点:使用 selector
(接口为 Selector
,目前有 SimpleSelector
和 SpringBasedSelector
两个实现),传入请求和组件接口两个参数,根据组件实现上的匹配规则,选择出合适的组件。
组合匹配规则
除了直接使用匹配规则,还可以自定义组合匹配规则:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EqualMatcher(property = "platform.tags")
@VersionMatcher(property = "appVersion")
@CompositeMatcherType
public @interface StoreViewHandler {
String ALL_PLATFORMS = "*";
@AliasFor(annotationFor = EqualMatcher.class, attributeFor = "value")
String[] platform() default ALL_PLATFORMS;
@AliasFor(annotationFor = VersionMatcher.class, attributeFor = "range")
String clientVersionRange() default "";
}
上例就是组合匹配规则的定义方式。定义了组合匹配规则之后,你就可以直接使用你自己创建的匹配规则去替代之前的多个匹配规则。
其它
除了可以通过组合自定义匹配规则,还可以支持完全自定义的匹配规则,以及 repeatable 的匹配规则(Java 8 的 Repeatable 注解)
后续计划
目前这个轮子的一大问题是由于使用了反射,使得当被筛选的类较多时,性能损耗较多。所以 1.1.0 版本将重点通过缓存等手段,降低反射带来的性能损耗。在 1.2.0 版本中,将重点增加对泛型的支持。在 1.3.0 版本中,将重点支持动态的依赖注入。