バレンタインのネタ系サービスを作った

# 概要
令和になってしまいましたが、2ヶ月以上前のイベントである、バレンタインのネタ系サービスを開発していたので、今更ながら記事を書きます。
また、今回紹介するサービスはすでにクローズしているので、githubに公開しています。
https://github.com/kouryou/ValentineGacha

## 経緯
弊社では数ヶ月に1回のペースで、エンジニア全体でのLT大会が行われます。ちょうどそのLT大会がバレンタインの時期だったので、バレンタインに関するデモアプリケーションを作って登壇しました。
その後サーバーサイドのソースコードを公開しようと思い、リファクタリングをしていたのですが、他にもやるべきことをやっていたら、気がついたらこんなに時間が経ってしまいました。キング・クリムゾンのせいですね。

### メンバー
今回は(当時)新卒3人で以下のような構成でチームを組んで開発を行いました。

- サーバーサイドエンジニア
- フロントエンドエンジニア
- デザイナー

僕はサーバーサイドの開発を担当したため、今回はサーバーサイドで使った技術のお話をしようと思います。

## 作ったもの
「バレンタインガチャ」というサービスを開発しました。
![vg-log.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233620/c8f36497-935a-7ca9-bfdc-d858ba14c008.png)
↑デザイナーさんが作ってくれたロゴです

このサービスは、「チョコが欲しい人」と「チョコが余ってる人」をつなぐサービスです。
バレンタインにおける格差を是正するために開発しました。これで、チョコをもらえないバレンタインは二度と来ないでしょう。
え？「チョコをもらえないバレンタインなんてなかった」だって？
そういう人と友達になってはいけませんとお母さんに言われました。

ちなみに、「チョコが余ってる人なんているのか？」って意見が聞こえてきそうですが、そこはご安心を。足りないと困るという理由で、割と女性陣はチョコを多めに用意するらしく(チームメンバーの女性意見)、余るケースがよくあるとのことです。
もし余らなかったらコンビニにチョコを買いに行きましょう。

### 前提
前提として、 Slack APIを利用したアプリケーションになっているため、事前に「チョコが欲しい人」と「チョコが余ってる人」は、指定されたSlackのワークスペースに入ってもらいます。

#### 機能
- ガチャ機能  
「チョコが余ってる人」がチョコをあげたい人数を決めて、ガチャを回すことができます。
ガチャを回すと、「チョコが欲しい人」の中から指定した人数の当選者が選ばれます。(特に確率操作はしておらず、完全にランダムです)
- 通知機能
当選者に通知するため、以下のように当選通知のSlackが送ることができます。
かわいい子からチョコもらえる感じがあって最高です。この例では福沢諭吉にチョコが当選しました。(ホワイトデーのお返しが楽しみですね)
<img width="665" alt="スクリーンショット 2019-05-02 19.01.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233620/ecbc091a-cdb5-fc1c-b623-1adc9e44d6f8.png">

- チャンネル作成機能
当選者とチョコをあげる人が、その後のコミュニケーションを取りやすいように、専用のプライベートチャンネルを作成する機能もあります。
この機能を利用すると、以下のようにチャンネルが作成されます。
<img width="690" alt="new_channel.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233620/a28f90c0-77ed-f0fb-bc7c-4ae9e6da4a42.png">


## 技術
僕の担当したサーバーサイドの技術のみ紹介します。
今回は以下のようなサーバレスアーキテクチャで構成しました。
![ValentineGacha.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233620/baf73083-f3ca-0754-5931-bb270e53b25e.png)

利用した技術は主に以下です。

- Serverless Framework
- Python
- Slack API

ちなみに全て初めて利用しました。
それぞれについて説明していきます。

### Serverless Framework
https://serverless.com
Serverless Frameworkはサーバレスアプリケーションを簡単にビルド&デプロイできるCLIツールです。名前で勘違いしやすいですが、フレームワークというよりはツールに近いと思います。

サーバレスアプリケーション全体を`serverlss.yml`にまとめて記述できるので、AWSの画面をぽちぽちする必要がないのでいいですね。

また、慣れてくると非常に早くアプリケーションを構築できるので、素早くプロトタイプを構築したい時にも良い選択肢になると思いました。
日本の個人開発だとFirebaseが盛り上がってますが、サーバレスはAWSが進んでいるため、AWS上で構築可能なServerless Frameworkも有力な選択肢だと思います。

### Python
Pythonに関しては正直この記事で特筆する内容は少ないですが、後述するSlack APIをPythonでどう叩くかは悩んだので、ここに記したいと思います。
今回は[python-slackclient](https://slack.dev/python-slackclient/)というライブラリを使用しました。
以下は使用例で、あるチャンネルにメッセージを送るAPIを使用しています。

```python
from slackclient import SlackClient


sc = SlackClient("SLACK_API_TOKEN")

sc.api_call(
  "chat.postMessage", #API名
  channel="C0XXXXXX",
  text="Hello from Python! :tada:"
)
```
使い方はシンプルで、第一引数に利用したいAPI名を、以降の引数に各APIにおいて必要なパラメータを渡してあげるだけです。
呼び出し方が統一されてるのでシンプルで使いやすかったです。

### Slack API
今回使用したSlackのAPIを用途ベースで紹介しようと思います。

#### ワークスペースに入っているユーザ全員の情報を入手する
[users.list](https://api.slack.com/methods/users.list)というAPIを用いました。
このAPIのレスポンスを元に、その中からランダムで当選者を選ぶということをしました。

#### 画像付きの通知をする
[files.upload](https://api.slack.com/methods/files.upload)というAPIを用いました。
他にもテキストベースのメッセージに[attachments](https://api.slack.com/docs/message-attachments)を使用するという方法もありますが、URLを送信した時のような見た目になるので今回は使用しませんでした。

#### 指定した人とのプライベートチェンネルを作成する
1つのAPIで行う方法が見つからなかったので、今回は以下のように3つのAPIを使用して対応しました。

- [groups.create](https://api.slack.com/methods/groups.create)
- [groups.invite](https://api.slack.com/methods/groups.invite)
- [groups.leave](https://api.slack.com/methods/groups.leave)

チャンネルを作成して、作成したチャンネルに招待し、最後に退会するという流れです。
ちなみに退会する理由は、これらのAPIはbotユーザに権限がないため、管理者ユーザが行うことになるためです。
作成と招待だけでは、新しいチャンネルの作成者として、管理者ユーザがチャンネルに入った状態になってしまいます。
今回はチョコをあげる人と当選者のみのチャンネルにしたかったので、管理者ユーザには退会してもらうようにしました。

これで目標としていたチョコをあげる人と当選者のチャンネルが作成できました。

## 最後に
新卒でチーム組んで開発したのは凄く良かったです。
LTで登壇するというのはハードルが高いように感じますが、このように新卒でチーム組んで登壇すると、ハードルが下がるのでおすすめです。
また、チーム開発をしている中で、同期から技術的に学ぶこともあります。
さらに一人で開発するよりもモチベーションが上がるのでサボりにくくなります。至れり尽くせりですね。

4月から新卒になったエンジニアの皆さんも同期に声をかけて始めてみてはいかがでしょうか。