Однажды я столкнулся с проблемой загрузки относительно большого файла двоичных данных из PostgreSQL . Существует несколько ограничений для хранения и извлечения таких данных (все ограничения можно найти в официальной документации ). Для решения проблемы было предложено найти более подходящее хранилище данных.
По некоторым внутренним причинам для этой цели было выбрано хорошо известное ведро Amazon S3. Выбор повлиял на тестовую базу проекта. По-прежнему невозможно использовать легковесные базы данных, такие как HSQL или H2, для реализации тестов. Это ключевая проблема, которую мы попытаемся решить в этой статье.
Вам также может понравиться: Интеграция с хранилищем файлов или хранилищем объектов
Строительство объектов хранения
Одним из возможных решений для поддержания модульных тестов является реализация некоторого хранилища фиктивных объектов, полностью совместимого с клиентом S3 Bucket, с другой стороны, мы могли бы использовать уже существующее хранилище объектов этого типа. MinIO — отличный пример довольно простого, но высокопроизводительного хранилища объектов, которое в то же время совместимо с Amazon S3 (по крайней мере, это написано в документации).
Для интеграции MinIO в наш модульный тест мы будем использовать мощную библиотеку Testcontainers, написанную на Java. Testcontainers — это специальная библиотека, которая поддерживает тесты JUnit и предоставляет легкие, одноразовые экземпляры общих баз данных, веб-браузеры Selenium и все остальное, что может работать в контейнере Docker. Чтобы начать использовать эту удивительную библиотеку, просто нужно иметь Docker и добавить следующую зависимость в наш pom.xml:
XML
xxxxxxxxxx
1
<dependency>
2
<groupId>org.testcontainers</groupId>
3
<artifactId>testcontainers</artifactId>
4
<version>1.12.3</version>
5
<scope>test</scope>
6
</dependency>
К сожалению, для нашей цели нет подходящего контейнера, но библиотека предоставляет все необходимые инструменты для его простого создания. К счастью, есть официальный образ докера для MinIO на DockerHub.
Для создания собственного контейнера MinIO необходимо расширить GenericContainer
пользовательские данные: DEFAULT_PORT
(в документации MonIO предлагается использовать порт 9000), DEFAULT_IMAGE
(имя изображения), DEFAULT_TAG
(версия изображения).
Будьте внимательны с назначением тегов! В нашем примере используется тег «edge» для поддержки последней развернутой версии MinIO, но в большинстве случаев лучше время от времени исправлять и обновлять тег вручную, чтобы избежать непредсказуемых сбоев теста. Также настоятельно рекомендуется предоставить учетные данные (ключ доступа, секретный ключ) для контроля доступа к контейнеру. Вот пример реализации пользовательского контейнера MinIO:
Джава
xxxxxxxxxx
1
public class MinioContainer extends GenericContainer<MinioContainer> {
2
3
private static final int DEFAULT_PORT = 9000;
4
private static final String DEFAULT_IMAGE = "minio/minio";
5
private static final String DEFAULT_TAG = "edge";
6
7
private static final String MINIO_ACCESS_KEY = "MINIO_ACCESS_KEY";
8
private static final String MINIO_SECRET_KEY = "MINIO_SECRET_KEY";
9
10
private static final String DEFAULT_STORAGE_DIRECTORY = "/data";
11
private static final String HEALTH_ENDPOINT = "/minio/health/ready";
12
13
public MinioContainer(CredentialsProvider credentials) {
14
this(DEFAULT_IMAGE + ":" + DEFAULT_TAG, credentials);
15
}
16
17
public MinioContainer(String image, CredentialsProvider credentials) {
18
super(image == null ? DEFAULT_IMAGE + ":" + DEFAULT_TAG : image);
19
withNetworkAliases("minio-" + Base58.randomString(6));
20
addExposedPort(DEFAULT_PORT);
21
if (credentials != null) {
22
withEnv(MINIO_ACCESS_KEY, credentials.getAccessKey());
23
withEnv(MINIO_SECRET_KEY, credentials.getSecretKey());
24
}
25
withCommand("server", DEFAULT_STORAGE_DIRECTORY);
26
setWaitStrategy(new HttpWaitStrategy()
27
.forPort(DEFAULT_PORT)
28
.forPath(HEALTH_ENDPOINT)
29
.withStartupTimeout(Duration.ofMinutes(2)));
30
}
31
32
public String getHostAddress() {
33
return getContainerIpAddress() + ":" + getMappedPort(DEFAULT_PORT);
34
}
35
36
public static class CredentialsProvider {
37
private String accessKey;
38
private String secretKey;
39
public CredentialsProvider(String accessKey, String secretKey) {
40
this.accessKey = accessKey;
41
this.secretKey = secretKey;
42
}
43
44
// getters
45
}
46
}
Тест
Поскольку у нас есть подходящий тестовый контейнер, который можно использовать в качестве поставщика хранилища объектов-хранилищ Amazon S3, самое время показать пример простого теста JUnit с ним. Конечно, во-первых, нам нужно настроить клиент S3 для взаимодействия с нашим контейнером. В нашем случае мы используем оригинальный AmazonS3Client. Поэтому для реализации нашего модульного теста нам нужно добавить дополнительную зависимость.
XML
xxxxxxxxxx
1
<dependency>
2
<groupId>com.amazonaws</groupId>
3
<artifactId>aws-java-sdk-s3</artifactId>
4
<version>1.11.60</version>
5
</dependency>
6
<dependency>
7
<groupId>com.amazonaws</groupId>
8
<artifactId>aws-java-sdk</artifactId>
9
<version>1.11.60</version>
10
</dependency>
Вот обычный тест для создания корзины S3 с указанным именем:
Джава
1
public class MinioContainerTest {
2
3
private static final String ACCESS_KEY = "accessKey";
4
private static final String SECRET_KEY = "secretKey";
5
private static final String BUCKET = "bucket";
6
7
private AmazonS3Client client = null;
8
9
10
public void shutDown() {
11
if (client != null) {
12
client.shutdown();
13
client = null;
14
}
15
}
16
17
18
public void testCreateBucket() {
19
try (MinioContainer container = new MinioContainer(
20
new MinioContainer.CredentialsProvider(ACCESS_KEY, SECRET_KEY))) {
21
22
container.start();
23
client = getClient(container);
24
Bucket bucket = client.createBucket(BUCKET);
25
assertNotNull(bucket);
26
assertEquals(BUCKET, bucket.getName());
27
28
List<Bucket> buckets = client.listBuckets();
29
assertNotNull(buckets);
30
assertEquals(1, buckets.size());
31
assertTrue(buckets.stream()
32
.map(Bucket::getName)
33
.collect(Collectors.toList())
34
.contains(BUCKET));
35
}
36
}
37
38
private AmazonS3Client getClient(MinioContainer container) {
39
S3ClientOptions clientOptions = S3ClientOptions
40
.builder()
41
.setPathStyleAccess(true)
42
.build();
43
44
client = new AmazonS3Client(new AWSStaticCredentialsProvider(
45
new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY)));
46
client.setEndpoint("http://" + container.getHostAddress());
47
client.setS3ClientOptions(clientOptions);
48
return client;
49
}
50
}
Пример кода из этого поста можно найти на GitHub .
Дальнейшее чтение
Развертывание рабочих нагрузок больших данных в хранилище объектов без снижения производительности