Blog

Effizientes Entwerfen Ihrer DynamoDB-Tabellen und Modellierung gemischter Datentypen mit Kotlin

Benjamin Komen

Aktualisiert Oktober 20, 2025
6 Minuten

AWS (Amazon Web Services) bietet eine ziemlich ordentliche NoSQL-Datenbank namens DynamoDB. Sie ist schnell und kann skalieren, was will man mehr? Die Sache ist die, dass Sie als Entwickler immer noch dafür verantwortlich sind, Ihre Tabellen so zu gestalten, dass Sie die Vorteile, die DynamoDB zu bieten hat, auch tatsächlich angemessen nutzen. Wenn Sie einfach nur Ihr Wissen anwenden, das Sie mit anderen Datenbanken erworben haben, könnten Sie am Ende Geld und Leistung verschwenden.

Auswahl des besten Schlüssels für die Abfrage

DynamoDB kann als ein Key-Value-Datenspeicher beschrieben werden. Jede Datenzeile besteht aus einem Schlüssel, der indiziert ist, und einem oder mehreren Werten, die nicht unbedingt indiziert sind. Da es am effizientesten ist, die Datenbank mit diesem indizierten Schlüssel abzufragen, ist es wichtig, den besten Schlüssel für die Abfrage zu wählen. Bei einigen Datenmodellen ist dies recht einfach. Wenn Sie zum Beispiel Bestellungen oder Kunden in einer Tabelle speichern möchten und diese eine eindeutige ID haben, wird dieses Feld natürlich als Primärschlüssel verwendet.

Bei der Modellierung von eins-zu-vielen Datenbeziehungen ist dies jedoch nicht mehr so einfach. Es gibt verschiedene Möglichkeiten, dieses Problem zu lösen, aber ich werde eine Strategie demonstrieren, die meiner Meinung nach gut funktioniert. Weitere Informationen zu den verschiedenen Ansätzen finden Sie z.B. hier.

Zusammengesetzter Primärschlüssel

In DynamoDB kann der Primärschlüssel einer Tabelle aus mehreren Spalten zusammengesetzt sein. Betrachten wir das folgende Beispiel mit einem zusammengesetzten Primärschlüssel, der aus einer Partition und einem Sortierschlüssel besteht.

Beispiel DynamoDB-Tabelle - Kapazität der Einrichtung
Beispiel DynamoDB-Tabelle - Kapazität der Einrichtung

Diese Beispieltabelle modelliert die Verarbeitungskapazität bestimmter Einrichtungen und besteht aus vier Spalten:

  • Betriebsnummer: eine eindeutige Identifikationsnummer für jeden Betrieb;
  • Zeitpunkt: entweder ein Wochentag für reguläre Kapazität oder ein bestimmtes Datum für außergewöhnliche Kapazität;
  • Kapazität: die Menge an Produkten, die in der Anlage an einem bestimmten Tag verarbeitet werden kann;
  • Beschreibung: ein optionales Feld, um zu beschreiben, warum an diesem Datum eine außergewöhnliche Kapazität vorhanden ist.

Mischen von Datentypen in einer Spalte

Wenn Sie sich die zweite Spalte, Moment, ansehen, könnten Sie denken: "Moment mal, warum sollten Sie jemals mehrere Datentypen in einer einzigen Spalte mischen?" Das ist nicht die Art und Weise, wie Sie die Daten in einer relationalen Datenbank wie MySQL modellieren würden. Und auch nicht, wie Sie sie in einer Dokumentendatenbank wie MongoDB anlegen würden.

Die erste Spalte ist in dieser Tabelle nicht eindeutig. Sie kann der Partitionsschlüssel (auch Hash-Schlüssel genannt) sein, aber nicht der Primärschlüssel. Zusammen mit dem Sortierschlüssel (auch Bereichsschlüssel genannt) ist sie jedoch immer eindeutig, da jeder Wochentag oder jedes Datum nur einmal vorkommt. Wenn wir den Wochentag und das Datum in separaten Spalten untergebracht hätten, müsste unser Primärschlüssel aus drei Spalten bestehen, was nicht zulässig ist. Deshalb haben wir diesen speziellen Primärschlüssel gewählt, der aus der Nummer der Einrichtung als Partitionsschlüssel und einer Mischung aus Wochentag und Datum als Sortierschlüssel besteht.

Von der Theorie zur Praxis

Theoretisch könnten Sie diese DynamoDB-Tabelle so gestalten. Schauen wir uns nun an, wie Sie dies in der Praxis umsetzen. Letztendlich wird es Ihr Code sein, der Datensätze in Ihrer DynamoDB-Tabelle liest und schreibt. Die Struktur der Tabelle muss auch im Code ausgedrückt werden. Ich werde dies mit Kotlin unter Verwendung des AWS SDK für Java demonstrieren, aber es können auch andere Programmiersprachen verwendet werden.

Modellierung der DynamoDB-Tabelle in Kotlin

Wie können wir dies in Kotlin modellieren? Schauen wir uns den folgenden Codeschnipsel an.

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverted
@DynamoDBTable(tableName = "facility-capacity")
data class FacilityCapacity(
  @DynamoDBHashKey
  var facilityId: String? = null,
  @DynamoDBRangeKey
  @DynamoDBTypeConverted(converter = MomentConverter::class)
  @DynamoDBAttribute
  var moment: Moment? = null,
  @DynamoDBAttribute
  var capacity: Long? = null,
  @DynamoDBAttribute
  var description: String? = null,
)

Die Tabelle wird als Datenklasse modelliert und die Spalten als Felder der Klasse. Das Feld facilityId ist als Hash-Schlüssel (oder Partitionsschlüssel) und das Feld moment als Bereichsschlüssel (oder Sortierschlüssel) vermerkt. Die anderen Felder sind einfach Attribute.

Normalerweise würden wir das Schlüsselwort val für Felder in Datenklassen verwenden, um sie als unveränderlich zu kennzeichnen, aber aufgrund von Einschränkungen bei der Initialisierung dieser Daten durch das AWS Java SDK ist das leider nicht so einfach möglich.

Model union type in Kotlin

Schauen wir uns das Feld moment genauer an. Aus unserem Tabellenentwurf wissen wir, dass es entweder ein Wochentag (z.B. Dienstag) oder ein Datum (z.B. 13. März 2021) sein kann. Wenn ein Feld mehrere Typen sein kann, nennen wir dies einen Unionstyp. Sie haben dieses Konzept vielleicht schon in anderen Programmiersprachen kennengelernt, z.B. in Typescript. Wie modellieren wir dies in Kotlin?

Versiegelte Klassen verwenden

Die Antwort lautet: mit versiegelten Klassen. Werfen wir einen Blick auf den folgenden Codeschnipsel.

import java.time.DayOfWeek
import java.time.LocalDate
sealed class Moment
data class WeekDay(
  val dayOfWeek: DayOfWeek
) : Moment()
data class ExceptionDate(
  val date: LocalDate,
) : Moment()

Die versiegelte Klasse ist eine Art abstrakte Klasse, die mehrere Implementierungen haben kann. Unsere versiegelte Klasse Moment hat hier zwei Implementierungen, den normalen WeekDay, der einen Wochentag enthält, und den ExceptionDate, der ein bestimmtes Datum enthält.

Lesen von und Schreiben in die Spalte mit gemischten Daten

DynamoDB speichert unsere Moment-Spalte einfach als String. Es liegt in unserer Verantwortung, daraus zu lesen und in DynamoDB zu schreiben und die notwendige Typkonvertierung vorzunehmen. Dieser Codeschnipsel zeigt, wie unser Konverter aussieht.

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.time.DayOfWeek
import java.time.LocalDate
class MomentConverter : DynamoDBTypeConverter<String?, Moment?> {
  override fun convert(moment: Moment?): String? {
    return moment?.let {
      when (it) {
        is WeekDay -> it.dayOfWeek.toString()
        is ExceptionDate -> it.date.toString()
      }
    }
  }
  override fun unconvert(momentAsString: String?): Moment? {
    return momentAsString?.let {
      try {
        ExceptionDate(
          date = LocalDate.parse(it)
        )
      } catch (e: Exception) {
        try {
          WeekDay(
            dayOfWeek = DayOfWeek.valueOf(momentAsString)
          )
        } catch (e: Exception) {
          logger.error("Could not parse input '$momentAsString' to Moment.", e)
          null
        }
      }
    }
  }
  companion object {
    val logger: Logger = LoggerFactory.getLogger(this::class.java)
  }
}

Die Klasse MomentConverter ist eine Erweiterung der Klasse DynamoDBTypeConverter und kann Daten zwischen einem String (wie er in DynamoDB gespeichert ist) und einem Moment (wie er in unserem Code dargestellt wird) konvertieren. Die Klasse MomentConverter implementiert zwei Funktionen:

  • konvertieren (von Moment zu String)
  • unconvert (von String zu Moment)

Die Funktion convert verwendet den Kotlin-Ausdruck switch, um je nach Typ von Moment die entsprechende Methode toString() aufzurufen (denn wenn es sich um ein LocalDate handelt, wollen wir einen richtigen ISO-8601-formatierten String).

Die Funktion unconvert ist ein bisschen komplizierter. Wir haben einen String und müssen herausfinden, um welchen Typ es sich handelt. Zunächst versuchen wir, die Zeichenkette als LocalDate zu parsen. Wenn die Eingabe kein LocalDate ist, versuchen wir, sie als DayOfWeek zu parsen. Wenn auch das nicht klappt, protokollieren wir einen Fehler.

Fazit

Das war's. Die Implementierung dieses Datenbanktabellenentwurfs ist in der Praxis ebenso einfach wie in der Theorie. Zusammenfassend haben wir uns angesehen, wie wir unsere Datenstruktur in einer DynamoDB-Tabelle modellieren können, indem wir einen zusammengesetzten Primärschlüssel aus zwei Spalten verwenden, wobei eine Spalte mehrere Datentypen enthält. Wir haben gesehen, wie wir diesen Union-Typ in Kotlin mit einer versiegelten Klasse implementieren können. Schließlich wurde die Konvertierung und Entkonvertierung dieser Spalte mit gemischtem Datentyp demonstriert.

Ich hoffe, Sie haben einige neue Ideen, wie Sie DynamoDB effizient nutzen können. Seien Sie sich bewusst, dass dies nur die Spitze des Eisbergs ist. Wenn Sie tiefer in den Kaninchenbau eintauchen möchten, empfehle ich Ihnen einen der Vorträge von Rick Houlihan, z.B. diesen.

Verfasst von

Benjamin Komen

Contact

Let’s discuss how we can support your journey.