aws / aws/aws-sdk-java-v2

Support optional prefix for @DynamoDbFlatten fields

Aperta
#3,695 4 commenti 5 reazioni 0 assegnatari Vedi su GitHub
dynamodb-enhanced feature-request p3
Lingua principale
Java
Stelle
2.6k
Fork
1k
Merge medio
2g 9h
PR unite (30g)
51

Descrizione

### Describe the feature

In order to provide better clarity to field names in Flattened `DynamoDdBean`s 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.

```java
// 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.

```java
public @interface DynamoDbFlatten {
String prefix default "";
}
```

```java
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 { ... }
```

```java
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](https://spockframework.org/spock/docs/2.3/spock_primer.html):

```java
// 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;
}
}
```

```java
// 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 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
}

}
```

```java
// 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.(StaticImmutableTableSchema.java:182)
at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema.(StaticImmutableTableSchema.java:80)
at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema$Builder.build(StaticImmutableTableSchema.java:429)
at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema.(StaticTableSchema.java:69)
at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema.(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

- [x] I may be able to implement this feature request
- [x] 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

Guida per i contributori

Apri la guida per i contributori

Direzione di ricerca

Inizia leggendo l’annotazione DynamoDbFlatten e il percorso FlattenedMapper esistente, quindi traccia la collisione di StaticImmutableTableSchema mostrata nello stack trace. Usa l’esempio Record/Edit fornito come copertura di regressione; il lavoro è completato quando due campi appiattiti dello stesso tipo possono usare prefissi distinti lasciando invariato il comportamento attuale senza prefisso.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
aws, java
Ambito
databases
Tipo di issue
Funzionalità
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.