Elixirでcronライクなライブラリ「quantum-elixir」を使ってみた！

## 1. やりたいこと

作成したElixirのライブラリを呼び出すジョブを定期的に叩きたいな、と思いました。

* cronを使う。    -> でもsystemdとかになったらTime Unit使わなきゃいけないしなんとなくわかりづらい。。
* Jenkinsなどのジョブスケジューラコンテナを立てたりする。   -> 一個だけしか動かしたくないし、牛刀か。。


ということで、Javaのquartzみたいなライブラリないのかな、とあさっていたら見つけました。

* [github: quantum-elixir](https://github.com/c-rack/quantum-elixir)

## 2. 作者の人の資料と内部実装について

以下でご紹介されているようです。

* [c-rack.github.io/job-scheduling-in-elixir](https://c-rack.github.io/job-scheduling-in-elixir)

pointは２つあるようで、

1. timeスケジューラのエイリアスを定義しているが、List.foldr等で変換処理を行っているよ
2. timer処理を`Process`ライブラリを利用して管理しているよ

と言ったとこでしょうか。2. については`Macro`という表記がスライド中にあるのですが、まだ私はキャッチアップが終わってないのでこれから追いかけてみようと思います。楽しそうですね＾＾

`1. についてのコード`

```ex
defmodule Quantum.Translator do

  @moduledoc false

  @days   ~w{sun mon tue wed thu fri sat}
  @months ~w{jan feb mar apr may jun jul aug sep oct nov dec}

  # Replaces all occurrences of abbreviated day and month names by their index
  def translate(s) do
    {s, _} = List.foldl @days,   {s, 0}, fn n, acc -> do_translate acc, n end
    {s, _} = List.foldl @months, {s, 1}, fn n, acc -> do_translate acc, n end
    s
  end

  defp do_translate({s, i}, n) do
    {String.replace(s, n, "#{i}"), i + 1}
  end

end
```

`accumulator`のnの値を１個ずつincrementすることで、`@days`や`@months`を数字に変換してますね。


`2. についてのコード`

```ex
defmodule Quantum.Timer do

  @moduledoc false

  def timezone_function do
    case Application.get_env(:quantum, :timezone, :utc) do
      :utc ->
        &:calendar.now_to_universal_time/1
      :local ->
        &:calendar.now_to_local_time/1
      timezone ->
        raise "Unsupported timezone: #{timezone}"
    end
  end

  def tick do
    {d, {h, m, s}} = timezone_function.(:os.timestamp)
    Process.send_after(self, :tick, (60 - s) * 1000)
    {d, h, m}
  end
end
```

ここがちょっとまだ？な状態です。`&`演算子ってなんだっけ。。

`send_after`については[elixir Process api](http://elixir-lang.org/docs/stable/elixir/Process.html#send_after/3)を見てみます＾＾

## 3. 使ってみました

以下のリポジトリで使ってみました。

* https://github.com/letusfly85/github_stalking


### 3-1. mix.exsを編集

quantumを追加しました。

```ex
  defp deps do
    [{:mix_test_watch, "~> 0.2", only: :dev},
     {:tentacat, "~> 0.2"},
     {:riak, "~> 1.0"},
     {:poison, "~> 2.0"},
     {:quantum, ">= 1.6.1"},
     {:factory_girl_elixir, "~> 0.1.1"},
     {:credo, "~> 0.3", only: [:dev, :test]}]
  end
```

applicationにも追加が必要なようです。

```ex
defp applications(_all), do: [:logger, :tentacat, :riak, :quantum]
```

### 3-2. quantum用のconigを作成

`Mix`の`config`ファイル設定で、スケジュールと実行させるメソッドを定義づけることができるようです。

[README](https://github.com/c-rack/quantum-elixir#usage)を参考にしつつ、毎分起動するジョブを定義しました。

```ex
config :quantum, cron: [
    # Every minute
    "* * * * *": {GithubStalking, :say_hello}
]
```

呼ばれている関数の実態は以下のとおりです。

```ex
  def say_hello() do
    Logger.info("Hello! World!")
  end
```

### 3-3. 常駐プロセス型のメソッドを用意する

ちょっとカッコよく書きましたが、お試しなので、無限ループするメソッドをちょろっと用意して試すことにしました。

毎秒インクリメントされた数字を表示するようにします。

```ex
  def my_loop(i) do
    :timer.sleep(1000)
    Logger.info(i)
    my_loop(x+1)
  end
```

### 3-4. mix runで実行する

```ex
mix run -e 'GithubStalking.my_loop(0)'
```

もちろん、この時点でdetatchしても良さそうですね。

### 3-5. 出力の様子

```ex
06:17:55.557 [info]  1893

06:17:56.562 [info]  1894

06:17:57.564 [info]  1895

06:17:58.566 [info]  1896

06:17:59.569 [info]  1897

06:18:00.236 [info]  Hello! World!

06:18:00.573 [info]  1898
```

と、上記のような感じで毎分出力され続けます。

常時実行しているプロセスに対して割り込みのような形で起動しているのか、などなどはきちんとソースを読んで確認してみようと思います＾＾

## 4. 所感

* 設定自体は簡単なので気軽にお試しができそう。
* ソースも小さいし、勉強がてら読むのには良さそう。
* エラーハンドリングがしづらい。。ジョブが動いた時に、ジョブの内部でエラーが起きた時に出力されるエラー内容がきちんと表示されないような。。（ソース読もう！）

## 5. 参考

* [c-rack.github.io/job-scheduling-in-elixir](https://c-rack.github.io/job-scheduling-in-elixir)
* [java : quartz](https://quartz-scheduler.org/documentation)
* [qiita: quartz APIの基礎](../../uzresk/items/21cb62e7ee38c92a7aca)
* [定期実行するタイマーPetickを作った](http://niku.name/articles/2016/02/08/%E5%AE%9A%E6%9C%9F%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E3%82%BF%E3%82%A4%E3%83%9E%E3%83%BCPetick%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%9F)

---

本日は以上となります。