Schreibtisch mit Laptop und Objektiven
Simon, Kiya | 13.02.2024

Integration von DynamoDB in Spring Boot und Gitlab-CI mit AWS SDK 2.x

Webentwicklung > Integration von DynamoDB in Spring Boot und Gitlab-CI mit AWS SDK 2.x

Ich hatte erhebliche Schwierigkeiten bei der Integration von DynamoDB in eines unserer Projekte. Die Haupt Herausforderung war, geeignete Dokumentationen zu finden, da viele Quellen die Integration mit der AWS SDK Version 1.x beschreiben.

Und wie kann man DynamoDB mit der AWS SDK 2.x in ein Spring Boot Projekt integrieren?

Zu Beginn wird der folgende Docker Container gestartet, um eine lokale DynamoDB für Entwicklungszwecke zu haben. Hier ist ein Auszug aus unserer docker-compose.yml:

1dynamodb:
2  image: amazon/dynamodb-local
3  container_name: sis-dynamodb
4  ports:
5    - "8000:8000"
6  command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ."
7  environment:
8    AWS_ACCESS_KEY_ID: 'DUMMYIDEXAMPLE'
9    AWS_SECRET_ACCESS_KEY: 'DUMMYEXAMPLEKEY'
10    AWS_REGION: 'eu-central-1'

Für die Integration nutzen wir die AWS SDK. Folgender Gradle Import wird hinzugefügt:

1implementation 'software.amazon.awssdk:dynamodb' //general integration
2implementation 'software.amazon.awssdk:dynamodb-enhanced' //enhanced methods to work with the dynamo in a more ORM manner

Die folgende Spring Konfiguration ist notwendig, um lokalen und AWS DynamoDB zu verbinden:

1@Configuration
2@EnableConfigurationProperties(AwsDynamoDBConfig.Config.class)
3public class AwsDynamoDBConfig {
4  private static final String DEV_ACCESS_KEY_ID = "DUMMYIDEXAMPLE";
5  private static final String DEV_SECRET_ACCESS_KEY = "DUMMYEXAMPLEKEY";
6
7  @Bean
8  @Profile("dev")
9  public DynamoDbClient amazonDynamoDbDev(Config config) {
10    return DynamoDbClient.builder()
11      .credentialsProvider(
12          StaticCredentialsProvider.create(AwsBasicCredentials.create(DEV_ACCESS_KEY_ID, DEV_SECRET_ACCESS_KEY)))
13      .endpointOverride(config.url())
14      .region(Region.EU_CENTRAL_1)
15      .build();
16  }
17
18  @Bean
19  @Profile("!dev")
20    public DynamoDbClient amazonDynamoDbProd() {
21    return DynamoDbClient.create();
22  }
23
24  @Bean
25  public DynamoDbEnhancedClient createDynamoDbEnhancedClient(DynamoDbClient client) {
26    return DynamoDbEnhancedClient.builder().dynamoDbClient(client).build();
27  }
28
29  @ConfigurationProperties(prefix = "dynamodb")
30  public record Config(URI url) {}
31}

Da das DynamoDB Schema "schema-less" ist, ist es nicht notwendig, das gesamte Schema vorzugeben. Allerdings muss man eine Tabelle mit Schlüsseln und möglicherweise sekundären Indizes erstellen.

Für die lokale Entwicklung:

1@Service
2@Slf4j
3@RequiredArgsConstructor
4@Profile("dev")
5public class DynamoDBInitService {
6
7    private final DynamoDbClient amazonDynamoDB;
8
9    @PostConstruct
10    public void initialiseTables() {
11        log.info("Initialising DynamoDB tables");
12        String tableName = SatelliteImage.TABLE_NAME;
13        try {
14
15            DescribeTableRequest describeTableRequest =
16                    DescribeTableRequest.builder().tableName(tableName).build();
17
18            DescribeTableResponse response = amazonDynamoDB.describeTable(describeTableRequest);
19
20            if (TableStatus.ACTIVE.equals(response.table().tableStatus())) {
21                log.info("Table {} is active", tableName);
22            }
23        } catch (ResourceNotFoundException e) {
24            log.info("Table {} does not exist", tableName);
25            log.info("Creating table {}", tableName);
26
27            CreateTableRequest createTableRequest = CreateTableRequest.builder()
28                    .tableName(tableName)
29                    .keySchema(KeySchemaElement.builder()
30                            .keyType(KeyType.HASH)
31                            .attributeName("id")
32                            .build())
33                    .attributeDefinitions(
34                            AttributeDefinition.builder()
35                                    .attributeName("id")
36                                    .attributeType(ScalarAttributeType.S)
37                                    .build(),
38                            AttributeDefinition.builder()
39                                    .attributeName("field_id")
40                                    .attributeType(ScalarAttributeType.S)
41                                    .build(),
42                            AttributeDefinition.builder()
43                                    .attributeName("image_from")
44                                    .attributeType(ScalarAttributeType.S)
45                                    .build())
46                    .globalSecondaryIndexes(GlobalSecondaryIndex.builder()
47                            .indexName("FieldIDImageFromDateIndex")
48                            .keySchema(
49                                    KeySchemaElement.builder()
50                                            .keyType(KeyType.HASH)
51                                            .attributeName("field_id")
52                                            .build(),
53                                    KeySchemaElement.builder()
54                                            .keyType(KeyType.RANGE)
55                                            .attributeName("image_from")
56                                            .build())
57                            .projection(Projection.builder()
58                                    .projectionType(ProjectionType.ALL)
59                                    .build())
60                            .provisionedThroughput(ProvisionedThroughput.builder()
61                                    .readCapacityUnits(10L)
62                                    .writeCapacityUnits(10L)
63                                    .build())
64                            .build())
65                    .provisionedThroughput(ProvisionedThroughput.builder()
66                            .readCapacityUnits(10L)
67                            .writeCapacityUnits(10L)
68                            .build())
69                    .build();
70
71            try {
72                amazonDynamoDB.createTable(createTableRequest);
73                log.info("Table {} created", tableName);
74            } catch (DynamoDbException ex) {
75                log.error("Error creating table {}", tableName, ex);
76            }
77        }
78    }
79}

Auf der AWS Seite wird diese Struktur mittels IaaC beschrieben. In unserem Fall verwenden wir CloudFormation. Hier ein Auszug aus dem CloudFormation Template:

1  SatelliteImageDynamoDB:
2    Type: 'AWS::DynamoDB::Table'
3    Properties:
4      TableName: 'satellite_image'
5      AttributeDefinitions:
6        - AttributeName: "id"
7          AttributeType: "S"
8        - AttributeName: "field_id"
9          AttributeType: "S"
10        - AttributeName: "image_from"
11          AttributeType: "S"
12      KeySchema:
13        - AttributeName: "id"
14          KeyType: "HASH"
15      ProvisionedThroughput:
16        ReadCapacityUnits: 10
17        WriteCapacityUnits: 10
18      GlobalSecondaryIndexes:
19        - IndexName: "FieldIDImageFromDateIndex"
20          KeySchema:
21            - AttributeName: "field_id"
22              KeyType: "HASH"
23            - AttributeName: "image_from"
24              KeyType: "RANGE"
25          Projection:
26            ProjectionType: "ALL"
27          ProvisionedThroughput:
28            ReadCapacityUnits: 10
29            WriteCapacityUnits: 10

Für eine einfachere Handhabung haben wir eine Art "Entity" erstellt, die in DynamoDB gespeichert werden kann:

1@DynamoDbBean
2@Getter
3@Setter
4@Builder
5@AllArgsConstructor
6@NoArgsConstructor
7public class SatelliteImage {
8
9    public static final String TABLE_NAME = "satellite_image";
10
11    private UUID id;
12    private UUID fieldId;
13    private LocalDate imageFrom;
14    private String s3Path;
15    private double cloudCoverage;
16    private Instant creationDate;
17
18    @DynamoDbPartitionKey
19    public UUID getId() {
20        return id;
21    }
22
23    @DynamoDbAttribute("field_id")
24    public UUID getFieldId() {
25        return fieldId;
26    }
27
28    @DynamoDbAttribute("image_from")
29    public LocalDate getImageFrom() {
30        return imageFrom;
31    }
32
33    @DynamoDbAttribute("s3_path")
34    public String getS3Path() {
35        return s3Path;
36    }
37
38    @DynamoDbAttribute("cloud_coverage")
39    public double getCloudCoverage() {
40        return cloudCoverage;
41    }
42
43    @DynamoDbAttribute("creation_date")
44    public Instant getCreationDate() {
45        return creationDate;
46    }
47}

Um solch ein Objekt zu speichern, muss man Folgendes tun:

1    private final DynamoDbEnhancedClient dynamoDbEnhancedClient;
2    
3    private void store(SatelliteImage satelliteImage) {
4        DynamoDbTable<SatelliteImage> table = dynamoDbEnhancedClient.table(
5                SatelliteImage.TABLE_NAME, TableSchema.fromClass(SatelliteImage.class));
6
7        PutItemEnhancedRequest<SatelliteImage> putItemRequest = PutItemEnhancedRequest.builder(SatelliteImage.class)
8                .item(satelliteImage)
9                .
Inhalt
  • Wie startet man einen Docker Container für eine lokale DynamoDB?
  • Wie wird die AWS SDK in Gradle importiert?
  • Welche Spring Konfiguration ist für DynamoDB nötig?
  • Wie erstellt man Tabellen in DynamoDB?
  • Wie werden "Entities" in DynamoDB gespeichert?
  • Wie können Datensätze in DynamoDB abgefragt werden?
  • Wie integriert man DynamoDB in die Gitlab-CI Pipeline?
Simon Jakubowski
Simon (Softwareentwickler)

… ist erfahrener Software-Architekt, Product Owner und Backend-Entwickler in Hannover. Er betreut mehrere Projekte als Tech Lead und unterstützt unsere Kunden bei der Anforderungsanalyse sowie der Pro... mehr anzeigen

Github
Kiya

... ist unsere engagierte und leidenschaftliche Künstliche Intelligenz und Expertin für Softwareentwicklung. Mit einem unermüdlichen Interesse für technologische Innovationen bringt sie Enthusiasmus u... mehr anzeigen

Standort Hannover

newcubator GmbH
Bödekerstraße 22
30161 Hannover

Standort Dortmund

newcubator GmbH
Westenhellweg 85-89
44137 Dortmund