问题:我们在生产中使用了几个lambdas和dynamodb表,在发布我们的代码的新版本时,我们有时会剥离属性或向表类添加属性(使用com.amazonaws.services.dynamodbv2.datamodeling的Java代码)高水平的api。当我们部署新版本的代码并查询表时,如果现有项目不存在新属性,或者我们从代码中删除属性。它会破坏代码,因为我们的Item对象与生产数据不一致。
我们希望通过添加具有默认值的额外属性或删除现有项的属性来避免处理prod中的数据。在我们部署新版本之前,出于各种原因,包括竞争条件和一致性。如果我们在代码级别处理它会更好,如果该属性不存在,则会自动添加默认值。或者让代码忽略未在item / table类中定义的属性。这是否可以使用高级java sdk api?
我们提出的另一个解决方案是创建一个服务,该服务通过delta(代码项对象和prod中的数据之间的变化)来执行,该服务由预处理lambda执行,该lambda在部署新版本的lambda时处理数据。但是我们想避免这种情况。
package com.ourcompany.module.dynamodb.items;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBVersionAttribute;
import lombok.Data;
@Data
@DynamoDBTable(tableName = "Boxes")
public class BoxItem {
@DynamoDBHashKey(attributeName = "boxID")
private String channelID;
#This is the field we added, the previous version did not have this field, in prod we have many items without this attribute
@DynamoDBAttribute(attributeName = "lastTimeAccess")
private String lastTimeAccess;
@DynamoDBAttribute(attributeName = "initTime")
private String initTime;
@DynamoDBAttribute(attributeName = "boxIDhash")
private String streamBoxIDHash;
@DynamoDBAttribute(attributeName = "CFD")
private String cfd;
@DynamoDBAttribute(attributeName = "originDomain")
private String originDomain;
@DynamoDBAttribute(attributeName = "lIP")
private String lIP;
@DynamoDBAttribute(attributeName = "pDomain")
private String pDomain;
以上是我们改变的项目,添加了属性。
package com.ourcompany.shared.module.repository.dynamob;
import ...
public class DynamoDbRepository<Item, Key> {
private final DynamoDBMapper mapper;
private static final Logger logger = LogManager.getLogger(DynamoDbRepository.class);
@Inject
public DynamoDbRepository() {
val client = AmazonDynamoDBClientBuilder
.standard()
.withRegion(Regions.US_EAST_1) // TODO: hardcoded now
.withRequestHandlers(new TracingHandler(AWSXRay.getGlobalRecorder()))
.build();
DynamoDBMapperConfig dynamoDBMapperConfig = new DynamoDBMapperConfig.Builder()
.withSaveBehavior(DynamoDBMapperConfig.SaveBehavior.UPDATE_SKIP_NULL_ATTRIBUTES)
.withTableNameResolver(new DynamoDBTableNameResolver())
.build();
mapper = new DynamoDBMapper(client, dynamoDBMapperConfig);
}
/*
* Many accessor methods are listed here below is the one where we have issue,
*/
public List<Item> findBy(Map<String, Condition> filter, final Class<Item> clazz) throws Exception {
try {
logger.trace("DynamoDbRepository findBy(filter, class)");
val scanExpression = new DynamoDBScanExpression().withScanFilter(filter).withConsistentRead(true);
PaginatedScanList<Item> ls = mapper.scan(clazz, scanExpression);
ls.loadAllResults();
return ls;
} catch (Exception ex) {
logger.trace(ex.getMessage());
throw handleException(ex);
}
}
上面是我们的Dynamob DB映射器类,但只有相关方法。我们能够通过记录到logger.trace(“DynamoDbRepository findBy(filter,class)”)行来跟踪,我们知道映射器中出现了问题。但是它不会吐出异常,所以我们无法看到实际的错误。我们必须通过从prod中的表中清除所有数据来解决问题,然后让新版本的代码重新填充具有属性和代码的条目。
对于一个小窗口或者如果你进行长寿命拆分测试,你会遇到这个问题。
我们通过以下方式解决:
希望能帮助到你。
只是关于这个问题的更新。在尝试打印堆栈跟踪后获取@zapl建议后,我发现AWS DynamoDB Mapper或SDK的工作方式完全没有问题。我期待从SDK中捕获一些堆栈跟踪,并且在经过一些更仔细的跟踪之后我发现Java Devs误解了这个问题,真正的问题是它们有逻辑来过滤依赖于新字段的流。所以故事的教训,建筑师向后兼容的代码至少有一个版本落后!