フレームワークとクラウド技術をフル活用した通知基盤の構成(Laravel x AWS)

# 本記事作成意図
Webシステムにおいて、`社内通知`・`ユーザーへの販促`・`業者`への通知が必要になることがあり、
その要件に応じて、データの取得先や通知チャンネルもさまざまである。

それぞれの要件に合わせて0からインフラ構成を考えて適切なものを選択・構築することがあると思うけれど、通知処理というのは基本的に
1. `通知チャンネル`（Slack/メール/LINE/プッシュ通知など）
2. `チャネル内の宛先(メールアドレスやユーザーIDなど)`
2. `メッセージの内容`

が決まれば基本的に事が足りる。

つまり、適切なタイミングで適切な相手に情報を伝達するための手段であり、
その送信根拠となる永続化先や通知先のチャンネルというのは抽象化できるのではないかなと思ったので
外部ツールとの連携先が豊富なイメージのあるLaravelで下調べをしてみた。

# 通知基盤のイメージ

![スクリーンショット 2022-04-12 1.21.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378609/34820c0d-8b15-fb34-eee7-688bc3136f61.png)

基本構成はECSのタスクスケジューリングでコマンドを起動し、
データの取得先をRepository/DTOで抽象化
通知処理のインタフェースであるNotificationの実装クラスでチャンネルの切り分けを行う
とする事でおおよそのビジネス要件を満たすことを目的としている。

軽い処理の場合はLamdaを使うなども選択肢に入ってくるであろう。

# LaravelとAWSで作る基盤の方向性とメリット
ビジネスのフェーズ的にスケールしている場合だと大量処理を捌く用途としてGoを使う場合なども考えられるが、
ここでは比較的フェーズの浅いベンチャー企業などで、要件がコロコロ変わったり、負荷対策もそこそこに実装が手早く行えることを主眼に置く。

- スケールした場合や一時的な負荷にある程度耐えられること
- 実装コストが低いこと
- 運用コストが下げられる得ること
- テスト工数が抑えられること
- 極力実装しないこと
- 重複実行や実行漏れが起きにくい事


を念頭に置く。極力実装しないというのは一担当者として実装する箇所が多ければ多いほど一般にバグのリスクやテストの工数も上がるため、タイトルにもあるようにフレームワークの機能や要件にあったクラウド技術をフル活用する前提で考える。

ただし筆者がクラウド技術に関して精通しているわけではないので間違っているところがあれば指摘いただけると幸甚です。
主にLaravelの機能の調査・紹介・設計方針が中心になります。

# Laravel機能の概要


![スクリーンショット 2022-04-12 0.57.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378609/3e1f8f17-f618-b191-7591-0d86e33e8a15.png)

### 基本的には実行パターンは３通り考えていて下記の通り。
- CloudWatchの機能で[ArtisanコンソールのCommand](https://readouble.com/laravel/8.x/ja/artisan.html)を叩きバッチを起動
    - [Amazon ECS でタスクをスケジューリングして定期的に実行する](https://www.ritolab.com/entry/222)
- サブシステムからAmazonSQSにqueueを積み、Supervisorで監視し検知する。
    - [外部システムから投入されるSQSキューをLaravelで監視処理する](https://qiita.com/danishi/items/7e1f12a2430288a648ba)
    - [外部システムから投入されるSQSキューをLaravelで監視処理する](https://cloudpack.media/60343)
 
- システム管理画面から実行
    - Form/JSON/CSVなどのデータをDTOで一般化する

### 実行時に取得したデータを抽象化されたRepositoryの実装で取得する


### 通知インターフェースをimplementしたそれぞれの通知Job(ShouldQueue)でqueueに積み消化
   - [Laravel 8.x キュー](https://readouble.com/laravel/8.x/ja/queues.html)

次のセクションで詳しい機能の説明を見ていく

# Laravelのここが嬉しい

### 豊富なデータ接続先
- [Laravel 8.x データベース：準備](https://readouble.com/laravel/8.x/ja/database.html)
    - 5つのRDBMSをファーストパーティーサポート
- BigQuery
    - 標準対応でないものの[schulzefelix/laravel-bigquery](https://github.com/schulzefelix/laravel-bigquery/tree/1.2.0)を利用することで簡単にクエリ実行し結果を取得する事が可能
    - `BigQuery::runQuery("SQL文");`
- DinamoDB
    - [Laravel で Amazon DynamoDB を利用するための実装 tips 集](https://tech.willgate.co.jp/entry/2018/02/28/134537)
    - eloquentライクな文法で直感的に操作できる
- その他
    - awsのgithubアカウントで[aws-sdk-php-laravel](https://github.com/aws/aws-sdk-php-laravel)が提供されていて多くのサービスと接続可能

### 豊富な通知チャンネルとの連携
- さまざまなメール通知やSlackが標準対応
    - https://readouble.com/laravel/8.x/ja/notifications.html
- 豊富なサードパーティーカスタムチャンネル(接続先が50以上紹介されている)
    - https://laravel-notification-channels.com/about/#faq

### ジョブとキューで非同期処理が容易に書ける事から記事・ドキュメントが豊富
- Jobごとにqueueを分けたり、負荷対策・通知速度改善でqueueの並列化が容易
    - https://qiita.com/Nishi53454367/items/871614343ea77020db17
- 要件が厳しいものだけAmazonSQS、速度が必要なのでRedis、失敗の取り回しを自社でしたいのでDBなど、Job単位で柔軟に設定できる

### failed_jobsというテーブルが初期に備わっており通知非同期Jobの失敗時の再実行の設定が簡単かつ細かい設定ができる
- failed_jobsの運用が楽でphp artisan queue:retryで簡単にリトライできる
- https://reffect.co.jp/laravel/laravel-queue-setting-manuplate

### サービスコンテナを使うことで通知先の変更などインスタンスの差替容易
- https://readouble.com/laravel/8.x/ja/container.html
    - 通知を５回まで実行して失敗したら、社内用のSlackにJobの失敗通知をするといった要件に関しても、通知実装クラスに渡す引数は使いまわせる。

# マネージドサービスの活用
- 要件によってLamdaで動かしたりする事でコスト圧縮が見込める
- AmazonSQSを利用することで高精度で重複実行の恐れがなくなる
- オートスケーリングの設定やqueueを並列化してSQSに渡すなどすればある程度の負荷にも対応できるはず。

# より柔軟にビジネスの要件に対応するために

- 設計段階でRepositoryで永続化先を閉じておくことでテストが容易となる。
    - 仮にデータリソースの接続先が変わっても通知Job以降の処理は変わらないので通知インターフェースに引数として渡すところまでを考えれば、すでに動いている基盤の上に処理を載せるだけなので新たに考える事が減る。また、自動テストを書いていれば差替するだけになる。
- 逆に通知チャンネルが増えた場合でも、通知の共通インターフェースから各のqueueを積むところだけを考えればいいのでテスト工数が下がる。
- ステージング環境ではスラックで各通知チャンネル確認用の部屋を用意しておけば、通知内容が変わった時などに、対象の通知チャンネル用のところにメッセージが届けばほぼ動くことが担保できているので、0からインフラ構築する際に比べると自信を持ってデプロイできるはずだ。
- faild_jobsの通知がデータの永続化先や通知チャンネルに依存しないので、Slackの拡張などを使ってリトライの処理を画面に実装すれば、開発者でなくとも失敗Jobの対応をする事が可能になる。(運用フローの整備)

# 所感

やはりLaravelは外部サービスとの接続やクラウドのマネージドサービスとの接続が楽で痒いところに手が届く感じがした。

通知だけに限らず、特に外部に依存するような処理はできるだけ外側で閉じて、ビジネス的な要件と混ざらないような構成にしておく事で、
要望に対してスムーズにリリースができて、運用の負荷も減るのではないかと思った。
具体的にいうと通知というのは、通知先・通知時間の条件を満たすユーザーに情報を届ける行為で　
永続化先やチャンネルの要件はその手段に過ぎない。
この辺の切り分けの感度を高く持つことで、極力実装を行わずに要件を迅速に満たす事ができるようになるのではないかと思った。