📝Clojure Web Development
ClojureによるWeb開発ノウハウまとめ.
tags: 🏷Clojure 🏷Web Development
テーマが大きいので分割するかも.
- API Server, Webバックエンドはこちら: 📝Clojure API Server Development
- API Client はこちら: 📝Clojure API Client Development
- Webフロントエンドはこちら: 📝ClojureScript Frontend Development
Clojure Web Development概論 #
ClojureではRailsやDjangoのようなデファクトスタンダードなWebフレームワークをつかうよりも 小さなライブラリを組み合わせて開発することが多い.
そのため機能ごとにいろんなライブラリが存在する.
Clojure: Web Frameworks #
- Luminus
- Duct (正確には状態管理ライブラリ + モジュール作成テンプレート).
ref: 🏷Web Framework
Clojure: Ring #
- refs.
Clojureにおける Web サーバ抽象 のデファクトスタンダード.
- Why Use Ring? · ring-clojure/ring Wiki · GitHub
- なぜRingをつかうのか?
- WebアプリをClojureの関数とMapデータのみで構築するという設計概念を示す.
- Java servletの上で走るアプリにコンパイルする.
- なぜRingをつかうのか?
- 4つのコンポーネントからなる(ref).
- Handler
- Clojureの関数で表現される.
- Request Mapを受取り, Response Mapを返す.
- Request
- Response
- Middleware
- Handler
ref: 🏷Web Server Abstruction 📝Clojure: Pedestal
ring: middleware #
ref. ring
- wrap-params(ring.middleware.params)
- ringはデフォルトではrequestに付随するパラメータに対してなにもしない. wrap-paramsを利用すると パラメータを処理してくれる.
- (通常getからの) urlについたquery-paramsを :query-paramsにbind.
- (通常postからの) bodyの中のform-paramsを :form-paramsにbind.
- :query-paramsと :form-paramsのデータを :paramsにマージ. そのため大抵は :paramsをチェックすればデータが入っている.
- wrap-keyword-params(ring.middleware.keyword-params)
- ring は request-mapのkeyをdefaultではstringとして扱う.これをkeywordに変換する.
- wrap-json-paramsの:json-paramsは ring parameter mapである :paramsにマージされる. しかしring paramsは mapのkeyがkeywordではなくstringとして扱うため wrap-keyword-params との併用が必要.
ring-json: middleware #
ref. ring-json (ring.middleware.json)
- wrap-json-response
- response-mapのbodyのColojure MapやVectorをplain/textのJSONに変換.
- wrap-json-body
- request-mapのbodyのjson形式の文字列をclojure collectionに変換して :bodyにbinding.
- wrap-json-params
- request-mapの bodyの json形式の文字列を Mapに変換して(:body :json-paramsにbinding.
tips: run-jettyにhandlerのvarを渡してhot reloading #
varは リーダマクロ #' にて取得できる. すなわち,
(run-jetty #'handler {:port 3000})
詳しくは以下を参照.
- Column: REPL 駆動開発を取り入れて Ring でもう少し遊んでみる — Clojure の日本語ガイド
- 何故、RingハンドラーにVarを渡すと、ハンドラーを書き換えても書き換えたハンドラーが呼び出されるのか - ayato-p
varというのが参照型のため実際を差し替えられる.
References #
- clojure-ring - 開発者ドキュメント
- Part2: Ring について知る — Clojure の日本語ガイド
- あやとぴさんのWeb 開発チュートリアル.
- ClojureのWeb開発でもっとも重要なRing Handlerについて理解する - TOYOKUMO
- Clojure Ring Middleware大全 - TOYOKUMO Tech Blog
- トヨクモの新卒やアルバイト学生のための教育用Ring解説記事.
Active Recalls #
Clojure RingのようなWebアプリを構築するための仕組みを一般的になんといいますか? #
Webサーバ抽象, Web Server Abstruction.
Web Serverと Web アプリがやり取りをするための仕様.
Clojure Ring における4つのコンポーネントはなんですか? #
ハンドラー, ミドルウェア, リクエストマップ, レスポンスマップ.
Heroku with Clojure #
- https://devcenter.heroku.com/articles/deploying-clojure
- https://devcenter.heroku.com/articles/getting-started-with-clojure
- https://devcenter.heroku.com/articles/clojure-support
leiningenの作者がHerokuで働いてたらしくドキュメントがていねいとか.
https://twitter.com/as_chapa/status/1198104444711256064 https://twitter.com/iku000888/status/1099293410693808128
tip: Heroku上のappにreplで接続 #
heroku run lein replでHerokuサーバ上でreplを起動できる.
heroku run lein repl
howto: Heroku Deployでlein deps失敗の対処方法 #
Leiningen 1.7.1 がデフォルトで使用されますが、project.clj に :min-lein-version “2.0.0" がある場合は (強く推奨されます)、Leiningen 2.9.1 リリースが代わりに使用されます。
これにハマった. project.cljに :min-lein-version “2.0.0” を記載して解決✨
ref: https://devcenter.heroku.com/ja/articles/clojure-support
howto: Heroku上で Botを動かそうとするとエラー #
web appではなく worker appにする必要がある.
そうしないと 60secでBootTimeoutとして扱われてProcess Killされる.
2022-02-11T09:30:12.486217+00:00 heroku[web.1]: Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch
2022-02-11T09:30:12.581863+00:00 heroku[web.1]: Stopping process with SIGKILL
2022-02-11T09:30:12.834345+00:00 heroku[web.1]: Process exited with status 137
2022-02-11T09:30:13.225734+00:00 heroku[web.1]: State changed from starting to crashed
Procfileを新規作成して以下を記載.
worker: lein run
heroku cliより
heroku ps:scale worker=1
ただし workerは30分活動がないとsleepしてしまう.
実際はheroku schedulerの活用も検討.
howto: tools.deps管理のプロジェクトをHerokuにデプロイ #
未実施だけどブックマーク. leiningenの作者はライバルにいじわる?
ref: 試行錯誤な日々: clojure cliプロジェクトをherokuで動かす
Firebase/Google Cloud w/ Clojure #
ref: https://github.com/tsu-nera/meigen-bot-firebase-clj
Firebase Firestore(aka. Google Cloud Firestore) w/ Clojure #
せっかくのClojureなのでClojure onlyで頑張らずにJavaやnode.jsの資産を活用する方向がいいかな…Java資産活用しないとリッチーヒッキーの開発モチベに反する🤔
- Add the Firebase Admin SDK to your server | Firebase Documentation
- Get started with Cloud Firestore | Firebase Documentation
ref: 📝Firebase Firestore
client libraries #
firestore-cljがよくできている. これをベースに他のリポジトリを参考にカスタマイズするとよい.
- https://github.com/lurodrigo/firestore-clj
- java sdk
- https://github.com/samedhi/firemore
- javascript sdk with clojurescript
- https://github.com/cloudfuji/taika
- rest api
- https://github.com/alekcz/fire
- rest api
💡Firestoreのスキーマレスのメリットとclojure.specの思想が反する #
FirestoreはスキーマレスDBということを留意する.
すなわちこの特性によりスキーマ設計が不要というメリットを活かすならば動的言語であるClojureとの 相性がよく JSON Schema(Shcema/Malli/clojure.spce)は不要かもしれない.
ref. 📝Clojure spec
💡FirestoreのドキュメントデータベースパラダイムとClojureプロトコルの思想が反する #
またFirestoreは ドキュメントデータベースということも注意.
ドキュメントのモデルを作成してプロトコルを定義する必要はあるのか?
なぜならばドキュメントはコレクションに格納されてその範囲内でのみシーケンス処理される.
プロトコルとは操作抽象であり異なるデータ型を同一IFでシーケンシャルに処理することが目的であるがそもそもドキュメントデータベースのパラダイムにおいては異なるドキュメントを同一コレクションに入れるのかという問題がある.
ref: defprotocol
タイムスタンプの扱い #
調査中だがわかったところまで,
- java.time.x でFirestoreにデータを送るとobjectとして格納される.
- java.instantでFirestoreにデータを送るとobjectとして格納される.
- java.data.utilでFirestoreにデータを送るとTimestampとして格納される.
なのでタイムスタンプ型として時刻を格納するときはjava.util.Dateで投げる.
Firebase Functions(aka. Google Cloud Functions) w/ Clojure #
tags: 🏷GCF
ClojureScriptを利用してJavaScript(Node.js)のライブラリを使うのがよい.
- ClojureScript + Firebase - DEV Community
- My PWA made with Clojure/ClojureScript exceeded 400 users 🎉 - DEV Community
しかしClojureの道に挑戦してしまった…地雷だらけ.
基本方針としては, Google Cluud FunctionsのJava11ランタイムのページをみながら, functions-framework-java を利用する.
- https://cloud.google.com/functions/docs/concepts/java-runtime
- https://github.com/GoogleCloudPlatform/functions-framework-java
2022.01時点での注意点としては,
- 他のJVM言語のところにClojureが登場しない.
- https://cloud.google.com/functions/docs/concepts/jvm-langs
- Scala, Kotlin, Groovyは登場する.
- 他のJVM言語を真似してClojureを実行しようとすると,エラー.
- デプロイ方式は2つあるがおそらくローカルビルドでないと失敗.
- https://cloud.google.com/functions/docs/concepts/java-deploy
- ソースからのデプロイはpom.xmlとソースのuploadは成功するがclojureのビルドでコケる. そもそもCloud Functionsで使われているCloud BulildでClojureのビルドに対応しているのか怪しい. ClojureCoreその他ははdefaultでJava8でビルドされている. 一方Cloud Functionsのランタイムは11.
- したがってuberjar(fatjar)をローカルで作成して.classファイルと一緒にuploadするのがいい.
- 以下の3つのリポジトリが参考になる.
- ハマりポイントはエントリポイントをJavaで用意するところ.
- Clojureで用意しようとしてもビルドエラーする.
- ruberjarのビルドにはの 📝tools.build のguideをみて, JavaソースをコンパイルしてClojureコードをuberjarで.jarファイルに含める.
📝Google Cloud Run w/ Clojure #
🏷Jib を利用してコンテナビルドする.
- Clojure in Google Cloud Run with Jib – Hannu Hartikainen
- Clojure app on Google Cloud Run | 3sky’s notes
- Google Cloud Run で Clojure アプリケーションを実行しよう | Micheam’s TechBlog
実際に検証した感触ではCloud Functionsよりも圧倒的に楽.
refs:
- GitHub - tsu-nera/meigen-bot-gcloud-run-clj
- 🖊Clojure/Firebaseで努力の名言Serverless Twitter Bot作成した | Futurismo
最近, jibbit というclojureからjibを扱いやすしたツールも登場(2022/01).
- https://twitter.com/kipzter/status/1480982692036591620
- Containerizing a Clojure Project | Atomist Blog
tag: 🏷GCR
References #
- 🔗オブジェクト指向とはまったく違うClojureの世界と実際のWeb開発 - 紙箱
- 🔗Clojure で Web 開発をはじめてみよう — Clojure の日本語ガイド
- http://ayato-p.github.io/clojure-beginner/intro_web_development/index.html
- ayato-pさんの作.
- たしかわたしがClojureにはじめて触れた2015ごろにはすでにあった気がする.
- ayato-pさんの作.
- Web Development with Clojure (第3版) の紹介 - Qiita