AzureBlobStorgeSDK for Java + Azurite docker を使ってみた

# はじめに
この記事は [**セゾン情報システムズ Advent Calendar 2020**](https://qiita.com/advent-calendar/2020/sisco) 13日目の記事です。
Azuriteは非常に便利なツールなのですが、日本語の情報が殆どないので書きました。

## Azuriteとは
Azureのストレージサービスをローカルでエミュレートできるという代物です。AWSのS3でいうminioのようなものですね。
詳しくは[公式ドキュメント](https://hub.docker.com/_/microsoft-azure-storage-azurite)を参照あれ。
>
Azurite オープンソース エミュレーターには、Azure Blob およびキュー ストレージ アプリケーションをテストするための無料のローカル環境が用意されています。 ローカルでのアプリケーションの動作に満足できたら、クラウドでの Azure Storage アカウントの使用に切り替えることができます。 エミュレーターは、Windows、Linux、および macOS でのクロスプラットフォーム サポートを提供します。
>

AzuriteはVSCodeの拡張機能やnpmパッケージ、dockerイメージなどの形で配布されています。
今回はDockerイメージ形式で利用していきます。
また、今回はBlobストレージのエミュレーティングを利用してみます。

## 参考
SDKの公式クイックスタート - https://docs.microsoft.com/ja-jp/azure/storage/blobs/storage-quickstart-blobs-java
AzuriteのGithub - https://github.com/azure/azurite

## 環境
- OS : macOS Catalina 10.15.6
- Java : AdoptOpenJDK11 14.0.2
- Docker : 19.03.12
- AzureSDK : com.azure:azure-storage-blob:12.8.0

# Azuriteの起動
まずは[公式ドキュメント](https://hub.docker.com/_/microsoft-azure-storage-azurite)にある通り、Dockerイメージをプルして起動します。
なお、バックグラウンド実行したいので[公式ドキュメント](https://hub.docker.com/_/microsoft-azure-storage-azurite)のコマンドに`-d`を追加しています

```
$ docker pull mcr.microsoft.com/azure-storage/azurite
Using default tag: latest
latest: Pulling from azure-storage/azurite
cbdbe7a5bc2a: Pull complete
9287919c3a0f: Pull complete
43a47bbd54c9: Pull complete
3c1bcea295c4: Pull complete
5047118b6920: Pull complete
77c92003e19f: Pull complete
13ec4a55a3c3: Pull complete
21145ef27392: Pull complete
db43acfda40e: Pull complete
Digest: sha256:6b6c9c24e97a6c000a81503a41bbf366f04c23aa3b473bfc27267145c92eeceb
Status: Downloaded newer image for mcr.microsoft.com/azure-storage/azurite:latest
mcr.microsoft.com/azure-storage/azurite:latest

$ docker run -d -p 10000:10000 -p 10001:10001 \
    mcr.microsoft.com/azure-storage/azurite
fc6aba0b5929aa2b7fcb9fb7d2b51d2b9e06c31c0d5486cce580798f7e844eef
```

Azuriteでは、接続文字列は以下の2つになり、どちらでも繋がります。2つ目のほうが本物のAzureBlobストレージに近い形式ですね。

```
UseDevelopmentStorage=true
```

もしくは

```
DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;
```

# Azuriteにデータを作る
本当はDockerの起動時にマウントするなどしてデータを持たせたいのだが、中を見てみると一見して理解できない構造でデータを持っているようで、それができないでいる。
詳しい人いたら教えて下さい。。。

## Azure Storage Explorerから作る
[Azure Storage Explorer](https://azure.microsoft.com/ja-jp/features/storage-explorer/)というツールでGUIからデータを作成できます。

1. 「Azure Storage へ接続する」で「ローカルエミュレーターにアタッチする」を選択する
  ![スクリーンショット 2020-09-11 18.15.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319966/6bda5c4f-c1e3-a6eb-5940-986de7faf42c.png)
  あとはデフォルトで登録
1. エクスプローラーからContainerを作成する
2. ファイルをアップロードする
  ![スクリーンショット 2020-09-11 18.22.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319966/ccad5c2a-a73e-6dae-990c-37f31b3dad35.png)


# JavaからSDKを使ってアクセスする
[クイックスタート](https://docs.microsoft.com/ja-jp/azure/storage/blobs/storage-quickstart-blobs-java)に従ってJavaからアクセスしてみます。
`build.gradle`に依存関係を追加します。

```build.gradle
dependencies {
    compile 'com.azure:azure-storage-blob:12.8.0'  // 追加
}
```
コンテナ名、オブジェクト名、オブジェクトの最終更新日時を表示してみます。

```AzureSdkApp.java
import com.azure.core.http.rest.PagedIterable;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.azure.storage.blob.models.BlobContainerItem;

public class AzureSdkApp {
    public static void main(String[] args) {
        final BlobServiceClient blobClient = new BlobServiceClientBuilder().connectionString("UseDevelopmentStorage=true").buildClient();
        final PagedIterable<BlobContainerItem> containers = blobClient.listBlobContainers();
        containers.forEach(container -> {
            final String containerName = container.getName();
            System.out.println("containerName = " + containerName);
            final BlobContainerClient containerClient = blobClient.getBlobContainerClient(containerName);
            containerClient.listBlobs().forEach(blob -> {
                final String blobName = blob.getName();
                System.out.println("blobName = " + blobName);
                System.out.println("blob.getProperties().getLastModified() = " + blob.getProperties().getLastModified());
            });
            System.out.println("==========");
        });
    }
}
```

実行結果(Azuriteにデータを作ってあります)

```
containerName = container-1
blobName = folder-1/test.txt
blob.getProperties().getLastModified() = 2020-12-12T05:14:36Z
blobName = test.txt
blob.getProperties().getLastModified() = 2020-12-12T05:14:53Z
==========
containerName = container-2
==========
```

とこんな感じでAzureSDKを使ったプログラムの動作確認環境としても、Azuriteは便利です。
AzureSDKも非常に使いやすいですね。（~~この記事を書いている最中に、この記事を書くきっかけになったハマリポイントをかんたんに解決する方法が見つかったのは内緒~~）
ぜひ皆さん使ってみてください〜〜

## おまけ：Javaからコンテナの起動、オブジェクトの作成もやってみる
上記のコードのテストを書きたくなったときなど、Javaからコンテナの起動・オブジェクトの作成もしたくなる場合があると思います。
そんなときは[testcontainers](https://www.testcontainers.org/)を使ってJavaからコンテナを操作します。

`build.gradle`に依存関係を追加します。

```build.gradle
dependencies {
    compile 'com.azure:azure-storage-blob:12.8.0'
    compile 'org.testcontainers:testcontainers:1.14.3'  // 追加
}
```

`GenericContainer`を継承した`AzuriteContainer`クラスを作成します。
接続文字列内の`BlobEndpoint`のホスト名が`localhost`になっていることに注意！
**127.0.0.1だと繋がりません**

```AzuriteContainer.java
import org.testcontainers.containers.GenericContainer;

public class AzuriteContainer extends GenericContainer {
    public AzuriteContainer() {
        super("mcr.microsoft.com/azure-storage/azurite");
    }

    @Override
    protected void configure() {
        super.configure();
        addFixedExposedPort(10000, 10000);
    }

    public String getConnectionString() {
        return "DefaultEndpointsProtocol=http;" +
                "AccountName=devstoreaccount1;" +
                "AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;" +
                "BlobEndpoint=http://localhost:10000/devstoreaccount1";
    }
}
```

テストコード内から起動・オブジェクトの作成をしてみます。

```AzureSdkAppTest.java
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.azure.storage.blob.models.BlobItem;
import com.azure.storage.blob.specialized.BlockBlobClient;
import com.example.azuresdktest.container.AzuriteContainer;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;


class AzureSdkAppTest {
    @Test
    void azureTest() throws IOException {
        try(final AzuriteContainer azuriteContainer = new AzuriteContainer()) {
            azuriteContainer.start(); // コンテナの起動

            final BlobServiceClient serviceClient = new BlobServiceClientBuilder().connectionString(azuriteContainer.getConnectionString()).buildClient();
            final String containerName = "test-container";
            serviceClient.createBlobContainer(containerName); // コンテナの作成

            final BlobContainerClient containerClient = serviceClient.getBlobContainerClient(containerName);
            final String blobName = "testFile.txt";
            final BlockBlobClient blobClient = containerClient.getBlobClient(blobName).getBlockBlobClient();
            final String data = "Hello world!";
            InputStream dataStream = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
            blobClient.upload(dataStream, data.length()); // ファイルのアップロード
            dataStream.close();

            // verify
            for (BlobItem listBlob : containerClient.listBlobs()) {
                System.out.println(listBlob.getName());
            }
        }
    }
}
```

実行結果

```
testFile.txt
```

上記のコードは[Githubのexample](https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/storage/azure-storage-blob/src/samples)を参考にしました。
私は利用したことはないのですが、SDKでファイルをダウンロードなどもできるようですね。とても便利だ！