单体、微服务之间动态切换的代码项目落地方案

412 阅读7分钟

简介

从对Spring源码的思考,以及吸收了DDD的部分思想,再接借鉴了一下alibaba COLA架构和Vue组件的开发模式,最后再结合一下自己从业中遇到的问题和经验,从而总结出来的一个支持快速迭代开发、防腐烂、且可单体微服务之间动态切换的落地代码项目架构方案。

解决的问题

面对变化多端、既要又要且还要的需求,传统中的mvc三层架构(就算是三十层!)也明显是招架不住的,需求的不确定性以及随时可能面临的修改,再加了历史沉淀的堆砌,代码的下场往往都是庞大又臃肿,羞涩又难懂,让我对这行是又爱又恨。

面对如此变化多端的需求,就没有办法改变现状了吗?我坚信着,只有用魔法才能打败魔法。所以说,有没有这么一种可能,如果代码本身就比需求还要灵活,那么要解决这个需求还不是分分钟的事?

理论基础

我最近了解了一下DDD(Domain-driven design)领域驱动设计,但它只是一个理论框架,没有具体可参考的落地实战方案,如果说非要我挑一个它最大的缺点,那么我的答案就是它的概念太过庞大,传播和学习的成本过高。

我特别喜欢三体里的叶文洁和罗辑的一段对话

……
“我倒是有个建议:你为什么不去研究宇宙社会学呢?”
“但,叶老师,您说的宇宙社会学没有任何可供研究的实际资料,也不太可能进行调查和实验。”
“所以你最后的成果就是纯理论的,就像欧氏几何一样,先设定几条简单的不证自明的公理,再在这些公理的基础上推导出整个理论体系。”
“叶老师,这......真是太有意思了,可是宇宙社会学的公理是什么呢?”
"第一,生存是文明的第一需要;第二,文明不断增长和扩张,但宇宙中的物质总量保持不变。”
……

受到了这个启发,所以在这里,我也要进行设定了……

1. 假设一个需要外力才能改变驱动的对象,称之为“因子(Factor)”;驱动“因子”的外力,称之为“行为(behavior)”。
2. “行为”可以使“因子”发生改变、使别的“行为”启动,“因子”的改变也会触发上述效果。
3. 同一环境下,“行为”执行同一策略所得的结果要一致。

基于这几条设定,对于一个软件系统来说,“行为”的主体我这里只考虑人的因素,以商城系统来举例,从用户的角度,“因子”对应的就是产品、订单,“行为”对应的就是下单、付款。如果执行相同的“下单行为”,却产生了不一样的结果,那么就说明了同一行为下执行了不一样的策略。 把这一现象映射到人的身上,那么就是一个人的想法时刻都可能会发生变化,而需求的变化性正是来源于人的想法上的层出不穷,这也可以用来解释了为什么需求总是变化的。想法的快速变化和代码的固定性上,就存在着一个根本的矛盾。

所以现在再次回到那个问题,有没有这么一种可能,代码本身就比需求还要灵活?

架构设计

一、基本概念

1. 因子(Factor)

Factor的概念借鉴于DDD的充血模型,Factor本身不做任何的持久操作,但是每一个Factor持有一个FactorRepository,FactorRepository是啥?

2. 因子资源库(FactorRepository)

FactorRepository是一个因子操作的持久层的接口,Factor在实例化的时候必须设置一个具体实现的FactorRepository,当实现类是Msql时,因子操作Mysql持久层,当实现类是RPC时,因子则调用了别的微服务接口。FactorRepository是实现单体和微服务动态拆分的关键。

3. 行为(Behavior)

所有的因子只能由Behavior操作。

4. 空因子构建器(FactorBuilder)

因子只能由FactorBuilder实例化,FactorBuilder持有FactorRepository。

5. 因子工厂(FactorFactory)

Behavior的因子由FactorFactory获取,FactorFactory持有FactorBuilder。

6. 因子观测器(FactorObserver)

当因子发生变动时执行对应的FactorObserver。FactorObserver在FactorFactory构造因子时加入监听。

7. 其它

一整个Behavior执行下来,当一个因子触发了另一个因子发生变化的时候,这一过程有点像核裂变,于是我贴心的为它起了一个名字…… 二狗架构(bushi) 因子裂变架构。

ayst.jpg

二、优雅的加入SpringMVC

总得来说,mvc虽然有它的缺点,但奈何它开发速度就是快,长处也是特别明显,小孩子才做选择,大人全都要。

以商城项目为例,项目地址:gitee.com/JayEinstein…

mvcRegister.png

注册一个“下单行为”

下单PaymentBehavior需要用到产品(Product)、订单(Order)、会员(Member)因子。 而构建一个因子需要用到FactorFactory、FactorBuilder,则在Spring中分别进行注册。

  1. 注册FactorBuilder,填充FactorRepository
/**
 * 组件内因子构建器注册
 * @author JayEinstein
 */
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class FactorBuilderRegister {

    @Bean
    public OrderBuilder orderBuilder(OrderRepository orderRepository) {
        return new OrderBuilder(orderRepository);
    }

    @Bean
    public ProductBuilder productBuilder(ProductRepository productRepository) {
        return new ProductBuilder(productRepository);
    }

    @Bean
    public MemberBuilder memberBuilder(MemberRepository memberRepository) {
        return new MemberBuilder(memberRepository);
    }

}
  1. 注册因子工厂,注入上面的FactorBuilder
@Component
public class MemberFactory extends AbstractFactorFactory<Member, MemberBuilder> {

    @Autowired
    public MemberFactory(MemberBuilder factorBuilder) {
        super(factorBuilder);
    }

}

@Component
public class OrderFactory extends AbstractFactorFactory<Order, OrderBuilder> {

    @Autowired
    public OrderFactory(OrderBuilder factorBuilder) {
        super(factorBuilder);
    }

}

@Component
public class ProductFactory extends AbstractFactorFactory<Product, ProductBuilder> {

    @Autowired
    private List<OutOfStockObserver> outOfStockObserverList;

    @Autowired
    public ProductFactory(ProductBuilder factorBuilder) {
        super(factorBuilder);
    }

}
  1. 注册行为,把上面的FactorFactory注入
/**
 * 行为注册
 * @author JayEinstein
 */
@Configuration
public class BehaviorRegister {

    /**
     * 注册行为
     * @param productFactory 产品工厂
     * @param orderFactory 订单工厂
     * @param memberFactory 会员工厂
     * @return
     */
    @Bean
    public PaymentBehavior paymentBehavior(
            ProductFactory productFactory,
            OrderFactory orderFactory,
            MemberFactory memberFactory
    ) {

        return new PaymentBehavior(productFactory, orderFactory, memberFactory);
    }

}
  1. Behavior放入Controller
@RestController
@RequestMapping("/shop/order")
public class OrderController {

    @Autowired
    private PaymentBehavior paymentBehavior;

    /**
     * 创建订单
     * @param createOrder
     * @return
     */
    @PostMapping("/create")
    public Object createOrder(@RequestBody CreateOrderDto createOrder) {
        Order order = paymentBehavior.createOrder(createOrder);
        return order;
    }
    
}
加入一个需求

当一个产品库存为0时,通知运营进行补货。

  1. 实现一个FactorObserver
/**
 * 产品售罄通知
 * @author JayEinstein
 */
@Component
@Slf4j
public class OutOfStock implements OutOfStockObserver {

    @Override
    public void receive(Product factor) {

        log.info("产品售罄通知:{} 已售罄", factor.getName());

    }

}
  1. 在FactorFactory中加入监听
@Component
@Slf4j
public class ProductFactory extends AbstractFactorFactory<Product, ProductBuilder> {

    @Autowired
    private List<OutOfStockObserver> outOfStockObserverList;

    @Autowired
    public ProductFactory(ProductBuilder factorBuilder) {
        super(factorBuilder);
    }

    @Override
    public Product get(Long factorId) {

        ProductBuilder factorBuilder = getFactorBuilder();

        // 注入零库存监听器
        Product product = factorBuilder.buildOutOfStock(outOfStockObserverList.toArray(new OutOfStockObserver[]{}));

        Object value = getFactorInfoFromRepository(factorId);

        BeanUtil.copyProperties(value, product);

        return product;
    }

}

这就是想象力的空间,当在行为和因子足够丰富的情况下,是不是就可以是在开闭的原则下实现指哪打哪的快速开发?

三、单体到微服务的动态演变

这里借用了Vue component 模式,涉及到了几个模块的概念,简单的来说是用到了componentFactorRepositorystart模块。 component是功能的实现,依赖FactorRepository进行持久操作,start本身没有任何东西,是component和FactorRepository的集合。 如果start拥有了所有的component和FactorRepository那它成了一个单体的服务,通过这个模式,我们就可以巧妙的把微服务怎么划分的问题转化成了微服务怎么组装的问题。

回归项目中,start-all是所有的component集合,是一个单体,如果我们要把行为中product因子操作使用RPC模式,我们只需要把start-all的pom中从

    <dependency>
        <groupId>com.shop.factor.repo</groupId>
        <artifactId>product-mysql</artifactId>
    </dependency>

改为

    <dependency>
        <groupId>com.shop.factor.repo</groupId>
        <artifactId>product-rpc</artifactId>
    </dependency>

然后再搭建一个start-product服务。

修改start/start-all的pom,启动start/start-product,服务的拆分就完成了。

start repo

这又是另一个想象力的空间。同样的,当在功能组件足够丰富的情况下,我们是不是就可以通过简单排列组合就完成了系统的搭建?

最后

这是本人第一次写的技术分享文章,写得不好勿怪。此前在十八线不知名的小公司从事了一整年微服务架构的开发,从中遇到了很多很多令人头疼的问题,特别是这个拆分与耦合的问题,平时忙得也没时间思考,最近总算有了一点空。

个人本身的能力也有限,经验还尚浅,对很多东西都只是一知半解,只能局限于自己有限的知识做出来的一些思考。如果还有一些我没想到的、做错的,或者说还有一些更好的方案,欢迎大佬们帮忙指教和改进,感激不尽!