概要

Amazon DynamoDBを、AWSに依存せず、ローカルでテストする流れのメモ。

DynamoDBでは、AWSを利用せずとも、ローカルで検証できるように、AWS自身からDynamoDB Localというモジュールが提供されています。

このモジュールはAWSが管理しているMavenリポジトリにもホスティングされており、これを利用することで、事前に特別なコマンド等を発行することなく、JVM言語のビルドプロセス過程で、ローカルにDynamoDBを用意することができるようになります。

このポストでは、Kotlinプロジェクトで、DynamoDB Localを使用してテストする場合に、どのような設定が必要になるのかを中心に書いていきます。

この手順で使用したコードは、以下に公開しているので、こちらも参考にしてください。
https://github.com/yo1000/ddb-local/tree/40c9061dc6/ddb-local-test

要件

環境

今回の作業環境は以下のとおりです。

  • Java 1.8.0_131
  • DynamoDB Local 1.11.86
$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.12.5
BuildVersion:	16F2073

$ java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

依存関係解決

pom.xml

ここが肝なので、とりあえず全文掲載してしまいます。要点は後述。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yo1000</groupId>
    <artifactId>ddb-local-test</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>ddb-local-test</name>
    <description>DynamoDB Local Testing Example</description>

    <properties>
        <kotlin.compiler.incremental>true</kotlin.compiler.incremental>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <kotlin.version>1.2.10</kotlin.version>
        <dynamodblocal.version>[1.11,2.0)</dynamodblocal.version>
        <sqlite4java.version>1.0.392</sqlite4java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib-jre8</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-reflect</artifactId>
            <version>${kotlin.version}</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>DynamoDBLocal</artifactId>
            <version>${dynamodblocal.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.almworks.sqlite4java</groupId>
            <artifactId>sqlite4java</artifactId>
            <version>${sqlite4java.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
        <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
        <plugins>
            <plugin>
                <artifactId>kotlin-maven-plugin</artifactId>
                <groupId>org.jetbrains.kotlin</groupId>
                <version>${kotlin.version}</version>
                <executions>
                    <execution>
                        <id>compile</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>test-compile</id>
                        <phase>test-compile</phase>
                        <goals>
                            <goal>test-compile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <jvmTarget>${java.version}</jvmTarget>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-allopen</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                </dependencies>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.19.1</version>
                <configuration>
                    <argLine>-Dsqlite4java.library.path=${basedir}/target/dependencies</argLine>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.10</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>process-test-resources</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/dependencies</outputDirectory>
                            <overWriteReleases>false</overWriteReleases>
                            <overWriteSnapshots>false</overWriteSnapshots>
                            <overWriteIfNewer>true</overWriteIfNewer>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>dynamodb-local-oregon</id>
            <name>DynamoDB Local Release Repository</name>
            <url>https://s3-us-west-2.amazonaws.com/dynamodb-local/release</url>
        </repository>
        <repository>
            <id>dynamodb-local-tokyo</id>
            <name>DynamoDB Local Release Repository</name>
            <url>https://s3-ap-northeast-1.amazonaws.com/dynamodb-local-tokyo/release</url>
        </repository>
    </repositories>
</project>

sqlite4java

なぜDynamoDBを使うのに、SQLiteが出てくるのか、という部分ですが、DynamoDB Localでは、

  • DynamoDBとインターフェースが等しいこと
  • データを永続化できること

の2点が満たせればよいだけなので、内部実装としては、SQLiteを使用した永続化が行われています。そのため、この依存が必要になってきます。

maven-surefire-pluginで、引数に-Dsqlite4java.library.path=${basedir}/target/dependenciesを渡して、SQLiteのライブラリパスを指定している部分からも、これを読み取ることができます。

maven-dependency-plugin

outputDirectoryに、${project.build.directory}/dependenciesを設定して、通常、~/.m2/repository配下にしか保存されない依存ライブラリを、自身の環境下にコピーします。

この指定により、maven-surefire-pluginで、引数に設定した-Dsqlite4java.library.path=${basedir}/target/dependenciesを参照可能になります。

dynamodb-local-oregon, dynamodb-local-tokyo

DynamoDB Localは、Maven Centralにアップロードされておらず、Amazon S3上に、必要なファイル群がアップロードされているだけなので、追加リポジトリの指定が必要になります。

また、依存解決するロケーションにより、ネットワーク的に有利なリージョンを選択することが可能です。使用可能なリージョン別のリポジトリについては、以下を確認してください。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBLocal.html#DynamoDBLocal.Maven

テスト

テストコード

テストの前に、組み込みDynamoDBを作成して、テストの後に、これを破棄するようなサンプルを用意します。テストでは、テーブルの作成と、設定通りにテーブルが作成されているかを確認してみます。

書いた内容以上のものはないくらいシンプルなものなので、これ以上の説明はありませんが、この最小限のサンプルを理解しておくことで、Spring Boot等への転用も容易になります。

package com.yo1000.dynamo.local

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB
import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded
import com.amazonaws.services.dynamodbv2.model.*
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test

/**
 *
 * @author yo1000
 */
class DynamoDBEmbeddedTest {
    lateinit var dynamo: AmazonDynamoDB

    @Before
    fun createDB() {
        dynamo = DynamoDBEmbedded.create().amazonDynamoDB()
    }

    @After
    fun shutdownDB() {
        dynamo.shutdown()
    }

    @Test
    fun test_that_table_is_created_equally_with_setting() {
        val tableName = "Stationery"
        val hashKeyName = "item_id"
        val readCapacityUnits = 1000L
        val writeCapacityUnits = 1000L

        val result = dynamo.createTable(CreateTableRequest()
                .withTableName(tableName)
                .withKeySchema(listOf(
                        KeySchemaElement(hashKeyName, KeyType.HASH)
                ))
                .withAttributeDefinitions(listOf(
                        AttributeDefinition(hashKeyName, ScalarAttributeType.S)
                ))
                .withProvisionedThroughput(
                        ProvisionedThroughput(readCapacityUnits, writeCapacityUnits))
        )

        val tableDesc = result.tableDescription
        Assert.assertEquals(tableName, tableDesc.tableName)
        Assert.assertEquals("[{AttributeName: $hashKeyName,KeyType: ${KeyType.HASH}}]",
                tableDesc.keySchema.toString())
        Assert.assertEquals("[{AttributeName: $hashKeyName,AttributeType: ${ScalarAttributeType.S}}]",
                tableDesc.attributeDefinitions.toString())
        Assert.assertEquals(readCapacityUnits, tableDesc.provisionedThroughput.readCapacityUnits)
        Assert.assertEquals(writeCapacityUnits, tableDesc.provisionedThroughput.writeCapacityUnits)
        Assert.assertEquals("ACTIVE", tableDesc.tableStatus)
        Assert.assertEquals("arn:aws:dynamodb:ddblocal:000000000000:table/$tableName", tableDesc.tableArn)

        val tables = dynamo.listTables()
        Assert.assertEquals(1, tables.tableNames.size)
    }
}

テスト実行

テストを実行して、結果を確認します。

$ ./mvnw clean test

[INFO] --- maven-surefire-plugin:2.19.1:test (default-test) @ ddb-local-test ---

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.yo1000.dynamo.local.DynamoDBEmbeddedTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.738 sec - in com.yo1000.dynamo.local.DynamoDBEmbeddedTest

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 16.650 s
[INFO] Finished at: 2018-01-14T13:08:05+09:00
[INFO] Final Memory: 54M/710M
[INFO] ------------------------------------------------------------------------

参考