Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support optional prefix for @DynamoDbFlatten fields #3695

Open
2 tasks done
akiesler opened this issue Jan 16, 2023 · 4 comments
Open
2 tasks done

Support optional prefix for @DynamoDbFlatten fields #3695

akiesler opened this issue Jan 16, 2023 · 4 comments
Labels
dynamodb-enhanced feature-request A feature should be added or improved. p3 This is a minor priority issue

Comments

@akiesler
Copy link
Contributor

akiesler commented Jan 16, 2023

Describe the feature

In order to provide better clarity to field names in Flattened DynamoDdBeans we need to be able to prefix field names with additional context. I propose adding an optional String prefix to the DynamoDbFlatten annotation that would allow users to supply a prefix that would be appended to flatten field names in the parent object. Leaving the property unset would result in the current behavior of no prefixing in order to maintain backwards compatability.

Use Case

I want to flatten multiple objects of the same type but with different meanings and not have their names conflict.

// Record.java

import java.time.Instant;

import lombok.Data;
import lombok.Getter;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlatten;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;

@Data
@DynamoDbBean
public class Record {
    @Getter(onMethod_ = {@DynamoDbPartitionKey})
    String id;
    @Getter(onMethod_ = {@DynamoDbFlatten})
    Edit created;
    @Getter(onMethod_ = {@DynamoDbFlatten})
    Edit updated;

    @Data
    @DynamoDbBean
    public static class Edit {
        String id;
        Instant timestamp;
    }
}

Proposed Solution

Extend the existing FlattenedMapper class to include an optional prefix that would be appended to each attribute when building the map of mappers.

public @interface DynamoDbFlatten {
    String prefix default "";
}
DynamoDbFlatten dynamoDbFlatten = getPropertyAnnotation(propertyDescriptor, DynamoDbFlatten.class);

if (dynamoDbFlatten != null) {
  builder.flatten(TableSchema.fromClass(propertyDescriptor.getter().getReturnType()),
                  getterForProperty(propertyDescriptor, immutableClass),
                  setterForProperty(propertyDescriptor, builderClass),
                  dynamoDbFlatten.prefix());
} else { ... }
flattenedMapper.otherItemTableSchema.attributeNames().forEach(
    attrName -> {
        final String attributeName = flattenedMapper.prefix + attrName
        if (mutableAttributeNames.contains(attributeName)) {
            throw new IllegalArgumentException(
                "Attempt to add an attribute to a mapper that already has one with the same name. " +
                    "[Attribute name: " + attributeName + "]");
        }

        mutableAttributeNames.add(attributeName);
        mutableFlattenedMappers.put(attributeName, flattenedMapper);
    }
);

Other Information

Here is an example test written in Spock:

// Record.java

import java.time.Instant;

import lombok.Data;
import lombok.Getter;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlatten;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;

@Data
@DynamoDbBean
public class Record {
    @Getter(onMethod_ = {@DynamoDbPartitionKey})
    String id;
    @Getter(onMethod_ = {@DynamoDbFlatten})
    Edit created;
    @Getter(onMethod_ = {@DynamoDbFlatten})
    Edit updated;

    @Data
    @DynamoDbBean
    public static class Edit {
        String id;
        Instant timestamp;
    }
}
// DynamoDBSpec.groovy

import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable
import software.amazon.awssdk.enhanced.dynamodb.TableSchema
import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedTimestampRecordExtension
import software.amazon.awssdk.enhanced.dynamodb.extensions.VersionedRecordExtension
import software.amazon.awssdk.services.dynamodb.local.embedded.DynamoDBEmbeddedRule
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
import spock.lang.Shared
import spock.lang.Specification

import java.time.Instant

class DynamoDBSpec extends Specification {
    @Shared
    DynamoDbClient dynamoDbClient = DynamoDbEnhancedClient.create()
    @Shared
    DynamoDbEnhancedClient dynamoDb = DynamoDbEnhancedClient.builder()
            .dynamoDbClient(dynamoDbClient())
            .build()
    @Shared
    DynamoDbTable<Record> recordsTable = dynamoDb.table("Records", TableSchema.fromClass(Record.class))

    void setup() {
        recordsTable.createTable()
    }

    void cleanup() {
        recordsTable.deleteTable()
    }

    def "flattened records should not conflict"() {
        given:
        def created = new Record.Edit().tap {
            id = "Creating User"
            timestamp = Instant.EPOCH
        }
        def updated = new Record.Edit().tap {
            id = "Updating User"
            timestamp = Instant.EPOCH.plusSeconds(10L)
        }
        def record = new Record().tap {
            id = "Record ID"
            it.created = created
            it.updated = updated
        }

        when:
        recordsTable.putItem(record)

        then:
        def item = recordsTable.getItem(record)

        and:
        item == record
    }

}
// Stacktrace

Attempt to add an attribute to a mapper that already has one with the same name. [Attribute name: id]
java.lang.IllegalArgumentException: Attempt to add an attribute to a mapper that already has one with the same name. [Attribute name: id]
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema.lambda$null$2(StaticImmutableTableSchema.java:187)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at java.base/java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1085)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema.lambda$new$3(StaticImmutableTableSchema.java:184)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema.<init>(StaticImmutableTableSchema.java:182)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema.<init>(StaticImmutableTableSchema.java:80)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema$Builder.build(StaticImmutableTableSchema.java:429)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema.<init>(StaticTableSchema.java:69)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema.<init>(StaticTableSchema.java:67)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema$Builder.build(StaticTableSchema.java:259)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.createStaticTableSchema(BeanTableSchema.java:218)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:138)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:129)
	at software.amazon.awssdk.enhanced.dynamodb.TableSchema.fromBean(TableSchema.java:83)
	at software.amazon.awssdk.enhanced.dynamodb.TableSchema.fromClass(TableSchema.java:126)
	at com.amazon.aet.comms.configuration.utils.DynamoDBSpec.setupSpec(DynamoDBSpec.groovy:29)

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

AWS Java SDK version used

2.19.17

JDK version used

openjdk version "1.8.0_352"

Operating System and version

macOS 12.6.1

@akiesler akiesler added feature-request A feature should be added or improved. needs-triage This issue or PR still needs to be triaged. labels Jan 16, 2023
@debora-ito debora-ito added the needs-review This issue or PR needs review from the team. label Jan 23, 2023
@debora-ito
Copy link
Member

Acknowledged. Added this to our backlog.

@akiesler thank you for reaching out.

@debora-ito debora-ito added p3 This is a minor priority issue dynamodb-enhanced and removed needs-triage This issue or PR still needs to be triaged. needs-review This issue or PR needs review from the team. labels Jan 23, 2023
@CaptainDaVinci
Copy link

+1 This would be a very useful feature for our use case as well.

@adampoplawski
Copy link

@debora-ito any update?

@akiesler
Copy link
Contributor Author

@CaptainDaVinci @adampoplawski Please provide your input and thoughts on the pull request. #5601

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dynamodb-enhanced feature-request A feature should be added or improved. p3 This is a minor priority issue
Projects
None yet
Development

No branches or pull requests

4 participants