系统融合,简单的说就是把多个系统合并成一个系统。
- 组件化方式:是在服务化的拆分基础上,提取可独立部署和多次服用的部分。一个是合并,一个拆分,看似矛盾,但两者却可以同时使用,相得益彰
- 插件化方式:解决解耦后的易扩展问题,面向的是单个服务或组件
组件化
组件化”的结果就是把系统作为一个个“组件”独立部署并对外服务,我理解的系统“组件化”,其实是对系统 “服务化”或 “微服务化”的另一种称呼罢了。区别在于“组件”是对外的“服务”,有些“服务”是私有的不能对外。
这里封装了一个组件名称为“组件1”,包含3个子服务系统,其中A服务对外开放,B、C服务是为了支持对外的A服务而存在的,但不对外开放。这里采用了“微服务”的思想把“组件1”拆分为三个子系统,有点类似java里的public方法和private方法,A系统对应public方法可以对外服务,B、C系统对应private方法只能在“组件1”内部被调用。这里所谓的服务都是通过RPC框架搭建的子系统。
小前台 + 大中台
新增一个“前台业务”,只要“中台系统”足够强大,新业务可以通过调用各个公共的“组件”采用类似搭“积木”的方式,快速完成一个新业务系统开发。这应该就是阿里所谓的“小前台”、“大中台”理论基础。
好处就是快速上线、快速试错,“前台系统”只需要投入少量人力成本,就可以快速完成新产品的研发和上线,根据市场的反应再做调整。
“组件化”与项目“微服务化”
前面提到的“前中台系统”建设,是站在公司组织架构层面来划分的。个人认为 在各自所在的项目组,也可以采用这种“组件化”的思路来进行子系统拆分,在项目组内有自己的“前中台”子系统,不管这个项目是否在组织架构上属于“前台”还是“后台”。在具体项目内部进行“前中台”子系统拆分,其实有点类似“微服务化”拆分
上图中的“jsf服务子工程集”中的每个子工程都可以作为“组件”来看待(只是这个组件只有1个工程,但根据业务需要对每工程还可以继续模块化拆分),属于“中台系统”。
上图中的“web服务子工程集”其实就对应各种业务系统,通过调用各种基础服务堆积而成,属于轻量化的“前台”系统。只要“jsf服务子工程集”中的“组件化”做得足够强大,我们就可以在项目组最大化的复用这些公共组件,更少的人力投入,快速的实现业务开发。
项目“组件化”架构案例
在这个项目“组件化”之前,是按照业务对系统进行划分,分为pc店铺、pc活动、m店铺、m活动,系统划分如下:
采用组件化的思想对系统架构进行改造,分别对前、后端都进行“组件化”提取,把公共的功能模块提取为“组件”单独部署。具体的业务系统调用这些公共组件达到复用的目的。改造后的系统架构如下:
插件化架构
todo
融合模式
两个系统融合,最大的困难就是接口不统一
比如同样是商品接口,A、B两个公司的接口名可能不同,商品类的定义也不同。这时为了让外部系统调用这两个接口无感知,就需要一个统一的接口,这就产生了适配器模式。
适配器模式
使用适配器模式进行系统融合
在“系统融合”的场景中会为同一个接口创建多个Adapter适配器(这里是两个),分别对应多个类似业务。这里以A、B两个电商系统融合为例,两套系统有数十个接口我们需要在A、B两个系统之上新建一个“适配器”系统。为了顺应现在的“前中台系统建设”潮流,设计架构上对前中台进行区分,整体架构调整如下:
在A、B两个系统没有融合前,他们都各自对应自己的前台系统,架构说明如下:
1、A、B两个公司合并前,都有各自对应的前台系统和中台系统。如图中“绿色箭头”所示。
2、现在A、B两个公司合并,为了降低维护成本,以及增加用户体验,只维护一个前台系统。为了在系统融合期间,外部用户可以正常访问A、B前台系统,这里增加一个“新前台系统”。
3、同时为了兼容老数据,A、B两个系统保持原样不变,新增一个“适配器系统”,对A、B两个系统中的公共业务接口进行适配。接口调用流程,如上图中“红色箭头所示”,统一后的“前台系统”首先调用“适配器系统”,根据参数适配到A或B系统中。
4、A、B两套系统在融合前 虽然业务类似,但也就自己的个性化业务,统一后的“前台系统”直接调用A、B系统原接口即可。如上图中的“紫色箭头”所示。
5、当“新前台系统”开发完成并上线后,即可关闭两个老的前台系统。只维护一套“新前台系统”即可
通过上述系统架构,即可快速完成新系统的融合,又不影响老系统的访问,为了防止老客户对新系统的不适应,还可以让“三个前台系统”并行运行一段时间。是不是有种“酷毙了”的赶脚。
这个强大的系统架构设计的核心就是设计新的“适配器系统”,这个系统里设计有多个数据接口(A、B系统公共的接口),每个接口都是采用“适配器模式”对A、B两个系统的接口进行封装,让“新的前台系统”以为是一个接口。
下面就以“商品接口”为例,对“适配器模式”进行讲解。
适配器示例--融合“商品接口”
根据上述新系统架构,主要分为4个系统:“A系统”、“B系统”、新“适配器系统”、新“前台系统”。作为示例不会把4个系统都搬出来,这里使用一个java application程序进行模拟,如下:
其中两个老系统的商品类ProductA、ProductB业务很类似:
public class ProductA {
private Integer a_id;//A系统 商品id
private String a_name;//A系统 商品名称
private String a_category;//A系统 商品分类
//省略其他setter、getter方法
}
public class ProductB {
private Integer b_id;//B系统 商品id
private String b_name;//B系统 商品名称
private String b_category;//B系统 商品分类
private Integer venderId;//商家Id
//省略其他setter、getter方法
}
ProductB中多一个成员变量venderId(商家Id)。现在要在新“适配器系统”中,定义新商品类Product,需要包含两个系统中所有业务,定义如下:
public class Product {
private Integer id;//新商品id
private String name;//新商品名称
private String category;//新商品分类
private Integer venderId;//商家Id
//省略其他setter getter方法
}
新商品对象定义完毕,现在进行接口“适配”,这里以A系统商品接口为例(B系统类似);已有的被适配角色ProdcutManagerA(接口)、ProdcutManagerAImpl(实现类):
public interface ProdcutManagerA {
ProductA getById(Integer id);
}
public class ProdcutManagerAImpl implements ProdcutManagerA{
public ProductA getById(Integer id){
ProductA productA = new ProductA();
productA.setA_id(1000);
productA.setA_name("A系统彩电");
productA.setA_category("A系统_家电类");
return productA;
}
}
新接口:新接口返回类型是新商品类Product:
/**
* 新商品管理接口类
* Created by gantianxing on 2017/11/4.
*/
public interface ProdcutAdapter {
//新商品接口
Product getById(Integer id);
}
/**
* A系统商品接口 适配器
* Created by gantianxing on 2017/11/4.
*/
public class ProdcutAdapterAImpl implements ProdcutAdapter {
private ProdcutManagerA prodcutManagerA;
public ProdcutAdapterAImpl(ProdcutManagerA prodcutManagerA){
this.prodcutManagerA = prodcutManagerA;
}
public Product getById(Integer id) {
ProductA productA = prodcutManagerA.getById(id);
if(productA != null){
Product product = new Product();
product.setId(productA.getA_id());
product.setName(productA.getA_name());
product.setCategory(productA.getA_category());
return product;
}
return null;
}
}
可以看到ProdcutAdapterAImpl适配器,把“A系统”商品接口 转换为“新前台系统适配的”接口。
结合RPC框架进行解耦
但在真实的系统中通过引入RPC框架和Spring IOC注入,“新前台系统”只会依赖一个“适配器”接口类:ProdcutAdapter;同时新建的“适配器系统”只依赖老A、B系统的接口类:ProdcutManagerA、ProdcutManagerB。如下图所示:
聚合类型兼容性问题
在两个系统融合过程中,还经常遇到另一种情况:A系统返回的商品列表是ArrayList类型,B系统返回的商品列表是数组类型。
//A系统获取商品列表接口
public List<Product> getProdoucts(){
//省略业务代码
}
//B系统获取商品列表接口
public Product [] getProdoucts(){
//省略业务代码
}
这就是所谓的“聚合类型兼容性问题”。这时为了统一接口类型,可以在“适配器系统”把ArrayList转换成数组,或者把数组转换成ArrayList。但这不是最优雅的方式,我们还可以使用“迭代器模式”对两个接口进行兼容。Java中得聚合类型:数组、List、Set、Map等。
迭代器模式
迭代器模式提供一种顺序访问一个聚合对象中的各个元素的方法,而又不暴露其内部的表象。把遍历聚合中各个元素的任务移交到“迭代器”上,满足OO设计原则中的“单一责任原则”。另外具体的“迭代器”都实现自一个统一的接口(Iterator),可以兼容不同的聚合类型遍历(这就是解决本文开头“兼容性”问题的关键)。
简单的理解,就是把聚合类型中遍历每个成员的任务剥离出来,生成“迭代器”,这些迭代器都实现自同一个接口。类图关系:
从类图上看,该模式主要有4类角色:
抽象的聚合:AbsAggregate,可以是抽象类 也可以是接口。一般都会定义一个抽象方法,获取迭代器。
具体的聚合:ConcreteAggregate,实现或继承自AbsAggregate。一般都会实现AbsAggregate中的抽象方法,获取具体的迭代器。
抽象的迭代器:Iterator可以是抽象类 也可以是接口。一般最少有两个抽象方法,hasNext()和next()方法,用于遍历聚合中的元素。
具体的迭代器:ConcreteIterator,实现或继承自Iterator。对hasNext()和next()方法进行具体的实现。其构造过程依赖“具体的聚合”,也就是说每个“具体的聚合”,一般都会对应一个自己 “具体的迭代器”。
示例展示
回到文章开头,开始使用“迭代器模式”对A、B两个系统融合过程中,对两个不同的获取商品列表接口进行融合。为了方便理解,实现过程按照“迭代器模式”的4类角色 分类进行:
抽象的聚合:本示例中抽象的聚合是ProdcutAdapter接口,里面只定义了一个createIterator()的抽象方法:
public interface ProdcutAdapter {
//批量获取商品接口
Iterator<Product> createIterator();
}
具体的聚合:
public class ProdcutAdapterAImpl implements ProdcutAdapter {
public Iterator<Product> createIterator() {
List<Product> products =getProdoucts();
Iterator iterator = new ListIterator(products);
return iterator;
}
}
public class ProdcutAdapterBImpl implements ProdcutAdapter {
public Iterator<Product> createIterator() {
Product [] array = getProdoucts();
Iterator iterator = new ArrayIterator(array);
return iterator;
}
}
抽象的迭代器:Iterator,这里只定义迭代器的两个核心方法 hasNext和next:
public interface Iterator<E> {
boolean hasNext();
Object next();
}
具体的迭代器:本示例中有两类聚合,List和数组。对应的会创建两个具体的迭代器:ListIterator、ArrayIterator
public class ListIterator<E> implements Iterator{
private List<E> products;
int pos=0;//当前位置
//外部迭代器,构造函数需要传入一个待迭代的集合类型
public ListIterator(List<E> products) {
this.products = products;
}
public boolean hasNext() {
if (pos >= products.size()) {
return false;
} else {
return true;
}
}
public Object next() {
E product = products.get(pos);
pos = pos + 1;
return product;
}
}
public class ArrayIterator<E> implements Iterator{
private E [] products;
int pos=0;//当前位置
//外部迭代器,构造函数需要传入一个待迭代的集合类型
public ArrayIterator(E[] products) {
this.products = products;
}
public boolean hasNext() {
if (pos >= products.length) {
return false;
} else {
return true;
}
}
public Object next() {
E product = products[pos];
pos = pos + 1;
return product;
}
}
Java中的迭代器:Java的API中对大部分的聚合类型都已经默认实现了自己的迭代器,统一实现自接口java.util.Iterator,相比本示例中定义的Iterator,java.util.Iterator多了一个remove方法。
Java api中几乎已为所有的聚合类型创建了自己的迭代器,并且都实现自java.util.Iterator接口。如果要扩展自定义聚合类型的迭代器,直接实现这个接口即可,这样做的好处是可以跟java api中的聚合类型的迭代器完全兼容。
Ref:
https://moon-walker.iteye.com/blog/2393310
https://www.jianshu.com/p/cbde4b00b7c6