Clojureによるサービス/Bot開発まとめ
Service/Bot/Scheduler development with Clojure.
ClojureのBot開発についての知見をまとめていく. フロントエンドを持たないバックエンド開発, バックグラウンドタスク, スケジューラ, Botなどなど…
Clojureで手続きプログラミングなloopを書いたり状態を管理するベストプラクティスを知りたい. どうも手続き的な方法をそのまま導入することには違和感がある. もしくはフレームワークつかうか. 現在の仮の答えはステートチャート系のフレームワークの利用(clj-statechats).
HTTPプロトコルに依存するものは📝Clojure Web Development, もしくは📝Clojure API Server Development.
クローラー開発の定期実行はこのメモに, スクレイピング技術は📝Clojure Scrapingへ.
ドメイン特化はサブメモへ.
Clojureイベントループ実装
cronをclojureで実現しようとしたときは無限の遅延シーケンスを扱うことになる.
ネットの情報だとwebサーバが多いのだが, もはや📝Web Server Abstructionやそのフレームワークは内部の仕組みが隠蔽されて完成されているので, なかなかサーバのなかがなにをやっているのかよくわからない.
これはライブラリを使うのがいい. chimeがまず選択肢として上がる.
Clojureでの無限ループ
もっとも基本的な実装はloop/recurをつかった無限ループ実装. 簡単な処理ならこれでいい.
(loop []
(Thread/sleep 1000)
(recur))
core.async/timeoutをつかう
他には, 📝Clojure core.asyncのgo-loopをつかう.
ref. How to schedule a list of functions `n` seconds apart with Clojure Core Async - Stack Overflow
(a/go-loop []
(a/<! (a/timeout 3000))
(println "Hello!")
(recur))
止まらないので注意. 止めるにはexit-chみたいな外部から操作できるchannelを別途用意.
- refs.
componentやintegrantのようなClojure: 状態管理とシステムを導入して終了時に確実にchannelを閉じる仕組みをいれるのもよい.
(defmethod ig/init-key ::watcher [_ _]
(let [ch (a/chan (a/sliding-buffer 1))]
(a/go-loop []
(if-let [msg (a/<! ch)]
(do
(do-something!)
(a/<! (a/timeout 3000))
(recur))
(println "Done.")))
ch))
(defmethod ig/halt-key! ::watcher [_ ch]
(a/close! ch))
Clojure Schedular: 定期実行
📝cron的なものをClojureでどうやるか.
状態管理
うっかりREPLとかで無限ループを走らせるとREPLを再起動しないと止められないため, 状態管理の工夫が必要(start/stop). Clojureには状態管理ライブラリがあるのでそれらとくみあわせるといい.
以下の例ではduct(integrant)にchimeを組み込んでいる.
duct-frameworkに定時起動ジョブを仕込む - Hash λ Bye
状態遷移
Finate State Machine的なものを導入する.
FSMはReact界隈でも最近注目が集まっている(📝XState). この背景には宣言的なステートマシンの記述が関数型言語と相性がいいからかもしれない. するとClojureで状態を扱う方法としてのFSMは相性がいいことが類推できる.
Clojure: バックグラウンド実行
🔧Clojure: Chime
Clojureのスケジューラ開発のためのフレームワークで有名. チャイムと読む.
https://github.com/jarohen/chime
- chime/chime-at: で指定時刻に実行.
- chime/periodic-seq で無限ループ
chimeの設計
時間ごとの無限シーケンスをperiodic-seqで作成して, chime-atでその無限シーケンスを一つずつ消費していく. source(GitHub)
java.util.concurrent.ScheduleExecutorServiceの .schedule をつかっている.
ref. ScheduledExecutorService 使い方メモ - Qiita
💡chimeのループを止める方法
Returns an AutoCloseable that you can `.close` to stop the schedule. You can also deref the return value to wait for the schedule to finish.
(chime/chime-at)の戻り値をatomなどに保持しておいて, そのvalueに対して.closeを呼ぶと止まる.
(def state
(atom ((chime/chime-at interval-seq
exec
{:on-finished stop})))
(.close @state)
💡エラー発生時の運用継続/中断
:error-handlerのタグにハンドラーを渡すとエラー時の処理を記述できる.
さらに関数がtrueを返すと運用継続, not trueならば運用中断する.
chimeで無限ループ(periodic-seq)
chime/periodic-seqをつかうと無限遅延シーケンスが生成できる. これをchime-atの引数に渡す.
chime-atで生成したオブジェクトは .closeをよばないとずっと動いているので注意.
(def sched (chime/chime-at (chime/periodic-seq (Instant/now) (Duration/ofSeconds 1))
(fn [time] (println "Chiming at" time))))
(.close sched)
chimeの無限ループをmain threadにくっつける方法
<!! をつかう.
(let [now (Instant/now)
chimes (chime-ch [(.plusSeconds now 2)
(.plusSeconds now 3)])]
(a/<!! (go-loop []
(when-let [msg (<! chimes)]
(prn "Chiming at:" msg)
(recur)))))
READMEのサンプルそのものだが, a/<!!の部分でメインスレッドをブロッキングしている.
ref. How to make a Clojure go loop run forever - Stack Overflow
🔗References
- duct-frameworkに定時起動ジョブを仕込む - Hash λ Bye
- integrantとchimeのcore.async連携例.
Topics
💡Clojure ランタイム実行
ClojureはJVMランタイムが重いのでスクリプト用途には向かない
ClojureはJVMランタイムが重いのでスクリプト用途には向かない. たとえばclojure/clj -Xで実行するようなもの. 起動が遅く, メモリ使用量が大きい. 立ち上げるだけで数100MBが必要. Node.jsやRuby, Pythonのようなスクリプト要素では厳しい.
選択肢しては以下.
- 🔧Clojure CLI(tools.deps)
- clj/clojure
- 📝ClojureScriptをつかう. これはNode.jsと同等.
- 🔧nbbのような特化環境もある.
- 🔧GraalVMと組み合わせる.
無限ループを止めるにはどうすればいいの?
Javaの仕組みを利用しているのでJavaの方法を検索するといろいろでてくる.
- フラグで制御
- (core.async/channelを仕込む)
- 外部からのinterrupt
- java.io.Closableを継承することによって.close呼び出し.
あまり自分で頑張らずになんかのフレームワークにのっかったほうがいいかも.
起動と終了(start/stop)の機能追加をするには?
Clojure: 状態管理ライブラリを利用する.
👉Related
- up: 📂Clojure開発
- tags: 🔖Bot
- refs.