一个内部API高度耦合的Java实现案例


某项目使用了“一套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
//common jar
public interface BaseTestRepo {

default public <T> T get(String key) {
//TODO
throw new NotImplementedException("not implement!");
}

default public void set(String key, byte[] value) {
//TODO
}
}

//couchbase.repo jar
@Repository("cbDataRepo")
public class CocuhbaseRepo implements BaseTestRepo {
}

//hbase.repo jar
@Repository("dataRepo")
public class HBaseRepo implements BaseTestRepo {
//TODO
}

//redis.repo jar
@Component("dataRepo")
public class RedisRepo implements BaseTestRepo {
//TODO
}

//serviceA
@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) {
//TODO
}
}

//serviceB
@Service
public class TestBService {
@Autowired
BaseTestRepo dataRepo;

@Autowired
BaseTestRepo cbDataRepo;

//通用API
public <T> T getEntity(String key) {
return dataRepo.get(key);
}

//通用API
public void setEntity(String key, Object value) {
dataRepo.set(key, toByte(value))
}

//TODO
}

上层业务模块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。

话说回来,这类还是场景考虑不全,实现的不够理想。