業務を改善していこう、Common Lispで。

この記事は[Nextremer Advent Calendar 2018](https://qiita.com/advent-calendar/2018/nextremer)の1日目の記事です。

NextremerのCommon Lisp大好きプログラマ、t-sinです。普段のおしごとではクライアントサイドやサーバーサイドで主にJavaScriptを、たまにRubyやPythonを書いて暮らしています。

この記事では、社内Slack上でのコミュニケーションノイズとなっていたレビュー依頼メンションを、Common Lispを用いて、大事なことなのでもう一度言いますが、**Common Lispを用いて改善した**ことを紹介します。

## 背景

弊社ではソースコードのリポジトリホスティングサービスとしてGitHubを用いています。開発プロジェクトのソースコードはそこにホスティングされ、課題の管理やソースコードレビューなどにもGitHubのIssue機能やPull Request機能を利用しています。

ぼくが入社したてのころ、Issue上の確認依頼やPull Requestのレビュー依頼などはSlack上でリンクを提示しつつ依頼メッセージをメンションとしてポストすることで行われていました。たとえばこんな感じです:

> t-sin [今日 12:06]
> @reviewer-san ほげほげの改修、PR作成しました。レビューおねがいします:bow:
> https://github.com/kaisha/oshigoto/pull/123

そして、それに対してレビューをしたよと、これもまたSlackでメンションしていました。

> reviewer-san [今日 13:06]
> @t-sin レビューしましたー。何点かコメントしたので確認願いますー。

ふむ。これだけで2メッセージが画面を占拠します。また、こういう場面もあります。だれかが既存のIssue内で、確認依頼を出す、という場面です。

> dareka-san [今日 14:06]
> @t-sin ふがふがの仕様についてコメントしましたー。
> https://github.com/kaisha/oshigoto/issues/123

複数のレビュー依頼やIssue確認依頼がたまたま重なると、チャネルの上でノイズとなってしまいます。また、そもそもこのやりとりって、Issue上でメンションしたり、Pull Request等でレビュアーにアサインされたタイミングに自動で通知してほしくないかしらんと感じだしました。だんだんめんどうで、かつ退屈に感じはじめたのです。

退屈なことはCommon Lispでやりましょう。
そうすればきっと楽しくなること間違いなしです。

そんなわけでぼくは、人生初のCommon Lispによるウェブアプリケーションを書き始めたのでした。

## 仕様策定

まずは要件を整理してみます。

Slack上でメンションをするので、SlackのBotとして動く必要があります。そのためには[SlackのWeb API ドキュメント](https://api.slack.com/web)を読んで、使い方やBotとしての登録方法等を学ぶ必要があるでしょう。また、SlackユーザとGitHubユーザの対応をどこかに持っていなければなりません。

GitHub上のイベントを取得するのには、[GitHub Webhooks ドキュメント](https://developer.github.com/webhooks/)にあるペイロードを処理するAPIプログラムとして動くとよいでしょう。ちなみに[GitHubのEvent API](https://developer.github.com/v3/activity/events/)は、リアルタイムのイベント取得には向いていないので、今回の用途には使えませんでした（試して撃沈しました😭）。

### アプリケーション概要

事前の調査が終わったところで、アプリケーションが実在することを妄想していってみると、こんな感じになりました:

- SlackユーザとGitHubユーザの対応表をDBに持つ
    - ユーザを登録できる画面つき
    - めんどくさいのでDBはSQLiteとする
- GitHubのWebhookとして動くAPI `POST /api/github/webhook`をもつ
- SlackのBotとして、上記Webhookが叩かれるたびにアクションする
    - 特定のイベントのとき、ユーザ名をDBから検索してSlack上でメンションを送る
    - Slack用の認証トークンは環境変数を介して設定する
    - メンション用メッセージをポストする先のchannelは事前に用意しておき、環境変数で設定する

### 使用言語・ライブラリ等

言語はもちろん**Common Lisp**なのですが、とくに[Roswell](https://github.com/roswell/roswell) (Common Lispの処理系インストーラ・スクリプティング環境)のデフォルト処理系でありスクリプトのビルドをサポートしているという理由で **[Steel Bank Common Lisp (SBCL)](http://www.sbcl.org)**を選択しました😁

ライブラリについては前述の仕様から、だいたい以下のようなライブラリが必要になりそうです:

- ライブラリバージョン管理ツール: [Qlot](https://github.com/fukamachi/qlot)
- ウェブアプリケーションフレームワーク: [Utopian](https://github.com/fukamachi/utopian)
- O/Rマッパー: [Mito](https://github.com/fumakachi/mito)
- HTTPクライアント: [Dexador](https://github.com/fukamachi/dexador)
- JSONエンコーダ・デコーダ: [Jonathan](https://github.com/Rudolph-Miller/jonathan)

ウェブアプリケーションライブラリとしてUtopianを選択したのは、使ってみたかったからです。ガチ案件に使うものではないため、あまりノウハウの溜まっていないものを選択し、楽しくつくることができます。使い方がわからなくて、ハマってうんうん唸っている時間の愛おしさといったらありませんよね？？

## 実装

この記事では実装の詳細には立ち入りませんが、休日や仕事の合間にもりもりと実装していった結果、このようなものができあがりました。

- https://github.com/t-sin/niko

このNikoを開発するときに初めてQlot, Utopian, Mitoを利用したので、それぞれの使い方をREADMEやソースコードを読んで調査しながら開発しました。たしか、構想三日、製作期間二週間、くらいだったと記憶しています。

接続に必要なトークン情報は環境変数を通じて行います。

実行環境としては、Common LispはAWS Lambdaなどのマネージドプラットフォームは利用できないので、EC2インスタンスの上で直に起動スクリプトを叩いて実行しています。このあたり、もっと改善したいところです。

## 実際の動作イメージ

Nikoにはトップ画面、ユーザ登録画面、ユーザ一覧画面の3画面のみがあり、その他はAPIとして動作します。画面は、たとえばトップ画面がこんな感じ

![トップ画面](https://qiita-image-store.s3.amazonaws.com/0/109205/18ca7751-e04c-7fee-092f-cfd5ed071b5b.png)

で、ユーザ一覧画面でこんな感じ

![ユーザ一覧画面](https://qiita-image-store.s3.amazonaws.com/0/109205/75644f53-63c9-4c80-8daf-3cd14cf9781b.png)

です。ここあたりはまあふつうの小さなウェブアプリケーションです。

気になるSlack上での動作イメージはこんな感じです。

![Slack上のNiko](https://qiita-image-store.s3.amazonaws.com/0/109205/c9ad19b8-da5c-831f-b071-870c078013ea.png)

## Nikoを導入してみて

Nikoを社内で運用しはじめて、その存在を周知していったところ、Slack上の案件チャネルで行われていたレビュー依頼・Issue確認依頼が徐々にNikoを経由して行われるようになり、現在ではSlack上でのGitHub系確認依頼はほぼ完全に姿を消しました。「ほぼ完全に」というのは、Niko自体が落ちてしまっている場合には手動で依頼を行う必要があるためで、そろそろ死活監視の必要を感じています。

また、運用しだしてから気づいた点として、Issueのアサインや、Pull Requestのレビュアーアサイン、Pull Requestのレビュー機能のコメント等も自動で通知したほうが便利である、という点があります。実装時には「不要だろう」と思って消した機能だったのですが、初期開発時の想定課題と現実の課題は往々にして異なるということを改めて実感しました。

なにより、Common Lispでも実世界の課題を解決できるシステムを開発することができることを実感できたのは、いちCommon Lisp使いとしてとても誇らしいことだと思いました。Common Lispの高品質な処理系をFLOSSライセンスで公開してくれているコミュニティや、利用したライブラリ・ツール群を開発されたハッカーの方々に、感謝します。

## Nikoのこれから

上記にも挙げましたが、監視システムの導入や冗長化が必要な時期が差し掛かってるようです。要因はいくつかあります。ひとつに、手動の確認依頼の面倒さが広まり、Nikoが落ちているときのコミュニケーションコストが上がりました。また、バックオフィス系のほうにもNikoを転用しようという動きがでてきており、社内のインフラとなっていくかもしれない空気が漂っているからです。

また、今はDBにSQLiteを用いており冗長化もなにもない点[^1]や、ソースコードをクローンして展開するだけのデプロイや、Common Lispスクリプト実行による起動など、煩雑な手順を踏まないとセットアップできない点[^2]、Pull Requestのレビュアーを沢山アサインすると、人数分のメンションが飛んでしまう不具合がある点、など改善点はいくつもあります。まだまだ当分はNikoで楽しめそうな気配があります。

こうして、ひとつのCommon Lispプログラムでひとつの業務改善を為し遂げました。つぎに何をするかはわかりませんが、ぼくの**素敵Common Lisp侵略計画**は少しずつ着々と進んでいっているのです😎

----

Nikoをつくってみてわかったことについては、12/05(水)のアドベントカレンダー記事にまとめます。乞うご期待！

[^1]: 2018-12-14追記。この点は2018-12-05くらいにPostgreSQLを利用するように改善しました。
[^2]: 2018-12-14追記。この点は2018-12-05くらいのバージョンでモリッとシングルバイナリ化しました。それによりデプロイプロセスの単純化、systemdサービス化などがとても容易になりました。