IBM Cloud Code Engine + Quarkus + Push7 を使用した幼稚園バスの延着通知の仕組みを無料で作る

# はじめに

子供が幼稚園に通うようになり、バスで通園するようになったのですが、特に雨の日などに通園バスが延着することがあります。幼稚園ではバスに遅延が発生した場合に、外部公開のWebページに延着情報を掲載してくれるのですが、朝の出かけの準備でバタバタしているときにスマホでチェックする時間も無く、慌てて家を出たもののバスが遅れてなかなか来ない、ということが発生するようになりました。

幼稚園によってはバスが遅れた際にスマホにプッシュ通知でお知らせしてくれるところもあるのですが、うちの子が通っているところではそのようなサービスは利用していないので、「延着情報ページを定期的にチェックし、延着していればスマホにプッシュ通知してくれる」仕組みを自作しました。当記事はその内容について記したものになります。

なお、全体の仕組みの話がメインで、個別のサービス・技術の詳細なところまでは踏み込んでいません。


# アーキテクチャ概要

以下が自作した延着通知のアーキテクチャ概要です。全てのサービスが無料で利用できています(なお、IBM Cloudのアカウントは個人でプライベートに作成したアカウントになります)。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386149/59f4f23d-0ccc-e016-16fc-8c7a4f5d4d80.png)

延着情報をタイムリーにプッシュ通知するため、延着情報ページをスクレイピングでチェックするプログラムを作成し、それを定期的に実行する必要があります。そのための環境として、[IBM Cloud Code Engine](https://www.ibm.com/jp-ja/cloud/code-engine)を利用しました。Code Engineはコンテナベースのアプリケーションを稼働させることのできるサーバレス環境です。毎月一定量のCPU、メモリ使用については無償で利用することができます。Code Engineについては「[IBM Cloud Code EngineにQuarkusのサンプルアプリケーションをデプロイしてみる](https://qiita.com/hisato_imanishi/items/45c83963fa44c34e0255)」でも触れていますので、よろしければ参照ください。

延着情報ページをスクレイピングするバッチジョブは、Javaのマイクロサービスフレームワークである [Quarkus](https://ja.quarkus.io/) を使用しました。単なるバッチジョブのためマイクロサービスでも何でもないのですが、Quarkusではバッチ処理も作れるので利用しました。

スクレイピングした結果、延着情報が掲載されていればプッシュ通知を実行するのですが、1日に何度もプッシュ通知しないように、朝(送り)、夕(迎え)のそれぞれで1回通知したら以後は通知しないようにする必要があります。そのためのデータ永続化として、IBM Cloudのドキュメント型NoSQLである [Cloudant](https://www.ibm.com/jp-ja/cloud/cloudant) を利用しました。Cloudantも一定のデータ量までは無料で利用できます。IBM Cloudは無料で利用できる範囲が多いのがよいですね。

プッシュ通知には、プッシュ通知専用のサービスである [Push7](https://push7.jp/) を利用しました。プッシュ通知は、iOS含めて対応しようとすると、どうしてもアプリの作成・配布が必要となってきます(Androidのみが対象であればWebプッシュ通知が可能)。Push7を利用することで、アプリの作成・配布を自前で行わなくてもよくなります。

Push7でプッシュ通知を受信できるようにするためには、利用者側での購読処理が必要となります。具体的には、Webページに配置した購読用のボタンをクリックしてもらい、iOSであればPush7アプリ(事前インストール必要)への通知、AndroidであればWebプッシュ通知というかたちで購読登録を行う必要があります。そのためのWebページの配置先として [Netlify](https://www.netlify.com/) を利用しました。

それでは以下で、それぞれの要素についてもう少し細かく記載したいと思います。


# Quarkusでのジョブ作成 & コンテナイメージの作成

Quarkusは、上記でも述べた通り、本来はマイクロサービスのためのフレームワークです。ただ、Quarkusは割と懐が深く、「[コマンドモードアプリケーション](https://ja.quarkus.io/guides/command-mode-reference)」という、main実行できるアプリケーションを作成する機能が用意されています。

Quarkusでジョブを作成する利点としては、(他にもあるかもしれませんが今回開発している範囲では)以下のような点がありました。

**1. MicroProfile Configをデフォルトで利用できる**

今回作成したジョブはコンテナ化してCode Engineに載せるのですが、Cloudantの接続先や、Push7のAPIキーなどの設定は、コンテナから外出しし、環境変数で設定することが望ましいです。

Quarkusでは標準で MicroProfile Config が利用できるようになっています(他のMicroProfile仕様も利用可能)。

MicroProfile Config を使用すると、以下のようなフィールドを用意することで、Javaコードでのデフォルト値の設定、プロパティファイルでの設定、環境変数での設定、JVMの起動時引数での設定に対応することができるようになります。

```java
@ConfigProperty(name = "cloudant.url")
String cloudantUrl;
```

**2. コンテナイメージをデフォルトで作成できる**

Quarkusにはエクステンション機能が備わっており、エクステンションを追加することで様々な機能を追加することができます。Quarkusの特徴はGraalVMを使用したネイティブバイナリ、および、それを使用したコンテナイメージを作成できることですが、JVMベースのJavaアプリケーション・Dockerコンテナイメージを作成することも当然ですが可能です。

以下のようなコマンドでエクステンションを追加することで、mvnコマンド1発でDockerコンテナイメージまで作成することができます。

```
./mvnw quarkus:add-extension -Dextensions="container-image-docker"
```

# IBM Cloud Code Engineでのcron的なジョブ実行

IBM Cloud Code Engineでは、サーバレスの環境で、HTTPリクエストに応じて処理を行う「アプリケーション」と、バッチ処理を実行する「ジョブ」の2種類のタイプのプログラムを稼動させることができます。

今回は、上記で作成したスクレイピング処理を実行するQuarkusアプリを、Code Engine上で「[ジョブ](https://cloud.ibm.com/docs/codeengine?topic=codeengine-job-deploy)」として稼働させています。

また、Code Engine上のジョブはcronで定期的にキックすることが可能です。

Code Engineではこれを「ping イベント・プロデューサー」と呼んでいます。pingイベントの発生スケジュールをcron形式で設定し、そのイベントをサブスクライブするジョブを登録することで、定期的なジョブ実行を可能にしています。

pingイベント、および、イベントのサブスクライブについては、[IBM Cloudのドキュメントでより詳細な内容](https://cloud.ibm.com/docs/codeengine?topic=codeengine-subscribe-ping#eventing-ping-job)を確認することができます。

なお、2021年5月時点で、以下の問題を確認しています。ただ、ジョブのサブスクリプション機能はベータ段階なので、これらの問題も仕方ないかもしれません。

- ~~cronの設定でタイムゾーン指定ができない。CLIで、UTC以外のタイムゾーンを指定して登録しようとしてもエラーになる~~ (2021/9/14追記：現時点ではこの問題は解消されています)
- ジョブ実行結果が直近10件分しか保持されない([ドキュメント上は100件](https://cloud.ibm.com/docs/codeengine?topic=codeengine-limits#project_quotas)と記載されている)
- cronを設定した後、数日経過するとcronがいつの間にか無効になっている


# Cloudantでの通知結果の永続化

延着情報ページをチェックして延着があった場合、それをプッシュ通知するのですが、チェック間隔が1分または2分間隔のため、チェックのたびにプッシュ通知が来ては煩わしくなります。そのため、1回プッシュ通知したらその結果を履歴として永続化し、次回のチェックではその履歴を確認のうえ、同日朝・夕に既にプッシュ通知していたらプッシュ通知を送信しないようにしています（バスは平日の朝・夕に運行しているので、それぞれで履歴を保持)。

データを保存できれば何でもよかったのですが、データの構造が複雑ではなく、かつ、データのスキーマを後からでも容易に変更できるようにNoSQLを選択しました。また、具体的なサービスとしては、Code EngineがIBM Cloud上のサービスのため、同じくIBM Cloudで提供されているNoSQLであるCloudantを選択しました。

JavaプログラムからCloudantにアクセスするライブラリとしては、「[java-cloudant](https://github.com/cloudant/java-cloudant)」を使用しました。2021年5月時点では、「[IBM Cloudant Java SDK](https://github.com/IBM/cloudant-java-sdk)」が後継のライブラリのようでしたが、試した限り正常に動作しなかったためjava-cloudantに切り替えました。


# Push7を用いたプッシュ通知

上記で述べた通り、iOSを含めたスマホへのプッシュ通知を実現するためにはスマホアプリを作成する必要がありますが、Push7を利用することでそれが不要となります。

[Push7](https://push7.jp/)は、購読者に対してプッシュ通知を送信することのできるWebサービスです。Personalプランであれば一定の範囲で無料で利用することができます。

Push7では[iOS用のアプリが配信](https://apps.apple.com/jp/app/push7/id1088673537)されており、それをインストールしておくことでプッシュ通知を受信できるようになります(Androidの場合はWebサイトからのプッシュ通知(Webプッシュ)を利用できるため、アプリは配信されていないようです)。

プッシュ通知に際しては、配信側がWebページ上に購読ボタンを配置し、利用者(受信側)がそのボタンをクリック・購読登録することで、Push7からプッシュ通知を送信できるようになります。

Webページへの購読ボタンの配置は、Push7の管理画面から数行のコードをコピペするだけで完了します。

また、Push7では、プッシュ通知をプログラムから実行するためのREST APIを提供しています([APIドキュメント](https://push7.jp/docs/development/api/))。これを利用することで、簡単なJSONを組み立て、所定のURLにPOSTリクエストするだけでプッシュ通知を実行できます。


# プッシュ通知の登録用画面のホスティング(Netlify)

Push7のプッシュ通知の購読登録ボタンを配置するWebページを、何らかのホスティングサービスにアップする必要がありました(自分の家庭だけでなく、将来的に他の保護者に利用してもらうことを想定)。

今回はホスティングサービスとして [Netlify](https://www.netlify.com/) を使用しました。

Netlifyは静的コンテンツをホスティングするサービスとして有名ですが、無料プラン(Starterプラン)でも標準でSSLに対応していたり、GitHub/GitLabに静的コンテンツをpush/mergeすると自動的にサイトに反映してくれたりと、個人で使う場合でも非常に便利です。

今回はGitHubにプッシュ通知購読登録画面用のプライベートリポジトリを作成し、それと同期させるかたちでセットアップしました。


# さいごに(今後の展開)

スマホにプッシュ通知させることで、延着情報ページを能動的に見に行くというアクションを省き、保護者にタイムリーに延着情報を届けるということが実現できました。

ただ、朝の忙しいときはスマホに通知が届いても見ることすらできないことがあります(下の子のおむつを変えていたりすると)。そのため、今後の展開としては、うちのリビングにはAmazon Echoが置いてあるので、延着情報ページをチェックして延着がある場合にAmazon Echoに喋らせることができないかと思っています。