---
title: MongoDB互換のDocumentDBにSpring Boot + Testcontainersでアクセスするメモ
summary: この記事では、Spring BootとSpring Data MongoDBでDocumentDBへ接続し、Testcontainersの設定変更やTLS設定方法を解説します。
tags: ["Spring Boot", "DocumentDB", "MongoDB", "Testcontainers", "Java", "FerretDB"]
categories: ["Programming", "Java", "org", "springframework", "data", "mongodb"]
date: 2025-09-03T04:31:45Z
updated: 2025-09-03T04:31:45Z
---

[前の記事](/entries/862)では、MongoDB互換のOSSデータベースである[FerretDB](https://docs.ferretdb.io/)を紹介しましたが、
Documentdbのバックエンドに使われている[DocumentDB](https://github.com/documentdb/documentdb)自体がMongoDB APIをサポートしたので、FerretDBは不要になりました。

本稿では、Spring Boot + Spring Data MongoDBを使ってDocumentDBにアクセスする方法をメモします。
といってもアプリケーション観点ではMongoDBと同じで、Testcontainersを使った設定だけが変わります。

以下は基本的には前回の記事の焼き直しです。

### プロジェクトの雛形の作成

Spring Initializrを使って、Spring Bootプロジェクトの雛形を作成します。

```bash
curl -s https://start.spring.io/starter.tgz \
       -d artifactId=demo-documentdb \
       -d name=demo-documentdb \
       -d baseDir=demo-documentdb  \
       -d packageName=com.example \
       -d dependencies=web,data-mongodb,actuator,configuration-processor,prometheus,native,testcontainers \
       -d type=maven-project \
       -d applicationName=DemoDocumentDbApplication | tar -xzvf -
cd demo-documentdb
```

### サンプルアプリの作成

非常にシンプルな、メッセージを保存・取得するアプリケーションを作成します。

```java
cat <<EOF > src/main/java/com/example/Message.java
package com.example;

public record Message(String id, String text) {
}
EOF
```


```java
cat <<EOF > src/main/java/com/example/HelloController.java
package com.example;

import java.util.List;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

	private final MongoTemplate mongoTemplate;

	public HelloController(MongoTemplate mongoTemplate) {
		this.mongoTemplate = mongoTemplate;
	}

	@PostMapping(path = "/messages")
	public Message postMessage(@RequestBody String text) {
		return mongoTemplate.save(new Message(null, text));
	}

	@GetMapping(path = "/messages")
	public List<Message> getMessages() {
		return mongoTemplate.findAll(Message.class);
	}

}
EOF
```

Spring InitializrでTestcontainersを追加しているので、次のファイルがプロジェクトに含まれます。


```java
package com.example;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.utility.DockerImageName;

@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {

	@Bean
	@ServiceConnection
	MongoDBContainer mongoDbContainer() {
		return new MongoDBContainer(DockerImageName.parse("mongo:latest"));
	}

}
```

Testcontainersを使ってアプリを起動しましょう。`src/test/java/com/example/TestDemoDocumentDbApplication.java`を実行するか、次のコマンドを実行します：

```bash
./mvnw spring-boot:test-run
```

アプリケーションおよびMongoDBが起動したら、次のようにメッセージをPOST & GETしてみましょう。

```bash
$ curl http://localhost:8080/messages -H content-type:text/plain -d "Hello MongoDB\!"
{"id":"6879a6f3ba99e4ec5c9419fd","text":"Hello MongoDB!"}

$ curl http://localhost:8080/messages -H content-type:text/plain -d "Hello DocumentDB\!"
{"id":"6879a6f8ba99e4ec5c9419fe","text":"Hello DocumentDB!"}

$ curl -s http://localhost:8080/messages | jq .
[
  {
    "id": "6879a6f3ba99e4ec5c9419fd",
    "text": "Hello MongoDB!"
  },
  {
    "id": "6879a6f8ba99e4ec5c9419fe",
    "text": "Hello DocumentDB!"
  }
]
```

この動作確認に相当するテストコードは次のようになります。

```java
cat <<EOF > src/test/java/com/example/DemoDocumentDbApplicationTests.java
package com.example;

import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.context.annotation.Import;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClient;

import static org.assertj.core.api.Assertions.assertThat;

@Import(TestcontainersConfiguration.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DemoDocumentDbApplicationTests {

	RestClient restClient;

	@BeforeEach
	void setUp(@LocalServerPort int port, @Autowired RestClient.Builder restClientBuilder) {
		this.restClient = restClientBuilder.defaultStatusHandler(statusCode -> true, (req, res) -> {
			/* NO-OP */}).baseUrl("http://localhost:" + port).build();
	}

	@Test
	void contextLoads() {
		{
			ResponseEntity<Message> res = this.restClient.post()
				.uri("/messages")
				.contentType(MediaType.TEXT_PLAIN)
				.body("Hello MongoDB!")
				.retrieve()
				.toEntity(Message.class);
			assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
			Message message = res.getBody();
			assertThat(message).isNotNull();
			assertThat(message.text()).isEqualTo("Hello MongoDB!");
			assertThat(message.id()).isNotNull();
		}
		{
			ResponseEntity<Message> res = this.restClient.post()
				.uri("/messages")
				.contentType(MediaType.TEXT_PLAIN)
				.body("Hello DocumentDB!")
				.retrieve()
				.toEntity(Message.class);
			assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
			Message message = res.getBody();
			assertThat(message).isNotNull();
			assertThat(message.text()).isEqualTo("Hello DocumentDB!");
			assertThat(message.id()).isNotNull();
		}
		{
			ResponseEntity<List<Message>> res = this.restClient.get()
				.uri("/messages")
				.retrieve()
				.toEntity(new ParameterizedTypeReference<>() {
				});
			assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
			List<Message> messages = res.getBody();
			assertThat(messages).isNotNull();
			assertThat(messages).hasSize(2);
			assertThat(messages).map(Message::id).allSatisfy(id -> assertThat(id).isNotNull());
			assertThat(messages).map(Message::text).containsExactly("Hello MongoDB!", "Hello DocumentDB!");
		}
	}

}
EOF
```

テストでもTestcontainersでMongoDBが起動します。次のコマンドでテストを実行できます：

```bash
./mvnw clean test
```

### DocumentDBへの入れ替え

MongoDBからDocumentDBに入れ替えます。アプリケーションのコードはそのままでよく、Testcontainersの設定だけを変更します。
Documentdbのコンテナイメージは`MongoDBContainer`との互換性がなかったので、`GenericContainer`を使います。
そのため、ServiceConnectionは使わず、`DynamicPropertyRegistrar`を使ってMongoDBの接続情報を動的に登録します。 Documentdbはデフォルトで認証が有効になっているので、ユーザー名とパスワードも含めて`spring.data.mongodb.uri`を設定します。

`TestcontainersConfiguration`を次のように変更します：

```java
cat <<EOF > src/test/java/com/example/TestcontainersConfiguration.java
package com.example;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.DynamicPropertyRegistrar;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;
import org.testcontainers.utility.DockerImageName;

@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {

	@Bean
	GenericContainer<?> documentdbContainer() {
		return new GenericContainer<>(DockerImageName.parse("ghcr.io/microsoft/documentdb/documentdb-local:latest"))
			.withExposedPorts(10260 /* MongoDB Port */, 9712 /* PostgreSQL port */)
			.withEnv("USERNAME", "user")
			.withEnv("PASSWORD", "password")
			.withEnv("ENFORCE_SSL", "false")
			.waitingFor(new HostPortWaitStrategy().forPorts(10260, 9712));
	}

	@Bean
	DynamicPropertyRegistrar dynamicPropertyRegistrar(GenericContainer<?> documentdbContainer) {
		return registry -> registry.add("spring.data.mongodb.uri", () -> "mongodb://user:password@%s:%d/test"
			.formatted(documentdbContainer.getHost(), documentdbContainer.getMappedPort(10260)));
	}

}
EOF
```

`TestcontainersConfiguration`変更後も同じテストが通るはずです：

```bash
./mvnw clean test
```

`src/test/java/com/example/TestDemoDocumentDbApplication.java`を再実行するか、次のコマンドを再実行します：

```bash
./mvnw spring-boot:test-run
```

先ほどと同じように、メッセージをPOST & GETしてみましょう。

```bash
$ curl http://localhost:8080/messages -H content-type:text/plain -d "Hello MongoDB\!"
{"id":"6879af654c503243968ecba0","text":"Hello MongoDB!"}

$ curl http://localhost:8080/messages -H content-type:text/plain -d "Hello DocumentDB\!"
{"id":"6879af6a4c503243968ecba1","text":"Hello DocumentDB!"}

$ curl -s http://localhost:8080/messages | jq .
[
  {
    "id": "6879af654c503243968ecba0",
    "text": "Hello MongoDB!"
  },
  {
    "id": "6879af6a4c503243968ecba1",
    "text": "Hello DocumentDB!"
  }
]
```

特に問題なくMongoDBからDocumentdbに切り替わることが確認できました。

### スタンドアローンで実行

Testcontainersを使わずに、アプリケーションをスタンドアローンで実行してみます。

Documentdbは次の`docker run`コマンドで起動します：

```bash
docker run --rm --name documentdb -p 10260:10260 -e USERNAME=user -e PASSWORD=password -e ENFORCE_SSL=false ghcr.io/microsoft/documentdb/documentdb-local:latest
```

次のコマンドで実行可能なjarファイルを作成します：

```bash
./mvnw clean package
```

実行時に`spring.data.mongodb.uri`を指定して、Documentdbに接続します。

```bash
java -jar target/demo-documentdb-0.0.1-SNAPSHOT.jar --spring.data.mongodb.uri=mongodb://user:password@localhost:10260/test
```

先ほどと同じように、メッセージをPOST & GETできるでしょう。

### DocumentDBへのTLS接続を有効にする

`ghcr.io/microsoft/documentdb/documentdb-local`のDockerイメージはデフォルトでTLSが有効になっています。
先の例では環境変数`ENFORCE_SSL=false`でTLSを無効にしていましたが、この設定を削除するとTLS接続が必要になります。

テスト中にMongoDBへのTLS接続を有効にするにはDocumentDBに設定されたTLS証明書のCA証明書をTrustStoreに登録する必要があります。
今回は[netty-pkitesting](https://central.sonatype.com/artifact/io.netty/netty-pkitesting)を使用して、テスト用に自己署名証明書を作成して、動的にこの証明書を使用するように設定します。

`pom.xml`に次の依存関係を追加します：

```xml
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-pkitesting</artifactId>
      <version>4.2.4.Final</version>
      <scope>test</scope>
    </dependency>
```

`TestcontainersConfiguration`を次のように変更します：

```java
cat <<EOF > src/test/java/com/example/TestcontainersConfiguration.java
package com.example;

import io.netty.pkitesting.CertificateBuilder;
import io.netty.pkitesting.X509Bundle;
import java.io.File;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
import org.springframework.boot.ssl.pem.PemSslStoreDetails;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.DynamicPropertyRegistrar;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;
import org.testcontainers.utility.DockerImageName;

@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {

	@Bean
	X509Bundle selfSignedCertificate() throws Exception {
		return new CertificateBuilder().subject("CN=localhost").setIsCertificateAuthority(true).buildSelfSigned();
	}

	@Bean
	GenericContainer<?> documentdbContainer(X509Bundle selfSignedCertificate) throws Exception {
		File tempCertChainPem = selfSignedCertificate.toTempCertChainPem();
		File tempPrivateKeyPem = selfSignedCertificate.toTempPrivateKeyPem();
		return new GenericContainer<>(DockerImageName.parse("ghcr.io/microsoft/documentdb/documentdb-local:latest"))
			.withExposedPorts(10260, 9712)
			.withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("documentdb")))
			.waitingFor(new HostPortWaitStrategy().forPorts(10260, 9712))
			.withFileSystemBind(tempCertChainPem.getAbsolutePath(), "/tmp/cert.pem", BindMode.READ_ONLY)
			.withFileSystemBind(tempPrivateKeyPem.getAbsolutePath(), "/tmp/private.key", BindMode.READ_ONLY)
			.withEnv("CERT_PATH", "/tmp/cert.pem")
			.withEnv("KEY_FILE", "/tmp/private.key")
			.withEnv("USERNAME", "user")
			.withEnv("PASSWORD", "password");
	}

	@Bean
	DefaultSslBundleRegistry sslBundles(X509Bundle selfSignedCertificate) {
		DefaultSslBundleRegistry bundles = new DefaultSslBundleRegistry();
		bundles.registerBundle("self-signed", SslBundle.of(new PemSslStoreBundle(null,
				PemSslStoreDetails.forCertificate(selfSignedCertificate.getRootCertificatePEM()))));
		return bundles;
	}

	@Bean
	DynamicPropertyRegistrar dynamicPropertyRegistrar(GenericContainer<?> documentdbContainer) {
		return registry -> {
			registry.add("spring.data.mongodb.uri", () -> "mongodb://user:password@%s:%d/test"
				.formatted(documentdbContainer.getHost(), documentdbContainer.getMappedPort(10260)));
			registry.add("spring.data.mongodb.ssl.enabled", () -> "true");
			registry.add("spring.data.mongodb.ssl.bundle", () -> "self-signed");
		};
	}

}
EOF
```

この設定に変更してもテストが成功することを確認してください：

```bash
./mvnw clean test
```


先ほどと同様に、Testcontainersを使わずに、アプリケーションをスタンドアローンで実行してみます。

次のコマンドで自己署名証明書を作成します：

```bash
DIR=/tmp/self-signed
mkdir -p ${DIR}

# Create CA certificate
openssl req -new -nodes -out ${DIR}/ca.csr -keyout ${DIR}/ca.key -subj "/CN=@making/O=LOL.MAKI/C=JP"
chmod og-rwx ${DIR}/ca.key

cat <<EOF > ${DIR}/ext_ca.txt
basicConstraints=CA:TRUE
keyUsage=digitalSignature,keyCertSign
EOF

openssl x509 -req -in ${DIR}/ca.csr -days 3650 -signkey ${DIR}/ca.key -out ${DIR}/ca.crt -extfile ${DIR}/ext_ca.txt

cat <<EOF > ${DIR}/ext.txt
basicConstraints=CA:FALSE
keyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement
extendedKeyUsage=serverAuth,clientAuth
EOF

# Create Server certificate signed by CA
openssl req -new -nodes -out ${DIR}/server.csr -keyout ${DIR}/server.key -subj "/CN=localhost"
chmod og-rwx ${DIR}/server.key
openssl x509 -req -in ${DIR}/server.csr -days 3650 -CA ${DIR}/ca.crt -CAkey ${DIR}/ca.key -CAcreateserial -out ${DIR}/server.crt -extfile ${DIR}/ext.txt
```

Documentdbは次の`docker run`コマンドで起動します：

```bash
docker run --rm --name documentdb -p 10260:10260 -v /tmp/self-signed:/tmp/self-signed -e USERNAME=user -e PASSWORD=password -e CERT_PATH=/tmp/self-signed/server.crt -e KEY_FILE=/tmp/self-signed/server.key ghcr.io/microsoft/documentdb/documentdb-local:latest
```

次のコマンドで実行可能なjarファイルを作成します：

```bash
./mvnw clean package
```

実行時に`spring.data.mongodb.uri`を指定して、Documentdbに接続します。

```bash
java -jar target/demo-documentdb-0.0.1-SNAPSHOT.jar --spring.data.mongodb.uri=mongodb://user:password@localhost:10260/test --spring.ssl.bundle.pem.self-signed.truststore.certificate=file:/tmp/self-signed/ca.crt --spring.data.mongodb.ssl.enabled=true --spring.data.mongodb.ssl.bundle=self-signed
```

先ほどと同じように、メッセージをPOST & GETできるでしょう。



動作確認したソースコードは[こちら](https://github.com/making/demo-documentdb)です。


---

DocumentdbをSpring Boot + Spring Data MongoDB + Testcontainersで使う方法を紹介しました。
SSPLなMongoDBの代替としてDocumentDBを使うことで、ライセンスの問題を回避しつつ、MongoDB互換の機能を利用できそうです。

その他の情報は[ドキュメント](https://documentdb.io/)を参照してください。
