Antikythera framework + mongodbでAPIサーバを作ってみた

#前置き
最近新しくElixirという言語を勉強していて，ruby on railsみたいなフレームワークがないか探していたところ，Antikytheraという面白そうなフレームワークに出会ったので，少し遊んでみた．今回はその導入過程で色々試行錯誤したので，それについて書いていく．

Antikythera公式：https://github.com/access-company/antikythera

この記事はWSL上で作業することが前提なので，コンソールでの入力はすべてWSLで叩くことを想定している．curlコマンドだけPowerShellとかそういう変態的なことはしてないので，動作は未確認．また，作業する上でつまずきそうな所は下の方にまとめてあるので合わせて参照されたい．

#概念的なところを少し
RubyにはRuby on RailsというRubyの名をとどろかせる発端となった有名なフレームワークがある．このフレームワークを模倣して，Elixir向けに作られたのがPhoenixというフレームワーク．今のところElixirだとこのPhonixがデファクトっぽい．
対して，今回紹介するAntikytheraはつい最近OSS化したElixir向けフレームワーク．詳しくは公式を参照してもらうとして，ざっくり理解している範囲で説明すると，Antikytheraの恩恵は1つのサーバー上で複数のサービスを動かせる点らしい．Ruby on RailsやPhoenixだと複数のサービスを提供するには複数のサーバーソフトを起動させる必要があって，どんどん管理コストが上がっていく．Antikytheraではインスタンスと呼ばれるベースを用意してあげて，その上で複数のサービスを同居させるらしい．このサービスの単位をギアと呼んで，ギア同士の通信も可能な構成になっているとか．それでいて各ギアはサブドメインの形で提供されるから，見かけ上は別個のものになる．

#環境
ちょっと特殊だけどWindows上にWSLでUbuntuを入れて，その上で動かした．多分ネイティブなUbuntuでも問題無く動くと思う．

- Windows10 Pro (バージョン1903)
- WSL(Ubuntu 18.04.2)

#目標
書く順番が前後してしまっているが，今回の目標は簡単なAPIサーバをAntikytheraで作る事．getメッセージを投げたら，適当なデータをデータベースに格納して，何かしらのjson形式のデータを返してくれるサービス構築を目指す．

#準備
- WSLとかUbuntuとかの環境構築（gitが必須）
- githubアカウント（Antikytheraのギアを作るために必須っぽい）

#導入方法
この導入方法はmongodbを使うために公式のドキュメントから外れた操作もしているので，データベースとの連携が必要ないと言う場合は公式の[Getting Started guid](guide：https://hexdocs.pm/antikythera/getting_started.html)を読む方が良いと思う．

###Antikytheraのリポジトリをクローン
mongodbを使うために，Antikytheraに変更を加えるので，公式のリポジトリをフォークする．フォークするリポジトリは以下2つ．

1. https://github.com/access-company/antikythera
2. https://github.com/access-company/antikythera_instance_example

1がAntikytheraのギアの原型で，2の方はインスタンス用のコードなんだろうか（このへんはあまり理解できていない）．実際にやっていることとしては2のリポジトリ中でmixタスクを実行してギアのテンプレを作るのだが，その時に1をベースに作るっぽく，2のmix.exs中で1への参照が記述されている．

フォークができたら，フォークしたそれぞれのリポジトリをクローンする．

```
git clone git@github.com:(githubアカウント)/antikythera.git
git clone git@github.com:(githubアカウント)/antikythera_instance_example.git
```

###mongodbのライブラリ参照を追記
mongodbへの参照を追記して，新しいギア作成時に依存ライブラリにmongodbが含まれるようにする．ライブラリ名はmongoというライブラリとmongodbと言うライブラリがあるので気をつけること．mongoの方を選択すると他のライブラリと競合を起こすのかコンパイルできず，解決することができなかった．

```Antikythera/mix.exs
(125行目付近)
+ {:mongodb, ">= 0.0.0"},
```

追記をしたら，依存ライブラリを取得してからAntikytheraをコンパイルする．deps.getを2回してるのは間違いではない．依存ライブラリの依存ライブラリを取得してるのだと思う．

```
$ cd /path/to/Antikythera
$ mix deps.get
$ mix deps.get
$ mix compile
```

コンパイルまで完了したら，git commitしてgithubにpushする．pushができたら，github上でコミットハッシュを確認してメモしておく．ギア生成時にこのコミットハッシュが参照されてテンプレとなるプロジェクトが作られるというカラクリ．

###ギアテンプレートの参照書換
ギアテンプレートと呼ぶのが正しいのかどうかわからないけども・・・・
テンプレの参照をさっきメモったコミットハッシュで書き換える

```Antikythera_instance_example/mix.exs
（3行目）
- antikythera_dep = {:antikythera, [github: "access-company/antikythera", ref: "5637618356382334de8da689ef0ed56cf0b51846"]}
+ antikythera_dep = {:antikythera, [github: "(githubアカウント)/antikythera", ref: "さっきメモったコミットハッシュ"]}

(40行目）
- github_url = "https://github.com/access-company/antikythera_instance_example"
+ github_url = "https://github.com/(githubアカウント)/antikythera_instance_example"
```

こちらも依存ライブラリを取得してコンパイル

```
$ cd /path/to/Antikythera_instance_example
$ mix deps.get
$ mix deps.get
$ mix compile
```

コンパイルできたらcommitしてgithubへpush．検証できてないないがpushしないとギア生成時に参照が更新されてないかも・・・

###新しいギアプロジェクトの作成
こちらもギアプロジェクトと呼ぶのが正しいのかわからんけども・・・

```
cd /path/to/Antikythera_instance_example
mix antikythera.gear.new (任意のディレクトリ)/example_gear
```

これで任意のディレクトリ配下にexample_gearが生成されているはず．

###ギアのコンパイル
こちらもリモートリポジトリにpushできていないと依存ライブラリの取得からコケてしまうので，まずは新しく生成したギアのプロジェクトでInitial Commitをして，githubにpushする．pushできたらコンパイルしていく．

```
$ cd /path/to/example_gear
$ mix deps.get
$ mix deps.get
$ mix compile
```

問題無くコンパイルができたら，取りあえずの導入は完了．

###動作確認
ギアのプロジェクト内でサーバを立ち上げる．

```
cd /path/to/example_gear
iex -S mix
```

ブラウザで，[http://example-gear.localhost:8080/hello](http://example-gear.localhost:8080/hello)に接続して何かしら見えたら成功．ちなみに，ブラウザでうまく名前解決ができなければ，hostsファイルにexample-gear.localhostを127.0.0.1に解決するよう追記すると見れるはず．

#データベースとの連携
データベースとうまく連携できるかチェックするために，APIの中身を少し弄る．以下の内容でもとからあるhello functionを上書きする．

```example_gear/web/controller/hello.ex
  def hello(conn) do
    {:ok, dbconn} = Mongo.start_link(url: "mongodb://localhost:27017/hoge")
    res = Mongo.insert_one(dbconn, "testcollection", %{"foo" => "bar"})
    IO.inspect res
    Conn.json(conn, 200, %{"ping" => "pong"})  
  end
```

適当な実装なので色々酷いのは気にしない．これでcurlコマンドでhello APIを叩けばmongodbにhogeというコレクションが作られて，{"foo": "bar"}というドキュメントが作成されるはず．コンソール側には{"ping": "pong"}というふざけたレスポンスが帰る．当然のことながらmongodbが立ち上がっていなかったらconnection refusedでサーバー側がエラーを吐く．

```
$ curl http://example-gear.localhost:8080/hello
{"res":"pong"}
```

コンソールだとほぼ間違いなく名前解決ができないので，/etc/hostsに127.0.0.1とexample-gear.localhostの対応を追記しておく．

APIが正しく機能していたら，mongodbにhogeというコレクションが生成されて，{"foo": "bar"}が追加されているはず．

#つまづきポイント
###mongodb on WSL
WSLだと，systemctlコマンドが許可されていないので，systemctlコマンドでの起動ができない．下記コマンドでとりあえずは対応可能．継続的にWSL上でサービスを動かすとなるともう少し手を加える必要はありそう．

```
$ mongod --config /etc/mongod.conf
```

###依存パッケージ
若干うろ覚えなのだが，inotifyをインストールしろとかなんとか警告されてような気がする．ubuntuならinotify-toolをaptで入れる．

```
$ sudo apt install inotify-tool
```

# あとがき
今回はとりあえずAntikytheraを導入してAPIの動作確認するところまでできた．実際にインスタンスをどうやって運用するとか，サービス運用まではちゃんと調べ切れてないので，そのうちやりたいと思う．あとwebsocketを使う方法も．

指摘とか疑問とかあれば教えて頂けると幸いです．