The problem: We have several lambdas and dynamodb tables we are using in production, when releasing a new version of our code we sometimes strip an attribute or add attributes to our table classes (Java code using com.amazonaws.services.dynamodbv2.datamodeling) High level api. When we deploy the new version of the code and we query the table, if an new attribute does not exist for an existing item, or we remove an attribute from the code. It breaks the code because our Item object is out of line with the production data.
We would like to avoid treating data in prod by adding extra attributes with a default value or removing attributes to existing items. Before we deploy the new version for a variety of reasons concerning race conditions and consistency. What would be preferable if we handled it at the code level, if the attribute does not exist automatically add it with a default value. Or have the code ignore attributes that are not defined in the item/table class. Is this possible using the high-level java sdk api?
The other solution we came up with was to create a service that is fed the delta (change between the code item object and data in prod), that is executed by a pretraffic lambda that treats the data when deploying a new version of the lambda. We would like to avoid this however.
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;
Above is our item that was changed, with added attribute.
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);
}
}
Above is our Dynamob DB mapper class, but with only the method in question. We were able to trace through logging up to the line logger.trace("DynamoDbRepository findBy(filter, class)");, and we know the issue occurs in the mapper. However it does not spit the exception up, so we are not able to see the actual error. We had to solve the issue by purging all the data from the tables in prod then have the new version of code repopulate the entries with the attribute and the code worked.