2019年末にQuarkusと和解し、ようやくAWS Lambda上からネイティブコンパイルしたJavaアプリでDBのデータを取得できるようになった。
その後は公式サイトのガイドを参考に、カバレッジの取得方法とか、設定ファイルの使い方とかを学んでいる。
今回はこちらのガイドを参考にしながら、メール送信機能を実装してみた。
[事前準備]
メール送信ロジックはQuarkus本体とは別に、エクステンションという別ライブラリの形で実装されている。
プロジェクトの作成時に-Dextensions="mailer"
を指定するとライブラリを追加することができる。
$ mvn io.quarkus:quarkus-maven-plugin:1.1.1.Final:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=sending-email-quickstart \ -Dextensions="mailer" $ cd sending-email-quickstart
すでにMavenプロジェクトがある場合は、次のコマンドを実行しても良い。
$ ./mvnw quarkus:add-extensions -Dextensions="mailer"
あるいは、pom.xml
を編集して依存関係を追加することもできる。
<project> </dependencies> ...(略)... <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-mailer</artifactId> <version>1.1.1.Final</version> </dependency> ...(略)... </dependencies> </project>
メールの送信機能を実装する前に、application.properties
ファイルで必要な設定を追加しておく。
quarkus.mailer.from=【メール送信者のアドレス】 quarkus.mailer.host=【SMTPサーバーのホスト】 quarkus.mailer.port=【SMTPサーバーのポート】 quarkus.mailer.ssl=true quarkus.mailer.username=【SMTPサーバーのユーザー名】 quarkus.mailer.password=【SMTPサーバーのパスワード】
メール送信者のアドレスや、SMTPサーバーのホスト名、ユーザー名、パスワードなど、そのままGitにコミットしてGitHubに公開したら困るデータがいくつもある。
Quarkusは機密情報を管理するHashiCorp Vaultというアプリと連携することができるので、今回はそちらを利用することにした。
[機密情報はHashiCorp Vaultに保管]
Vaultはどうやって使うんだろうと思っていたら、Vault連携を学ぶためのQuarkusのガイドが公開されていた。
Quarkus - Working with HashiCorp Vault
Vaultそのものを学ぶためのHashiCorp公式コンテンツもあるようだが、Getting Startedで1時間近くかかるコンテンツのようなので、今回はスキップ。
すでにMavenプロジェクトは用意したので、Vault用のQuarkusエクステンションをコマンドで追加する。
$ ./mvnw quarkus:add-extensions -Dextensions="vault"
Vault本体の準備も忘れずに。
ガイドではDockerにインストールされたVaultを利用していたが、今回はbrew install vault
でMacBookに直接インストールして利用した(バージョンはVault v1.3.1)。
vault server -dev
コマンドでVaultサーバーを起動する。開発モードなので、データはメモリ上にしか保存されない。かつ、開発モードだとHTTPSではなくHTTPで起動するので、vaultの接続先情報を変更しておく必要がある。また、Vaultの機密情報管理エンジンにはバージョンが2つあるようで、デフォルトは新しい方のv2らしいが、Quarkusのガイドにしたがって下記のコマンドを実行し、v1を利用するよう設定を変更しておく。
$ export VAULT_ADDR='http://127.0.0.1:8200' # 接続先のURLをhttpに変更 $ export VAULT_TOKEN=【起動時のログから取得したトークンを設定】 $ vault secrets disable secret # v2を無効化 Success! Disabled the secrets engine (if it existed) at: secret/ $ vault secrets enable -path=secret kv # v1を有効化 Success! Enabled the kv secrets engine at: secret/
ガイドではGmailのSMTPサーバーを経由してメールを送信する方法も紹介されていたが、最近Amazon SESでメール送信をした流れで、今回もそちらを利用する。
Amazon SESを利用する場合、以前紹介したようにAWS SDKを利用してIAMで認証を行いメールを送信できる。が、QuarkusのMailerエクステンションをAWS SDKに差し替えるのがめんどくさかったので今回はガイドにしたがってSMTPのユーザーネームとパスワードを用意する。
Amazon SESの管理コンソールを開き、「Create My SMTP Credentials」ボタンをクリックすると、ユーザーネームとパスワードを取得できる。
必要な情報を用意したら、一括でVaultに登録する。
$ # ZZZZZ, XXXXX, YYYYYには本物の値を入力 $ # quarkus/mail/configというパスで機密情報を登録 $ vault kv put secret/quarkus/mail/config host=email-smtp.us-west-2.amazonaws.com from=ZZZZZ@gmail.com username=XXXXX password=YYYYY Success! Data written to: secret/quarkus/mail/config
getコマンドを実行し、正しく登録できているかどうか確認する。
$ vault kv get secret/quarkus/mail/config ====== Data ====== Key Value --- ----- from ZZZZZ@gmail.com host email-smtp.us-west-2.amazonaws.com password YYYYY username XXXXX
機密情報を正しく保存できていたので、Quarkusで読み取るために、Vaultにポリシー、ユーザーネーム、パスワードを設定する。
$ vault auth enable userpass # ユーザーネームとパスワードによる認証を有効化 Success! Enabled userpass auth method at: userpass/ $ # Vaultに読み取り権限のみのポリシーを作成する $ cat <<EOF | vault policy write vault-quarkus-policy - > path "secret/quarkus/mail/*" { > capabilities = ["read"] > } > EOF Success! Uploaded policy: vault-quarkus-policy $ # kdnaktというユーザーネーム、パスワードで、vault-quarkus-policyポリシーを適用したユーザーを作成 $ vault write auth/userpass/users/kdnakt password=kdnakt policies=vault-quarkus-policy Success! Data written to: auth/userpass/users/kdnakt
あとは、Quarkusに設定を反映するためにapplication.properties
ファイルを修正する。
# Vault関連の設定値 quarkus.vault.url=http://127.0.0.1:8200 # vault cliからのアクセス同様URLを修正する quarkus.vault.authentication.userpass.username=kdnakt quarkus.vault.authentication.userpass.password=kdnakt quarkus.vault.secret-config-kv-path=quarkus/mail/config # Vaultから読み込んだデータをQuarkusに渡す quarkus.mailer.from=${from} quarkus.mailer.host=${host} quarkus.mailer.port=465 quarkus.mailer.ssl=true quarkus.mailer.username=${username} quarkus.mailer.password=${password}
これでようやく事前準備が完了した。
[メールを送信する]
再びメール送信のガイドに戻り、コードを実装していく。
quarkus-sandbox/MailResource.java at master · kdnakt/quarkus-sandbox · GitHub
重要な部分だけ抜き出すと以下のようになる。
@Path("/simple") public class MailResource { // メール送信基盤を用意 @Inject Mailer mailer; @Get public Response send() { // 送信先、件名、本文を指定したメールオブジェクトを作成し、送信 mailer.send(Mail.withText(to, "A simple email from quarkus", "This is my body.")); return Response.accepted().build(); } }
上記コードではシンプルなテキストメールが送られるだけだが、添付ファイルつきのメールや画像を埋め込んんだHTMLメールを送る方法も紹介されていた。
メールの送信テストをするべく、./mvnw compile quarkus:dev
で開発モードでQuarkusアプリを起動する。
$ ./mvnw compile quarkus:dev [INFO] Scanning for projects... [INFO] [INFO] ------------< com.kdnakt.quarkus:sending-email-quickstart >------------- [INFO] Building sending-email-quickstart 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ sending-email-quickstart --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 4 resources [INFO] [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ sending-email-quickstart --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- quarkus-maven-plugin:1.1.0.Final:dev (default-cli) @ sending-email-quickstart --- Listening for transport dt_socket at address: 5005 2020-01-11 01:04:36,765 INFO [io.quarkus] (main) Quarkus 1.1.0.Final started in 2.260s. Listening on: http://0.0.0.0:8080 2020-01-11 01:04:36,770 INFO [io.quarkus] (main) Profile dev activated. Live Coding activated. 2020-01-11 01:04:36,770 INFO [io.quarkus] (main) Installed features: [cdi, mailer, resteasy, vault, vertx]
curl http://localhost:8080/simple
を実行するとメールが送信されるはずなのだが、何度か試しても一向にGmailのInboxにメールが届かない。
開発モードで起動したQuarkus側の標準出力には次のように表示されていた。意図通り送信先のアドレス、送信元のアドレスがZZZZZ〜になっているので、Vaultへの接続はうまくいっていることがわかる。
simple!! 2020-01-11 01:06:02,793 INFO [quarkus-mailer] (executor-thread-1) Sending email A simple email from quarkus from ZZZZZ@gmail.com to [ZZZZZ@gmail.com], text body: This is my body html body: null
もう少し調べていくと、開発モード(とテストモード)でQuarkusアプリを起動した場合は、Mailerエクステンションはモックとして機能するということが分かった。なので、application.propertiesファイルにquarkus.mailer.mock=false
と追記して、強制的にメールを送信するよう修正し、再度curlコマンドを実行した。
すると、メールを無事に受信することができた。🎉
ちなみにHTMLメールに画像を埋め込むとこんな感じになった。
ガイドを進めていく中で、ガイドに問題を見つけたので、ドキュメント修正のプルリクエストを出した。あっという間にマージされてびっくりした。
例によってネイティブコンパイルした場合はちょっと問題があるようで、macOS上でアプリを起動して、画像を埋め込んだHTMLメールを送ろうとしたところ、org.jboss.resteasy.spi.UnhandledException: java.lang.IllegalStateException: No ReactiveStreamsFactory implementation found!
というエラーがでた(詳細なスタックトレースはGitHubに)。
こちらは未解決なので、いずれ時間を見つけて修正したい。
[メール送信機能のテストコード]
ガイドでは、メール送信のテストコードの書き方も説明されていた。
最初に開発モードでメールが飛ばずガッカリしたが、このモックのメールボックスを利用して、メール本文や件名に関するテストをすることができる。
// モックメールボックスを用意 @Inject MockMailbox mailbox; @Test void testTextMail() throws IOException { // APIを呼び出してメールを送信 given().when().get("/simple").then().statusCode(202).body(is("")); // モックメールボックスからメールを取得してアサート Listsent = mailbox.getMessagesSentTo(to); assertThat(sent).hasSize(1); Mail actual = sent.get(0); assertThat(actual.getText()).isEqualTo("This is my body"); }
便利だけど、万が一本番でフラグ切替ミスってメール送信できなくなったら……とか考えると、ちょっと怖くもある。
ガイド内では詳しい説明はなかったが、テストコードで利用されているテストライブラリはAssertJと言うらしい。知らなかった。JUnit5だとassertThatがなくなったとかで、こういうのを使うらしい。
[まとめ]
- Quarkus公式サイトのSending emailsガイドと、Working with HashiCorp Vaultを試した
- ネイティブアプリの動作はやはり一筋縄では行かなそう
- JavaのテストアサーションライブラリAssertJを知ることができた
- ガイドを読んでいるうちにリンク切れを発見したのでプルリクエストを出した
- 実装したコードは以下のリポジトリにまとめた
quarkus-sandbox/sending-email-quickstart at master · kdnakt/quarkus-sandbox · GitHub