DynamoDB笔记1-Table Prefix

摘要: 本文介绍如何使用 sping-data-dynamodb 实现不同的 Profile 创建带有不同前缀的 table。

需求介绍

需求很简单,因为我们知道每个帐户中 DynamoDB 在每个 Region 都只有一个节点,也就是说当我们确定了 Region ,无论我们需要在该 Region 上部署多少个应用,其最终都是连接到一个 DynamoDB 上,不同的是,需要用 Table 来隔离不同的应用。Table 在 DynamoDB 中有点类似于 MySQL 中的 Database。

所以在这种前提上我们就有一个场景需要考虑:我们需要针对同一个应用搭建不同的版本,比如测试版本和线上版本,这时候就会存在一个问题:Table 的名字一样,DynamoDB 无法创建两个名字一样的表。

当然简单粗暴的解决方法是直接选用不同的 Region,但是这样管理起来很麻烦,同时 EC2 的选择变得困难,经过测试跨 Region 访问 DynamoDB 不如同一个 Region 速度快。

对于 DynamoDB 来说,该问题的最佳的解决方法就是:Table Name 增加前缀来标识同一个应用的不同的环境。

比如我们有一个表: User 表,那么最后我们部署后的两个表可以为:

  1. User: default 表名用于线上版本
  2. qaUser: 加 `qa` 前缀,用于 QA 版本

下面我们介绍如何使用 sping-data-dynamodb 配置 Spring 的 Profile 来实现

原理

基本文档参考 :https://github.com/derjust/spring-data-dynamodb/wiki/Alter-table-name-during-runtime

默认 Table Name 是静态字符串,定义在注解上:

1
2
3
4
@DynamoDBTable(tableName = "someEntityTableName")
public class SomeEntity {
...
}

但是 AWS SDK DynamoDBMapperConfig 提供了更改表名的能力:

1
2
3
4
5
/**  
* @param value the new table name override
*/public void setTableNameOverride(TableNameOverride value) {
tableNameOverride = value;
}

其中的 TableNameOverride 就提供了更改前缀的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
/**  
* Allows overriding the table name declared on a domain class by the * {@link DynamoDBTable} annotation.
*/
public static final class TableNameOverride {
private final String tableNameOverride;
private final String tableNamePrefix;
/**
* Returns a new {@link TableNameOverride} object that will prepend the
* given string to every table name. */public static TableNameOverride withTableNamePrefix(
String tableNamePrefix) {
return new TableNameOverride(null, tableNamePrefix);
}
}

所以我们只需要在不同的 Profile 的 Spring Configuration中配置不同的前缀就可以了。

代码实践

我们假设我们已经有了一个 dev 的 profile,这个 dev profile 使用默认的表名,然后我们需要创建一个 dev2 的 profile,表名前缀为 dev2,他们都连接到同一个本地的 DynamoDB 节点: localhost:8000

dev2 Profile yaml:

1
2
3
4
5
6
amazon:  
dynamodb:
endpoint: http://localhost:8000
aws:
accesskey: key
secretkey: key2

DynamoDBDev2Configuration.java

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
@Configuration  
@Profile({"dev2"})
@EnableDynamoDBRepositories(
//dynamoDBMapperConfigRef = "dynamoDBMapperConfig",
basePackages = "science.mengxin.repository")
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class DynamoDBDev2Configuration {

@Value("${amazon.dynamodb.endpoint}")
private String amazonDynamoDBEndpoint;

@Value("${amazon.aws.accesskey}")
private String amazonAWSAccessKey;

@Value("${amazon.aws.secretkey}")
private String amazonAWSSecretKey;

@Bean
public AmazonDynamoDB amazonDynamoDB() {
if (!StringUtils.isEmpty(amazonDynamoDBEndpoint)) {
return AmazonDynamoDBClientBuilder.standard()
.withCredentials(amazonAwsCredentialsProvider())
.withEndpointConfiguration(
new EndpointConfiguration(amazonDynamoDBEndpoint, Regions.EU_WEST_2.getName()))
.build();
} else {
return AmazonDynamoDBClientBuilder.standard()
.withCredentials(amazonAwsCredentialsProvider())
.withRegion(Regions.EU_WEST_2)
.build();
}
}
@Bean
public AWSCredentials amazonAwsCredentials() {
return new BasicAWSCredentials(amazonAWSAccessKey, amazonAWSSecretKey);
}

private AWSCredentialsProvider amazonAwsCredentialsProvider() {
return new AWSStaticCredentialsProvider(amazonAwsCredentials());
}

// https://github.com/derjust/spring-data-dynamodb/issues/233
@Primary
@Bean public DynamoDBMapperConfig dynamoDBMapperConfig() {
// Create empty DynamoDBMapperConfig builder
DynamoDBMapperConfig.Builder builder = new DynamoDBMapperConfig.Builder();
// Inject the table name overrider bean
builder.setTableNameOverride(tableNameOverrider());
// Sadly this is a @deprecated method but new DynamoDBMapperConfig.Builder() is incomplete
// compared to DynamoDBMapperConfig.DEFAULT. Actually, this method is merge the DEFAULT to the // new build config. because the merge is private, so we cannot invoke this method directly. // seems like even the AWS deprecated this Constructor, but this is no corresponding method to // make a default builder and customisation.
return new DynamoDBMapperConfig(DynamoDBMapperConfig.DEFAULT, builder.build());
}

@Bean
public DynamoDBMapperConfig.TableNameOverride tableNameOverrider() {
// Use @Value to inject values via Spring or use any logic to define the table prefix
String prefix = "dev2";
return DynamoDBMapperConfig.TableNameOverride.withTableNamePrefix(prefix);
}

@Primary
@Bean public DynamoDBMapper dynamoDBMapper(AmazonDynamoDB amazonDynamoDB, DynamoDBMapperConfig config) {
return new DynamoDBMapper(amazonDynamoDB, config);
}
}

这里我们要注意两个问题:

  1. 原文档中在 EnableDynamoDBRepositories 注解中加入了 dynamoDBMapperConfigRef = "dynamoDBMapperConfig",但是经过测试这个属性会导致启动失败,原因是无法创建 Repository。所以删除了这个属性,使用默认值。
  2. DynamoDBMapperConfig(DynamoDBMapperConfig.DEFAULT, builder.build()) is deprecated,虽然是弃用,但是这里我们希望基于 DynamoDBMapperConfig.DEFAULT 做一些修改,目前没有提供类似的功能,因为其中 Builder 中有一个 merge 方法,但是该方法是 private,而这个弃用的构造函数正是依靠这个 private 的merge 实现了。貌似 AWS 推荐完全通过 builder 自己构建。 p.s. 建议增加一个 toBuilder 的方法或者一个构造 DynamoDBMapperConfig.DEFAULT builder 的方法。