空いてる時間を気軽に共有出来るサービスを作ってみた

この記事は[CA21 Advent Calendar 2020](https://adventar.org/calendars/5308)に投稿する予定の記事です。

## 初めに
来年からインフラエンジニア(SRE)として働く予定のしがない学生です > [Twitter](https://twitter.com/_tetsuya28)
アドカレ3日目担当ということで今日はとあるサービスを勉強がてら作ってみたお話をしようと思います
あんまり1つずつの技術の深堀りはしないのでよろしくお願いします

## 空き時間共有サービス
という訳で早速今回作ってみたサービスの紹介から
題名の通りなんですが、ミーティングや遊びなどの色々な場面で日程調整をすることがあると思うんですが、僕はその時間を列挙すると言う作業へのだるさを感じていて以前はGoogleカレンダーと連携して空き時間をリスト化出来るサービスを使っていたのですがどうしてもちゃんとしているサービスが故に僕の目的を果たすのにはToo muchな部分や、逆に足りないなと思う部分がありました
具体的には

- 文字だけでは視認性が悪い
    - 1番手軽な方法で空いている時間を箇条書していく事も多いと思うのですが、双方にとって手間だしタイプミスなども起こりうる
- 共有したカレンダーからミーティングの予約が出来る
    - 便利だけど、空き時間を提示するだけの場面には不要
- リッチなUIが故にわざわざサイトに飛ばないといけない
    - Slackなどで時間を提示する時にURLを踏ませるのは相手への負担がある
- 既存のサービスは共有するカレンダーが1つの場合が多く、コミュニティの違いなどによって共有したい時間が違う場合に新規で作ると0から設定しないといけなくて非常に憂鬱

などなど、みなさんも心当たりある事も多いのでは？？
1つ1つの課題を解決するサービスは世の中にいくつもあるのですが、どうしても全てを解決するものが見つからなかったし、エンジニアなのでどうせなら作ってみた
というものが以下のものになります

https://sharing.tetsuya.cloud

簡単な使い方はサイトにも書いてあるのですが、サイト上でカレンダーを作成して空いてる時間をクリックして公開することで共有用リンクが発行されるのでそれをSlackに貼り付けるだけ！！
※ アーキテクチャの都合上発行されてすぐ貼ると画像が展開されないので~5秒くらい経ってから貼ってね。笑
するとSlack上でOGPが展開され、空いている時間帯をマークした画像が表示されます
これだけで簡単に、しかも分かりやすく相手に空いている時間帯を伝えることが出来ます
OGPが小さくて見辛い場合はリンクをクリックすることでサイト上でも確認出来ます
↓Slackに展開されたOGP
![スクリーンショット 11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462473/76e9f185-0fd2-45eb-af96-88c153072ba2.png)
↓OGPの画像を開いたとき
![スクリーンショット 12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462473/f79141bd-7d0c-bc08-22aa-c696e403e7f1.png)

## 使用技術紹介
内部で使った技術についても軽く触れてみようと思います
今回これを作った1番のモチベーションは先程述べたものですが、どうせ作るなら今流行のAWSの勉強もしてみたい！という事でクラウド前提のデザインパターンで作ってみました
なので、本来この程度のサービスでは不要なものまでも盛り込んでたり、無駄にリクエストを捏ねくり回してます。笑
後は個人利用なので基本的にはスケーリングなども考えたくないし、作ったら後はいい感じに動いておいて欲しいって事でその辺を意識したり、しなかったり…
早速、インフラのアーキテクチャ図です

![sharing (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462473/0d4642b0-196d-7339-00a0-cf5a36b2177c.png)




それぞれの大まかなコンポーネントに分けて説明していきます

### フロントエンド
中央の上側のサービスページのフロントはReactで書いてあり、ビルドした成果物をS3に置き、CloudFront経由で配信しています
特に凝ったことはしてないのでAxiosでAPIと通信しつつ関数コンポーネントを用いて実装しています
デザインとか見た目とかは全然考えてないのでアイディアある人は教えて下さい
`Static Files Cache`の右側のLambdaについては後ほど解説

### API
フロントエンドから呼び出されるAPIはAPI Gateway経由でGolangで書かれたLambdaを呼んでいます
API GatewayはLambda統合プロキシを利用しており、CORSなどはLambda側で対応しています
予定作成、公開などのリクエストは全てこのLambdaが受け取り、予定の登録は裏側のDynamoDBに、公開リクエストはSQSへリクエストを流しています
AWS公式のDynamoDBのライブラリを使ったんだけどクエリ書くの辛かった…
最近発表された[ParatiQL](https://partiql.org/)とか使ってみたい

### OGP画像作成
Slackでの画像表示に関してはOGPを使って表示してます
このOGPを作成するのが中央部のLambdaです
内部的には予定が公開されたタイミングでAPIからOGP画像作成のリクエストをSQSを通してPythonで書かれたLambdaが起動されます
このLambda上ではLambda Layerを用いてSeleniumを起動しています
Lambda Layerを勉強してみたいという理由だけでOGP作成は別のコンポーネントで切り出しました。笑
Layerには`headless chrome`と`chromedriver`を入れるレイヤーと`selenium`を入れるレイヤーの2枚が差し込んであります
ここで生成されたOGP画像はS3上にアップロードされます
ちなみに、ここでLambda関数のタイムゾーンを考えるの忘れてて2日程想定通りに動かずに詰んでました
(正確には、ちゃんと動いていたんだけど予定がずれて見えてバグってると思い込んでいた)

### OGP情報取得
ユーザがサービス上で公開リクエストを投げた段階でAPIから共有用リンクIDが発行されます
このIDを含めたURLを投げることでSlackなどはOGPの情報を取得しに来ます
このリクエスト自体は先程出てきた`Static files cache`のCloudFrontに飛んできます
飛んできたリクエストがOGP取得用のリクエストと判断された場合、これも興味あった技術としてCloudFrontのエッジ上でLambda関数を実行するLambda@Edgeを使い、NodeJSで書かれたLambdaがリクエスト内容を元にOGP情報を返却します
注意点としてLambda@Edgeで起動するLambda関数は`us-east-1`リージョンで作成しないといけないことですね
後、地味に悩んだのですが、Lambda@Edgeで実行されたLambda関数のログは`us-east-1`のCloudwatchではなく、アクセスされたCloudFrontのエッジが存在するリージョンのCloudwatchに出力されるので知ってないと辛いですね。内部の構成を考えたらまぁそれはそうか。という感じなのですが。
ここで指定しているOGP情報は以下のサイトの説明が分かりやすいので興味ある人は是非
[Everything you ever wanted to know about unfurling but were afraid to ask /or/ How to make your site previews look amazing in Slack](https://medium.com/slack-developer-blog/everything-you-ever-wanted-to-know-about-unfurling-but-were-afraid-to-ask-or-how-to-make-your-e64b4bb9254)

### OGP画像取得
さて、ここまででSlackのOGP情報取得BotがOGPの情報を手に入れることが出来たので後はそこに設定されている画像を取得するだけですが、これは単純に画像取得用のCloudFront(`Image Cache`)経由でS3に置いてある画像を取得するだけです

### Slack通知
画像中央下の謎に薄く表示されているやつらです
これは、いつ・どのカレンダーがどのIDで共有されたことを個人のSlackに通知する機能です
APIのLambdaからKinesisにデータを投げておいて、そのKinesisをイベントソースとするLambdaにSlackに通知させる処理を書いているだけです
何故、薄くなっているかと言うと完全に思いつきでKinesisを使ってみたもののお金かかるし特にKinesisを使う理由もないので暫くしたら消そうと思っているので…

### GraphQL
最後に1番左側のこれまた薄くなっているものについてです
これも完全に勉強がてらノリで手を出してみたものです。GraphQLを触ったことがなかったのでAPIの一部でAppSyncを使って見ました
が、結論から言うと完成しなくて本番には存在していないコンポーネントです
というのもAppSyncで発行されるエンドポイントを使っては実装出来たのですが、そのエンドポイントに対してカスタムエンドポイント(独自ドメイン)を割り当てるためにAPI GatewayやCloudFrontを手前に挟むとターミナルなどからの`curl`では通るのですが、ブラウザ経由でアクセスが弾かれるようになって挫折しました
(この辺の知識ある方、是非教えていただきたい)

## まとめ
今回このサービスを作るに至った経緯や使用技術について軽く触れてきました、やっぱりエンジニアは作りたいと思ったものを自分で作れるので楽しいですね
更に、今回はサービス要件を満たすだけでなくAWSに完全に依存することでメンテナンスフリーな技術になる用に心掛けました
今回使ったマネージドサービスは殆ど無料枠で完結し、かつSQSなどを挟むことで多少の可用性の担保にも気を使ってみました
最近はパブリッククラウドも出てきて個人のハードルが下がっていて、これくらいのインフラであればほぼお金をかけずに勉強出来てサービスを公開出来る、便利な世の中になってきましたね

今回のサービスですが、まだまだ作りきったばっかりのβ版程度のものなので必要な機能があれば追加していきたいです
欲しい機能やバグを見つけたらTwitterのDMや知っている人であればSlackからお気軽にどうぞ

それでは、本日は最後まで読んでもらいましてありがとうございます
明日からも是非アドカレ御覧ください

### PR
内定先のCyberAgentでは22卒のエンジニア採用を行っています！
https://www.cyberagent.co.jp/careers/news/detail/id=25511