Composite Primary Keys Kotlin Example - derjust/spring-data-dynamodb GitHub Wiki

Overview

This example demonstrates how to model DynamoDB HASH/RANGE partition keys using a SpringData-style composite primary key and Kotlin data classes. I built this example from sample code submitted by @rheber as part of issue #240: Table with hash and range key in Kotlin.

It also borrows largely from an existing wiki article which demonstrates the said mapping in Java: Use Hash Range keys.

Example

Composite Key Class

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBDocument
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey
import java.io.Serializable

/**
 * Composite primary key for the [FoobarEntry] domain.
 */
@DynamoDBDocument
data class FoobarEntryId(

    @field:DynamoDBHashKey
    var foobarCode: String? = null,

    @field:DynamoDBRangeKey
    var foobarKey: String? = null

) : Serializable

Domain Class

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIgnore
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable
import org.springframework.data.annotation.Id

@DynamoDBTable(tableName = "Foobars")
data class FoobarEntry(
    @field:Id
    @DynamoDBIgnore
    val id: FoobarEntryId = FoobarEntryId()
) {

    @DynamoDBHashKey(attributeName = "foobarCode")
    fun getFoobarCode() = id.foobarCode

    fun setFoobarCode(foobarCode: String) {
        id.foobarCode = foobarCode
    }

    @DynamoDBRangeKey(attributeName = "foobarKey")
    fun getFoobarKey() = id.foobarKey

    fun setFoobarKey(foobarKey: String) {
        id.foobarKey = foobarKey
    }

    @get:DynamoDBAttribute
    var foobarValue: String? = null
}

Repository Interface

import org.springframework.data.repository.CrudRepository

interface FoobarRepository : CrudRepository<FoobarEntry, FoobarEntryId> {
    fun findAllByFoobarCode(foobarCode: String): Iterable<FoobarEntry>
}

I tested the example above using:

  • Kotlin: 1.3.21
  • Spring Boot: 2.1.3.RELEASE
  • Spring Data DynamoDB Version: 5.0.4 (2.0)
  • Spring Data Version: 2.0.8.RELEASE
  • AWS SDK Version: 1.11.478
  • Java Version: 1.8.0_201
  • Platform: Linux 4.15.0-46-generic

Additionally, I created a JUnit 5 integration test that leverages the excellent testcontainers library to launch a localstack Docker container to emulate AWS DynamoDB in local mode. I am providing the said test for illustration purposes only as it does not run on its own.

Integration Test

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.junit.jupiter.SpringExtension

@ExtendWith(SpringExtension::class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class FoobarRepositoryTest : BaseDynamoDbIntegrationTest() {
    @Autowired
    private lateinit var foobarRepository: FoobarRepository

    private val foobarEntryId = FoobarEntryId(foobarCode = "1", foobarKey = "key")

    @BeforeAll
    fun setupTestData() {
        foobarRepository.save(FoobarEntry(foobarEntryId).apply {
            foobarValue = "value"
        })
    }

    @AfterAll
    fun cleanTestData() {
        foobarRepository.deleteById(foobarEntryId)
    }

    @Test
    fun `Test findAllByFoobarCode() query finder works as expected`() {
        assertThat(foobarRepository.findAllByFoobarCode("1")).hasOnlyOneElementSatisfying { entry ->
            assertThat(entry.id.foobarCode).isEqualTo("1")
            assertThat(entry.id.foobarKey).isEqualTo("key")
            assertThat(entry.getFoobarCode()).isEqualTo("1")
            assertThat(entry.getFoobarKey()).isEqualTo("key")
            assertThat(entry.foobarValue).isEqualTo("value")
        }
    }
}