微服务的架构下,如何根据业务抽象出适合自己系统的组件?

导读:基础SpringBoot/SpringCloud微服务的架构下,我们或多或少会根据业务抽象出适合自己系统的组件或SDK,来应对对内、对外的拓展。

在SpringBoot/SpringCloud先前介绍了一些,如:

  • @Conditional 来指定指定条件的时候才将某个 bean 加载到应用上下文中。
  • @FunctionalInterface 函数式接口申明
  • @JsonTypeInfo 在Java类继承的情况下如何实现父类及子类的JSON序列化与反序列化。

等等其他的注解标识,极大简化了业务逻辑和代码。

当然不这么实现是不是没有其他方式实现了,其实也不是唯一的方式!简单暴力的方式也是可以的,写业务逻辑时,if-else 可能是最容易想到的逻辑方式了。然而大量堆砌的 if-else 毫无疑问将给代码维护带来巨大的困难。如果想用if-else 来完善你的业务组件,尽量优化你的代码,避免后期业务拓展棘手。

如果在同一JVM中上述的方式没有多大问题,但是分布在不同的JVM中(微服务集群),上述的方案估计要丢弃了。

在阅读下文时,考虑几个问题:

  • 自定义的组件规则/SDK包,什么时候扫描才合理?
  • 组件元数据怎样采集?

案例场景

目前存在三个服务,引擎层服务A,业务服务B、业务服务C。A|B|C在同一注册集群中,A需提供组件于B\C服务使用,依赖关系如下图所示。

图片

方案选型

方案一 | 业务服务主动上报

业务服务启动后主动上报服务引擎A所需要的元数据信息和提供服务的实现服务引擎A指定的数据上报接口

图片

优点:

服务器引擎只集成对外开放的业务,业务服务模块需自行完善数据上报,对于服务引擎A来讲。业务简单化,不需要关注业务服务太多场景。

只需要持久化或者缓存业务服务上报的元数据即可!

缺点:

缺点就是对于服务引擎A中集成SDK的升级调整,需要既要考虑自身服务的升级改造,同时锁涉及的业务模块也需要随之调整升级。对于业务模块或者第三方服务是非常不友好的。

方案二 | 引擎服务主动采集

引擎服务在完善业务模块元数据持久化或者缓存的同时,并且提供业务模块采集的元数据SDK或客户端,便于业务模块快速集成,完善整体架构。

图片

方案实现

图片

定义基础元注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * Meta-annotation definition
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MetaProperty{
    // Custom properties
}
复制代码

再此基础上可对于基础元注解再进行内部拓展

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * Service engine metadata extension
 */
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MetaPropertyExtension{
    // Custom properties
}
复制代码

定义好元数据注解之后,面临的问题如开篇所提到的:自定义的组件规则/SDK包,什么时候扫描才合理? 思考一个问题,如果系统启动后再运行时业务初始化的时候发现元数据还未采集业务就阻塞住了,所以对于元数据的采集处理自然在系统启动时候就开应该处理。 对于SpringCloud服务启动后可以监听处理,简化代码如下:

@Slf4j
@Component
@Order(ReadyEventOrder.FRAMEWORK + 10)
public class InitMetaAwareDefinition extends AbstractFormFactory implements NamingConstant, MetaConstant,
        ApplicationContextAware,
        ApplicationListener<ApplicationReadyEvent> {
 
    private AnnotatedMetaDefinition metaDefinition;
 
    /** 监听器是否已执行标示,该监听器只需要执行一次 */
    public static volatile AtomicBoolean executed = new AtomicBoolean(false);
 
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        initMetaDefinition();
    }
 
    @Bean
    @Override
    public AnnotatedMetaDefinition setAnnotatedMetaDefinition() {
        metaDefinition = new ScannedAnnotatedMetaDefinition();
        return metaDefinition;
    }
 
    /** 注册元数据定义 */
    @Override
    public void initMetaDefinition() throws KmssRuntimeException {
        try {
            CompletableFuture.runAsync(() -> {
                // 扫描注册处理
            });
        } catch (Exception e) {
            executed.set(false);
            log.debug("引擎元数据注册失败!", e);
        }
        log.debug("引擎元数据注册成功!");
    }
 
    /** 初始化应用上下文 */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
 
    protected ApplicationContext applicationContext;
}
复制代码

确定触发时间后,要扫描注册处理

Set<BeanDefinition> beanDefinitionSet = scanByAnnotation(BASE_PACKAGE
                + NamingConstant.DOT
                + "**"
                + NamingConstant.PATH_PREFIX_ENTITY, MetaProperty.class);

/**
 * 扫描包路径下所有使用注解的类定义
 */
private Set<BeanDefinition> scanByAnnotation(String basePackage,
                                             Class<MetaProperty> annotationClass) {
    ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
        false);
    provider.addIncludeFilter(new AnnotationTypeFilter(annotationClass, true, true));
    return provider.findCandidateComponents(basePackage);
}
复制代码

ClassPathScanningCandidateComponentProvider 从基础包中提供候选组件的组件提供者。

图片

如果可用,可以使用the index ,否则扫描类路径。通过应用排除和包含过滤器来识别候选组件。

AnnotationTypeFilter , AssignableTypeFilter支持使用Indexed注释的注释/超类上的包含过滤器:如果指定了任何其他包含过滤器,则忽略索引并改为使用类路径扫描。此类实现基于 Spring 的MetadataReader工具,由 ASM ClassReader支持。后续针对源码再做详细讲解 

@SneakyThrows
public void registerMetaDefinition(BeanDefinition beanDefinition) {
        try {
            // 元数据格式化
            // 保存拓展元数据(远程调用引擎服务A接口)
        } catch (Exception e) {
            throw new RuntimeException("初始化元数据失败异常!", e);
        }
    }
复制代码

总结

针对服务集成无非两种方案,要么定义规则,乙方根据规则来完善业务,另外一种就是甲方定义好规则并且完善好业务,乙方集成使用即可。

目前市面上最多的还是第二种方案,毕竟后期迭代维护效率更好,版本更好把控。如果后期需要针对基础升级改造可完全替代集成SDK和业务模块的SDK依赖即可

首发地址:微服务的架构下,如何根据业务抽象出适合自己系统的组件?

微信搜“码农架构”,专注于系统架构、高可用、高性能、高并发类技术分享