Common LispのためのモダンなWeb系ツール・ライブラリたち

この記事は[Nextremer Advent Calendar 2018](https://qiita.com/advent-calendar/2018/nextremer)の5日目の記事です。そして、1日目の記事「[業務を改善していこう、Common Lispで。](https://qiita.com/t-sin/items/c8f2dc5d0002769a2792)」の続編でもあります。

NextremerのCommon Lisp大好きプログラマ、t-sinです。この記事では1日目の記事では割愛した、GitHubメンション通知Bot、Nikoの内部の話をします。

## 前回までのあらすじ

GitHubのIssue／Pull Requestの確認依頼メッセージを各個手動でSlackにポストしていた現状を、**Common Lispで実装された**GitHubメンション通知Botの力で自動化したt-sin。その結果、**社内Slackのコミュニケーションのノイズが減少**し、かつ**レビュー等の依頼漏れもなく**なり、快適な開発へ一歩近づくことに成功した。

しかし、そのプログラムNiko (https://github.com/t-sin/niko) の内部についてはとくに言及されていない。いったいどのようなツール・ライブラリを用いて実装されたのか？ Common Lispでモダンにウェブアプリケーションを実装するには、どのようなことをすればいいのか！？

----

ぼくがCommon Lispを用いてウェブアプリケーションをつくるのが初めてだったため、今日においてウェブ開発にどのようなライブラリやツールを利用したのかと、それらの理解不足ゆえのハマりポイント等を解説します。

## 利用したCommon Lispのツール・ライブラリたち

Nikoをつくる上で、以下のツール・ライブラリを利用しました:

- Common Lisp処理系マネージャ: [Roswell](https://github,com/roswell/roswell)
- ライブラリバージョン管理ツール: [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)

これらのうち、RoswellとUtopianは解説しません。Roswellの基本は拙作の記事「[いまから始めるCommon Lisp](https://qiita.com/t-sin/items/054c2ff315ec3b9d3bdc)」を参照してください。Utopianについてはよくまとまった記事「[Utopian手習い #01](https://t-cool.hateblo.jp/entry/2018/09/14/201908)」があり、かつNikoでは最新の`next`ブランチではないものを利用しているためです。

### Qlot

QlotはCommon Lispのライブラリを、入手先とバージョンを指定した上で固定するためのツールです。導入と利用にはRoswellが必要です。Node.jsの`npm install`のようなものです。

Common Lispのライブラリ定義・管理システム[ASDF (Another System Definition Facility)](https://common-lisp.net/project/asdf/)でもバージョンを指定することができますが、これと異なるのは、Node.jsの`package-lock.json`のように入手先やGitのrefを保持した上で、`node_modules/`のようにプロジェクトローカルなライブラリ環境を作り出します。これによって、Roswell管理下にあるライブラリ環境とも隔離され、入手先とバージョン (GitのコミットIDなど)を指定して同じライブラリ環境を再構築することも可能です。

依存ライブラリは`qlfile`に書きます。これは手書きします。例として、Nikoで指定している依存ライブラリ指定を出しましょう。こんな感じです:

```
git utopian https://github.com/fukamachi/utopian
git dissect https://github.com/Shinmera/dissect
git mito https://github.com/fukamachi/mito
ql lake :latest
git rove https://github.com/fukamachi/rove
git cl-dbi https://github.com/fukamachi/cl-dbi

ql dexador :latest
ql jonathan :latest
ql local-time :latest
ql cl-date-time-parser :latest
ql string-case :latest
```

`ql`で始まる行は[Quicklisp](https://www.quicklisp.org/beta/)というライブラリマネージャを経由してインストールすることを表します。このファイルを定義したときの最新のライブラリを使用しています。`git`で始まる行はGitのリポジトリを指定します。こちらはタグやコミットIDを指定することが可能ですが、こちらもその時点の`master`ブランチを利用しています。

この状態で`qlot install`を行うと、ライブラリがローカルにインストールされ、同時に以下のような`qlfile.lock`が生成されます:

```lisp
("quicklisp" .
 (:class qlot.source.ql:source-ql-all
  :initargs (:project-name "quicklisp" :%version :latest)
  :version "2018-04-30"))
("utopian" .
 (:class qlot.source.git:source-git
  :initargs (:project-name "utopian" :remote-url "https://github.com/fukamachi/utopian")
  :version "git-5f397ccc7593e61314f80b678e40d4fbb45c545c"
  :remote-url "https://github.com/fukamachi/utopian"
  :ref "5f397ccc7593e61314f80b678e40d4fbb45c545c"))
...
```

もし`qlfile.lock`があった場合はそちらの内容に従ってライブラリが入ります。

### Mito

MitoはCommon LispのO/Rマッパーです。Common Lispでのクラス定義からスキーマ定義を行ったり、オブジェクトに対する、あるいはオブジェクトによるRDBMSへのCRUD操作を行うことができます。

Mitoはもちろん便利であるのですが特におもしろい点があり、それは依存しているプロダクトです。Mitoだけではないのですが、Mitoを開発された[fukamachiさん](https://github.com/fukamachi/)のプロダクトのおもしろいところは、過去のプロダクトのまったくの拡張として上位のライブラリが定義されている点です。言い換えると、コアとなるライブラリの上に必要最小限の層を被せることで新たな抽象化を行っていることです。この点がPaul Grahamがエッセイや書籍等でよく言っている`対象領域のための言語をつくり、それを繰り返していくことで目的を達する`というLispらしさをまさに体現していると言えるでしょう。

Mitoは以下のライブラリを利用し、その上でCLOSのメタオブジェクトプロトコルを利用し、素のCommon Lispのクラス定義を拡張しています。

- RDBMS抽象化レイヤー: [CL-DBI](https://github.com/fukamachi/cl-dbi)
- SQLクエリジェネレータ: [SxQL](https://github.com/fukamachi/sxql)

なので、Mitoを通じてクエリを投げるときはSxQLの記法を理解していなければなりません（CL-DBIのほうは見える必要がないので隠されています）。

Nikoではユーザ情報のみをDBに持っているため、次のようにクラスを定義しています:

```lisp
;; https://github.com/t-sin/niko/blob/d328d5ae8e8416adf6662167f8646d3360807349/models/users.lisp

(defclass users ()
  ((github-id :col-type :string
              :initarg :github-id
              :accessor users-github-id)
   (github-name :col-type :string
                :initarg :github-name
                :accessor users-github-name)
   (slack-id :col-type :string
             :initarg :slack-id
             :accessor users-slack-id)
   (slack-name :col-type :string
               :initarg :slack-name
               :accessor users-slack-name))
  (:metaclass dao-table-class))
```

いたってシンプルです。このオブジェクトを、たとえばGitHubユーザ名がわかっている状態でSlackのユーザを引いてくるコードを見てみましょう。

```lisp
;; https://github.com/t-sin/niko/blob/d328d5ae8e8416adf6662167f8646d3360807349/api/github-webhook.lisp

(defun to-slack-user-id (github-usernames)
  (let ((users (mito:select-dao 'users
                 (sxql:where (:in :github-name github-usernames)))))
    (mapcar #'users-slack-id
            users)))
```

ここでは`mito:select-dao`を用いて`users`テーブルを検索しています。テーブル名の後に検索条件を指定できるのですが、`where`の前に`sxql`とパッケージ修飾が付いているのがわかります。MitoはSxQLのクエリを検索条件として受け取るため、**`sxql`パッケージを`use-package`しない場合は、修飾が必要**になります。なので、MitoやSxQLのREADMEにある例をそのまま持ってくるときは気をつけましょう。

この点を理解せずに使い小一時間首を捻るというハマり事案が発生したので、共有です。

### Dexador

DexadorはHTTPクライアントです。他のサーバにHTTPリクエストを投げたいときなどにとても重宝します。

[fukamachi products advent calendar 2016の20日目の記事](http://blog.8arrow.org/entry/2016/12/20/201912)にて開発経緯が明かされていますが、既存のHTTPクライアントライブラリ[Drakma](https://edicl.github.io/drakma/)を置き換えるために開発されたそうです。

Drakmaの微妙なところやハマりどころを解消するという目的で開発されているため、さくっと使えてしまいます。以下はCommon LispのREPLでNikoのGitHub用Webhook APIを叩いてみたところです。

```lisp
CL-USER> (dex:POST "http://niko-no-url/api/github/webhook")
""
200
#<HASH-TABLE :TEST EQUAL :COUNT 8 {10036ECD33}>
#<QURI.URI.HTTP:URI-HTTP http://niko-no-url/api/github/webhook>
NIL
```

### Jonathan

JonathanはJSON文字列のエンコーダ・デコーダです。それも、既存のものより高速なのです。昨今のウェブ開発ではJSON形式でデータをやりとりすることが多いため、とてもお世話になっております。

Jonathanのパッケージのニックネームが`jojo`となっており、おそらく奇妙な冒険をするあの人が元ネタなんじゃあないでしょうか？

利用するときは通常次の2つのAPIを知っておけばよいでしょう。JSON文字列をCommon Lispのplistに変換する`jojo:parse`と、逆にplistからJSON文字列を得る`jojo:to-json`です。以下は文字列からplistを得る例です。

```lisp
CL-USER> (jojo:parse "{\"key\": \"value\", \"array\": [1, 2, 3, 4]}")
(:|array| (1 2 3 4) :|key| "value")

CL-USER> (jojo:parse "[true, false]")
(T NIL)
```

plistから文字列を得る例はこんな感じです。

```lisp
CL-USER> (jojo:to-json (jojo:parse "{\"key\": \"value\", \"array\": [1, 2, 3, 4]}"))
"{\"array\":[1,2,3,4],\"key\":\"value\"}"

```

ちなみに、`nil`はCommon Lispでは空リスト（空のplist）、偽値、Null値など複数の意味がありますが、JSONはそうではありません。たとえば`null`を返したいときにこう書くと期待と異なる値が戻ってきます:

```
CL-USER> (jojo:to-json '(:|null| nil))
"{\"null\":[]}"
```

実は、Jonathanでは`nil`は常にJSONの`[]`に対応すると決められています。では空のオブジェクトとか`false`とかを返すにはどうしたらよいのか。答えは`jojo:parse`の説明をちゃんと読むと書いてあります。

```lisp
CL-USER> (let ((jojo:*null-value* :null)
               (jojo:*false-value* :false)
               (jojo:*empty-array-value* :[])
               (jojo:*empty-object-value* :{}))
           (jojo:to-json '(:|null-value| :null
                           :|false-value| :false
                           :|empty-array| :[]
                           :|empty-obj| :{})))
"{\"null-value\":null,\"false-value\":false,\"empty-array\":\"[]\",\"empty-obj\":\"{}\"}"
```

スペシャル変数として定義された`*null-value*`たちに望みの値を束縛してあげることで、好きな値を使うことができます。例だけでなんとなく理解をしているとこういうときに躓くので、READMEはちゃんと読みましょう。

## おわりに

GitHub上の通知を補足して通知するプログラムNikoをウェブアプリケーションとして実装するときに利用したライブラリ群を、ハマったポイントと共に紹介してみました。紹介したライブラリを利用するにあたってドキュメントのみならず、ライブラリやその依存ライブラリのソースコードを少し覗く機会も得たので、とても勉強になりました。

なお、Nikoは画面数も少なくAPIとしてのエンドポイントも少ないところにフルスタックフレームワークであるUtopianを利用したので、後々[Caveman2](https://github.com/fukamachi/caveman)や[Ningle](https://github.com/fukamachi/ningle)に移行するような気がします。