COLA简介 可自行到github上查看md简介 https://github.com/alibaba/COLA
目录结构分析 COAL提供了一个很方便的maven生成模板,按照项目说明我们生成一个web项目,打开目录我们发现如下几个子模块
父项目
└── start 项目启动模块
└── app 项目应用服务模块(可以理解为客户端程序员在此编写代码)
└── client 提供给客户端程序员使用的二方包,为应用服务模块提供一下基础类
└── controller 控制层SpringMVC实现
└── domain 领域层
└── infrastructure 基础设施层,包含数据库持久化,一些其它基础设施调用代码放在此,如RPC调用,队列调用等基础设施代码
其项目依赖目录如下 start依赖controller,controller依赖app,app同时依赖client和infrastructure,infrastructure依赖domain
start
└── controller
└── app
├── client
├── infrastructure
└── domain
从依赖中我们明显可以看到前文提到的六边形架构的影子,controller为输入端适配器,infrastructure为持久化输出适配器,而核心服务domain领域层作为软件的核心和最稳定的部分放在依赖的最底层
结合领域驱动设计分析目录结构 从目录结构当中划分其实这个框架应该分为两部分,app层之下(不包括)为领域部分,这部分由对领域知识理解较好的开发人员去建模,而app层(包括)之上则为客户端代码,可以由对领域知识不那么了解的开发人员去开发,作者之所以引入client模块一方面是提供了一些二方包和接口功能客户端调用,最主要的原因是反腐,为了反腐在client包中引入DTO,为了读写分离(在领域驱动中这点很重要)又有Command和Query两个实现类,以此完成写命令和读命令的区分,因为在写命令中需要完全遵守领域模型去完成,但是在查询中往往为了响应速度等的妥协而放弃领域模型,直接从基础设施层中读取查询对象返回给前端.
应用框架方面提供了一些注解如示例中的@Command注解,其需要作用在CommandExecutorI的实现类下,这部分内容放在app层代码由客户端人员去实现,这样可以将客户端的业务逻辑放在CommandExecutorI接口规定的方法下,客户端逻辑更加清晰,一个@Command代表着一个客户单操作,每个客户端操作的代码又放在不同的CommandExecutorI实现类下,而框架显示的将操作分为查询操作和命令操作来实现读写分离,实现了各个功能模块的解耦。
client包作为约束客户端(APP)层的重要模块,其下面定义了一个CustomerServiceI接口,此结构的实现类由app层去实现,这种依赖反转的方式很好的控制了app端能做什么也简化了开发。
domain包为领域开发人员所使用的包,领域开发人员将设计的领域模型都放在此提供app层和infrastructure层去调用
infrastructure包可以由数据库开发人员去实现,里面包含领域对象持久化内容和一些基础服务,并且其提供了一个SpringBean的配置文件负责整合了框架
核心源码解析 在demo app层中我们发现了被@Command注解的实现类
1 2 3 4 5 6 7 8 9 10 11 12 @Command public class CustomerListByNameQryExe implements QueryExecutorI<MultiResponse<Customer>, CustomerListByNameQry> { @Override public MultiResponse<Customer> execute(CustomerListByNameQry cmd) { List<Customer> customerList = new ArrayList<>(); Customer customer = new Customer(); customer.setCustomerName("Frank"); customerList.add(customer); return MultiResponse.ofWithoutTotal(customerList); } }
在app服务类调用中仅仅是将customerListByNameQry对象发送给CommandBusI(CommandBus总线)即完成调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Service public class CustomerServiceImpl implements CustomerServiceI { @Autowired private CommandBusI commandBus; @Override public Response addCustomer(CustomerAddCmd customerAddCmd) { return (Response)commandBus.send(customerAddCmd); } @Override public MultiResponse<Customer> listByName(CustomerListByNameQry customerListByNameQry) { return (MultiResponse<Customer>)commandBus.send(customerListByNameQry); } }
我们使用编译器进入CommandBusI实现类发现CommandBusI下对应着一个CommandBus的实现类
1 2 3 4 5 6 7 8 9 10 11 12 @Component public class CommandBus implements CommandBusI{ @Autowired private CommandHub commandHub; @Override public Response send(Command cmd) { return commandHub.getCommandInvocation(cmd.getClass()).invoke(cmd); } }
我们再进入CommandHub查看getCommandInvocation方法,发现其实CommandInvocation是从一个HashMap中来的,那么HashMap里的值是合适初始化的呢
1 2 3 4 5 6 7 8 private Map<Class/*CommandClz*/, CommandInvocation> commandRepository = new HashMap<>(); public CommandInvocation getCommandInvocation(Class cmdClass) { CommandInvocation commandInvocation = commandRepository.get(cmdClass); if (commandRepository.get(cmdClass) == null) throw new ColaException(cmdClass + " is not registered in CommandHub, please register first"); return commandInvocation; }
在infrastructure模块下我们发现框架的入口配置类ColaConfig
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration public class ColaConfig { @Bean(initMethod = "init") public Bootstrap bootstrap() { Bootstrap bootstrap = new Bootstrap(); List<String> packagesToScan = new ArrayList<>(); packagesToScan.add("com.liu"); bootstrap.setPackages(packagesToScan); return bootstrap; } }
于是我们定位到框架的入口,顺着初始化代码在源码COLA-master的cola-framework下找到Bootstrap类,找到初始化方法init
1 2 3 4 public void init() { Set<Class<?>> classSet = scanConfiguredPackages(); registerBeans(classSet); }
看到这里我们就已经明白了scanConfiguredPackages()方法是扫描编译目录下的Class文件,registerBeans是注册bean对象 我们进入registerBeans方法,发现其遍历之前扫描的class文件,并且传入在registerFactory
1 2 3 4 5 6 7 8 9 private void registerBeans(Set<Class<?>> classSet) { for (Class<?> targetClz : classSet) { RegisterI register = registerFactory.getRegister(targetClz); if (null != register) { register.doRegistration(targetClz); } } }
进入registerFactory.getRegister(targetClz)方法,发现代码在registerFactory中根据类上的注解返回对应的注册器,之前对应的@Command注解对应的就是为commandRegister注册器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public RegisterI getRegister(Class<?> targetClz) { PreInterceptor preInterceptorAnn = targetClz.getDeclaredAnnotation(PreInterceptor.class); if (preInterceptorAnn != null) { return preInterceptorRegister; } PostInterceptor postInterceptorAnn = targetClz.getDeclaredAnnotation(PostInterceptor.class); if (postInterceptorAnn != null) { return postInterceptorRegister; } Command commandAnn = targetClz.getDeclaredAnnotation(Command.class); if (commandAnn != null) { return commandRegister; } Extension extensionAnn = targetClz.getDeclaredAnnotation(Extension.class); if (extensionAnn != null) { return extensionRegister; } EventHandler eventHandlerAnn = targetClz.getDeclaredAnnotation(EventHandler.class); if (eventHandlerAnn != null) { return eventRegister; } return null; }
我们再看commandRegister中的doRegistration方法,现在结果一目了然,这里获取Spring容器中的CommandInvocation对象,并且调用commandHub.getCommandRepository().put(commandClz, commandInvocation)将之前被@Command注解的实现类传入commandRepository中也就是之前上面从HashMap中获取CommandInvocation的地方,代码中还提供了自定义AOP功能,但这部分并未开发接口,至此,整个框架的代码流程就已清晰。
1 2 3 4 5 6 7 8 9 @Override public void doRegistration(Class<?> targetClz) { Class<? extends Command> commandClz = getCommandFromExecutor(targetClz); CommandInvocation commandInvocation = ApplicationContextHelper.getBean(CommandInvocation.class); commandInvocation.setCommandExecutor((CommandExecutorI) ApplicationContextHelper.getBean(targetClz)); commandInvocation.setPreInterceptors(collectInterceptors(commandClz, true)); commandInvocation.setPostInterceptors(collectInterceptors(commandClz, false)); commandHub.getCommandRepository().put(commandClz, commandInvocation); }