某项目使用了“一套API,多种底层存储”的设计方式,可以灵活切换多种数据源,但同时能够保持暴露的API接口定义一致。这种设计方式,优点是显而易见的。
随着业务的增长,数据量越来越大。单种类存储已经捉襟见肘,需要依赖多种存储共同保障服务的状况下,早期高度耦合的内部API实现使得存储的改造工作量增大了不少。
以某项目为例,原本业务数据量没那么大,选择了Redis作为线上业务的主要存储,以Couchbase为辅,同时利用HBase实现了一套在线冷备服务,其中RedisAPI和HBaseAPI互斥。但随着数据量的突飞猛进,Redis的容量已不堪重负,计划全部迁移到容量更大的分布性缓存Couchbase。同时,在Couchbase存储未命中时,回源到HBase服务进行兜底读取。
由于上层业务逻辑基于同一套存储API实现的,为同时使用多种存储,要求对这套存储API进行改造。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| public interface BaseTestRepo {
default public <T> T get(String key) { throw new NotImplementedException("not implement!"); }
default public void set(String key, byte[] value) { } }
@Repository("cbDataRepo") public class CocuhbaseRepo implements BaseTestRepo { }
@Repository("dataRepo") public class HBaseRepo implements BaseTestRepo { }
@Component("dataRepo") public class RedisRepo implements BaseTestRepo { }
@Service public class TestAService { @Autowired BaseTestRepo dataRepo;
@Autowired BaseTestRepo cbDataRepo;
public <T> T getEntity(String key) { throw new NotImplementedException("not implement!"); }
public void setEntity(String key, Object value) { } }
@Service public class TestBService { @Autowired BaseTestRepo dataRepo;
@Autowired BaseTestRepo cbDataRepo;
public <T> T getEntity(String key) { return dataRepo.get(key); }
public void setEntity(String key, Object value) { dataRepo.set(key, toByte(value)) }
}
上层业务模块A需要依赖redis的高效部署业务,则可以引入service.jar、redis.repo jar、common.jar、couchbase.repo jar来实现; 上层业务模块B需要依赖hbase来实现离线任务,则可引入service.jar、hbase.repo jar、common.jar、couchbase.repo jar来实现。
|
如上所示,上层业务模块只需要按需导入hbase.repo jar或者redis.repo jar,然后利用Spring自动注入机制,获取dataRepo实例,基于同一套get/set API进行存储的读写操作。对上层业务而言,它是无需感知底层业务代码使用了哪一套存储。
此时,若将RedisRepo全部迁移到CouchbaseRepo,则会涉及到dataRepoAPI的使用变更,redis部分变化,但必须保持HBase不变。
解决方案:
1、新增Proxy,将底层RepoAPI再加一层代理,按需路由到不同的repo实现。
2、基于Spring Condition条件化构建Repo Bean。
话说回来,这类还是场景考虑不全,实现的不够理想。