Toyokumo Tech Blog

トヨクモ株式会社の開発本部のブログです。

Clojureで作るAPI 設定をednで管理する

[連載]Clojureで作るAPIの7記事目です。

前回の記事はこちらです。

tech.toyokumo.co.jp

この記事ではアプリケーションの設定をClojureのソースではない設定ファイルで管理できるようにしていきます。

なぜ設定ファイルで管理する必要があるのか

実際のアプリケーションでは、開発環境・ステージング環境・本番環境と複数の環境で動くようにしなければいけません。 DBの接続先や外部のAPIキーなど、動作環境によって切り替えたい値は複数あります。

設定値は固定値だけでなく環境変数から読み込んだものであったりするでしょうが、設定値を各所に書き散らかしてしまうと保守性が下がるのでそれらを一箇所にまとめて管理したいです。

今回はそういったことを実現していきます。

aeroを追加

この目的のためにaeroというライブラリがあります。こちらを使っていきましょう。

github.com

まずライブラリを deps.edn に追加します。

;; ./deps.edn
{:paths ["src" "resources"] ;; resourcesを追加
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        info.sunng/ring-jetty9-adapter {:mvn/version "0.17.6" :exclusions [org.slf4j/slf4j-api]}
        spootnik/unilog {:mvn/version "0.7.30"}
        com.stuartsierra/component {:mvn/version "1.1.0"}
        ;; ライブラリを追加
        aero/aero {:mvn/version "1.1.6"}}
 :aliases {:dev {:extra-paths ["dev"]}
           :build {:deps {io.github.clojure/tools.build {:git/tag "v0.8.2" :git/sha "ba1a2bf"}}
                   :ns-default build}}}

前回の記事でJARファイルにビルドできるようにしました。 多くのケースでJARをビルドした環境とそれを実行する環境とは異なるはずです。 ビルドした環境にはあるが実行環境にないファイルをビルドしたJARアプリケーションから参照したい場合は、そのファイルがJARに含まれるようにする必要があります。

:paths にresourcesを追加すると、resources下のファイルがJARに含まれるようになります。 その上で設定ファイルを ./resources/config.edn というパスに配置することで実行環境でも設定ファイルを参照できるようにします。

設定ファイルの追加

./resouces/config.edn に設定をaeroの作法に従って書いていきます。

;; ./resources/config.edn
{:server {:opts {:host "localhost"
                 :port #long #profile {:default 5000
                                       :dev 8000}
                 :join? false}}}

ひとまず前回までの記事で立ち上げたWebサーバーの設定だけ追加しています。

ほとんど単なるClojureのMapですが、2つだけタグリテラルを使っています。

#longは基本的に戻り値はStringであるところをlong型でパースして取得することを可能にします。他には #double#keyword などがあります。

#profileはprofileがMapのキーに一致する値を返します。profileはconfig.ednをパースする関数を呼び出すときに引数として与えます。この例だと :profile:dev なら8000、それ以外なら5000ということです。

これ以外にもタグリテラルはあります。ここでは解説しませんが、今後新しいものが登場するたびに説明するのでご安心ください。

設定の読み込み

cljapi.config/read-config に設定を読み込む関数を定義します。

;; ./src/cljapi/config.clj
(ns cljapi.config
  (:require
   [aero.core :as aero]
   [clojure.java.io :as io]))

(defn read-config [profile]
  {:pre [(contains? #{:dev :prod :test} profile)]}
  (-> (io/resource "config.edn")
      (aero/read-config {:profile profile})
      (assoc :profile profile)))

:pre は初登場ですが、これは関数の事前条件をチェックするために使います。上記の例だと引数profileが、:dev, :prod, :testのいずれかでなければ AssertionError 例外が投げられます。

io/resource はクラスパスに含まれるパスの一致するファイルから java.net.URLを取得する関数です。先ほどconfig.ednはresources下に配置し、resourcesをdeps.ednで :paths に指定してあるのでURLが取得できるということです。

REPLで確かめてみましょう。

read-config

Systemとの統合

最後にSystemと統合し、開発環境では :dev profileで、本番環境では :prod profileで起動できるようにします。

まず cljapi.system を次のようにします。

;; ./src/cljapi/system.clj
(ns cljapi.system
  (:require
   [cljapi.component.handler :as c.handler]
   [cljapi.component.server :as c.server]
   [cljapi.config :as config]
   [com.stuartsierra.component :as component]))

(defn- new-system [config]
  (component/system-map
   :handler (c.handler/map->Handler {})
   :server (component/using
            (c.server/map->Jetty9Server (:server config))
            [:handler])))

(defn start [profile]
  (let [config (config/read-config profile)
        system (new-system config)]
    (component/start system)))

(defn stop [system]
  (component/stop system))

これまで c.server/map->Jetty9Server に直接Mapを与えていたところをconfig.ednから読み込んだ設定を使うようにしています。

開発用の user も修正します。

;; ./dev/user.clj
(ns user
  (:require
   [cljapi.system :as system]))

(defonce system (atom nil))

(defn start []
  (reset! system (system/start :dev))) ;; startに :dev を与える修正

(defn stop []
  (when @system
    (reset! system (system/stop @system))))

(defn go []
  (stop)
  (start))

ここまで修正ができたら次の2つを確かめてみましょう。

  • REPLを立ち上げて (go) を評価して curl http://localhost:8000 を実行すると Hello, Clojure API と返ってくる
  • make build して java -jar target/cljapi.jar を実行して curl http://localhost:5000 を実行すると Hello, Clojure API と返ってくる

profileによって異なる設定が反映されていることが確認できました。

おわりに

ここまでで、設定ファイルをソースコードの外に配置し、コンテキストによって異なる設定でアプリケーションを起動できるようになりました。

コードはGitHubのリポジトリに上げてあります。 07_設定をednで管理するというタグからご覧ください。

次はロギングの設定を行なっていきます。


トヨクモでは一緒に働いてくれる技術が好きなエンジニアを募集しております。

採用に関する情報を公開しております。 気になった方はこちらからご応募ください。

Clojureで作るAPI ビルドして実行できるようにする

[連載]Clojureで作るAPIの6記事目です。

前回の記事はこちらです。

tech.toyokumo.co.jp

この記事ではビルドしてJARファイルを生成し、 java コマンドでアプリケーションを実行できるようにしていきます。

JARファイルとは

まずJARファイルとは、Javaのライブラリやアプリケーションを配布するためにJavaクラスファイル、メタデータ、リソース(textや画像など)を1つのファイルにまとめた物です。

詳細については英語版のWikipediaをご覧ください。

en.wikipedia.org

JARファイルの他に uberjar という言葉もありますが、これは実行に必要な全てのファイルやデータを含んだJARファイルを指します。 アプリケーションを実行するためには、uberjarであった方が都合がいいため、この先はJARファイルという言葉をubarjarであるという前提のもとで使用します。

tools.buildを使う

この目的のためにtools.buildというClojure公式ライブラリがあります。こちらを使っていきましょう。

github.com

基本的に公式サイトに載っている使い方の通りにやればうまくいきます。

まずライブラリを :build というaliasに追加します。

;; ./deps.edn
{:paths ["src"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        info.sunng/ring-jetty9-adapter {:mvn/version "0.17.6" :exclusions [org.slf4j/slf4j-api]}
        spootnik/unilog {:mvn/version "0.7.30"}
        com.stuartsierra/component {:mvn/version "1.1.0"}}
 :aliases {:dev {:extra-paths ["dev"]}
           :build {:deps {io.github.clojure/tools.build {:git/tag "v0.8.2" :git/sha "ba1a2bf"}}
                   :ns-default build}}}

次に build.clj にビルドスクリプトを用意します。

;; ./build.clj
(ns build
  (:require
   [clojure.tools.build.api :as b]))

(def class-dir "target/classes")
(def basis (b/create-basis {:project "deps.edn"}))
(def uber-file "target/cljapi.jar")

(defn uber [_]
  (b/copy-dir {:src-dirs ["src" "resources"]
               :target-dir class-dir})
  (b/compile-clj {:basis basis
                  :src-dirs ["src"]
                  :class-dir class-dir})
  (b/uber {:class-dir class-dir
           :uber-file uber-file
           :basis basis
           :main 'cljapi.core}))

最後にmain関数となる cljapi.core/-main を実行できるようにするために、このnamespaceをクラスとして生成させます。 そのためには :gen-class をつけます。

;; ./src/cljapi/core.clj
(ns cljapi.core
  (:gen-class)
  (:require
   [cljapi.system :as system]))

(defn -main
  [& _args]
  (system/start))

ここまで記述できたら clojure -T:build uber を実行すれば実行可能なJARファイルを生成できます。

$ clojure -T:build uber
# targetディレクトリにJARファイルが生成されました。
# 中身を見てみます。
$ cd target
$ tree -L 3
.
├── classes
│   ├── cljapi
│   │   ├── component
│   │   ├── core$_main.class
│   │   ├── core$fn__1158.class
│   │   ├── core$loading__6789__auto____140.class
│   │   ├── core.class
│   │   ├── core.clj
│   │   ├── core__init.class
│   │   ├── system$fn__1153.class
│   │   ├── system$loading__6789__auto____142.class
│   │   ├── system$new_system.class
│   │   ├── system$start.class
│   │   ├── system$stop.class
│   │   ├── system.clj
│   │   └── system__init.class
│   ├── com
│   │   └── stuartsierra
│   └── ring
│       ├── adapter
│       ├── core
│       └── util
└── cljapi.jar

実行して確認してみましょう。

$ java -jar target/cljapi.jar
# 別のterminalでcurlしてみる
$  curl http://localhost:8000
Hello, Clojure API

Makefileに追記

オプションを覚えるということはしたくないので、Makefileにも追記しておきます。

# ./Makefile
.PHONY: format
format:
  cljstyle check

.PHONY: lint
lint:
  clj-kondo --lint src

.PHONY: static-check
static-check: format lint

.PHONY: clean
clean:
  rm -fr target/

.PHONY: build
build: clean
  clojure -T:build uber

おわりに

ここまでで、単一のJARファイルを生成して java コマンドでアプリケーションを起動できるようになりました。

コードはGitHubのリポジトリに上げてあります。 06_ビルドして実行できるようにするというタグからご覧ください。

次は設定を外部ファイルで管理できるようにして、開発環境、ステージング環境、本番環境で値を変えられるようにしていきます。


トヨクモでは一緒に働いてくれる技術が好きなエンジニアを募集しております。

採用に関する情報を公開しております。 気になった方はこちらからご応募ください。

Clojureで作るAPI 開発用のコードを分離する

[連載]Clojureで作るAPIの5記事目です。

前回の記事はこちらです。

tech.toyokumo.co.jp

この記事では開発用のコードと実際のコードとを分離し、開発用の関数が評価された状態でREPLが起動されるようにすることで、今後の生産性を上げるようにします。

aliasを追加

今のところdeps.ednで :paths ["src"] と指定しているので、ライブラリ以外でクラスパスに含まれるファイルはsrc以下のものです。 しかしsrc以下に開発のためのコードを含めてしまうと、それらもビルドの対象になってしまうので避けたいです。

そこで、開発時にだけクラスパスに含まれるディレクトリを設定することでこの問題を回避します。 deps.ednに :aliases を追加します。

;; ./.deps.edn
{:paths ["src"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        info.sunng/ring-jetty9-adapter {:mvn/version "0.17.6" :exclusions [org.slf4j/slf4j-api]}
        spootnik/unilog {:mvn/version "0.7.30"}
        com.stuartsierra/component {:mvn/version "1.1.0"}}
 :aliases {:dev {:extra-paths ["dev"]}}}

EmacsとCIDERを利用している人は.dir-locals.elを追加しておく必要があります。

;; ./.dir-locals.el
((clojure-mode . ((cider-clojure-cli-aliases . ":dev")
                  )))

user.clj

ClojureのREPLはデフォルトではuserというnamespaceで起動します。

この挙動を利用して、 ./dev/user.clj に開発用の関数を定義します。ディレクトリがsrcじゃないことに注意してください。

;; ./dev/user.clj
(ns user
  (:require
   [cljapi.system :as system]))

(defonce system (atom nil))

(defn start []
  (reset! system (system/start)))

(defn stop []
  (when @system
    (reset! system (system/stop @system))))

(defn go []
  (stop)
  (start))

system.cljからは不要になったものを削除して次のようにしておきます。

;; ./src/cljapi/system.clj
(ns cljapi.system
  (:require
   [cljapi.component.handler :as c.handler]
   [cljapi.component.server :as c.server]
   [com.stuartsierra.component :as component]))

(defn- new-system []
  (component/system-map
   :handler (c.handler/map->Handler {})
   :server (component/using
            (c.server/map->Jetty9Server {:opts {:join? false
                                                :port 8000}})
            [:handler])))

(defn start []
  (let [system (new-system)]
    (component/start system)))

(defn stop [system]
  (component/stop system))

REPLを起動してうまくいっていることを確かめます。

user> (ns-interns *ns*)
{go #'user/go, system #'user/system, start #'user/start, stop #'user/stop}
user> (go)
16:03:35.893 [nREPL-session-9adbd434-340f-4921-8f28-60926ca9be17] DEBUG org.eclipse.jetty.util.component.AbstractLifeCycle - STOPPING Server@4b5f2286{STARTED}[10.0.9,sto=0]

おわりに

これで開発用のコードはdevディレクトリに置くことができるようになりました。

devディレクトリは :dev aliasを有効にしたときしかソースとして認識されないため、開発用のコードを分離できるようになっています。

コードはGitHubのリポジトリに上げてあります。 05_開発用のコードを分離するというタグからご覧ください。

次は実際にビルドしてJARファイルを生成して、javaコマンドで実行できるようにしていきます。


トヨクモでは一緒に働いてくれる技術が好きなエンジニアを募集しております。

採用に関する情報を公開しております。 気になった方はこちらからご応募ください。

Clojureで作るAPI ライフサイクルと依存関係を管理できるようにする

[連載]Clojureで作るAPIの4記事目です。

前回の記事はこちらです。

tech.toyokumo.co.jp

この記事ではREPLを再起動することなくWebサーバーを再起動できるようにしていきます。

なぜ再起動できる必要があるのか

そもそもなんでそんなことが必要なのかというと、コードを書き換えた後で、REPLに持たせている状態を今のコードに合わせて即座に置き換えたいからです。

前回の記事(jetty/run-jetty ring-handler {:port 8000}) という式を使ってWebサーバーを起動しました。 引数に与えている関数 ring-handler と マップ {:port 8000} は立ち上がったサーバーが保持していますから、サーバーを再起動しないことには置き換えることはできません。しかしそのためにいちいちREPLを再起動していては時間がかかってしまって生産的ではありません。

また、このような状態を持つものが複数あり、かつその起動が順序通りに行われる必要がある場合に注意しながら手動で起動していくなんてことはやりたくありません。

そこでそういったことを可能にするライブラリを使って宣言的に再起動できるようにしていきます。

使用するライブラリを追加

ライブラリComponentを追加します。

github.com

Componentは、

'Component' is a tiny Clojure framework for managing the lifecycle and dependencies of software components which have runtime state.

とあるようにまさに今回の目的のために作られたライブラリです。 frameworkとありますが、フレームワークと聞いたときにイメージする例えばRuby on Railsのようなものではなく、シンプルで学習コストも小さいのでご安心ください。

deps.ednを次のようにします。

;; ./deps.edn
{:paths ["src"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        info.sunng/ring-jetty9-adapter {:mvn/version "0.17.6" :exclusions [org.slf4j/slf4j-api]}
        spootnik/unilog {:mvn/version "0.7.30"}
        ;; 追加
        com.stuartsierra/component {:mvn/version "1.1.0"}}}

Componentを実装

ライブラリComponentにおいてはLifecycleというプロトコルを実装しているレコードなどをコンポーネントと呼びます。

(defprotocol Lifecycle
  :extend-via-metadata true
  (start [component]
    "Begins operation of this component. Synchronous, does not return
  until the component is started. Returns an updated version of this
  component.")
  (stop [component]
    "Ceases operation of this component. Synchronous, does not return
  until the component is stopped. Returns an updated version of this
  component."))

startに起動時の処理を書き、stopに停止時の処理を書きます。

コンポーネントという言葉が繰り返し出てきて紛らわしいですが、本記事では次の2つの意味で使っています。

  • ライブラリのComponent
  • ライブラリのComponentの要求に従って作ったコンポーネント

まずはRing handlerを提供するコンポーネントを実装します。 今は単純な関数なのでわざわざコンポーネントにする意味が感じられないですが、例えばテスト環境と本番環境で挙動を変えたい時などでコンポーネントにしておくと切り替えができて便利です。

;; ./src/cljapi/component/handler.clj
(ns cljapi.component.handler
  (:require
   [com.stuartsierra.component :as component]))

(defn- ring-handler
  [_req]
  {:status 200
   :body "Hello, Clojure API"})

(defrecord Handler [handler]
  component/Lifecycle
  (start [this]
    (assoc this :handler ring-handler))
  (stop [this]
    (assoc this :handler nil)))

次にサーバーを起動停止するコンポーネントを実装してみます。

;; ./src/cljapi/component/server.clj
(ns cljapi.component.server
  (:require
   [com.stuartsierra.component :as component]
   [ring.adapter.jetty9 :as jetty]))

(defrecord Jetty9Server [handler opts server]
  ;; handlerは :handler をキーにもつマップ(= コンポーネント)であることを期待している
  component/Lifecycle
  (start [this]
    (if server
      this
      (assoc this :server (jetty/run-jetty (:handler handler) opts))))
  (stop [this]
    (when server
      (jetty/stop-server server))
    (assoc this :server nil)))

Systemを構築する

ライブラリComponentではシステムというマップを定義して、作成したコンポーネントの依存関係を定義し、まとめて起動停止できるようにします。 依存関係を component/using を使って定義しておくと、ライブラリ側でよしなに起動順序(startを呼ぶ順序)を制御してくれます。

;; ./src/cljapi/system.clj
(ns cljapi.system
  (:require
   [cljapi.component.handler :as c.handler]
   [cljapi.component.server :as c.server]
   [com.stuartsierra.component :as component]))

(defn- new-system []
  (component/system-map
   :handler (c.handler/map->Handler {})
   :server (component/using
            (c.server/map->Jetty9Server {:opts {:join? false
                                                :port 8000}})
            ;; component/usingの第二引数で依存しているコンポーネントを宣言している
            [:handler])))

(defn start []
  (let [system (new-system)]
    (component/start system)))

(defn stop [system]
  (component/stop system))

(defonce system (atom nil))

(defn go []
  (when @system
    (stop @system)
    (reset! system nil))
  (reset! system (start)))

component/start を実行すると起動したコンポーネントが返ってくるので、それをatomで保持しておくことで後から停止できるようにしています。 また、go という関数で繰り返し起動させることができるようにしています。 namespaceを移動して go を実行してみます。

※namespaceを評価しておくのを忘れないようにしてください。

user> (in-ns 'cljapi.system)
#namespace[cljapi.system]
cljapi.system> (go)
12:51:40.387 [nREPL-session-e6ae6d15-5885-4f3b-912b-741243dab0a0] DEBUG org.eclipse.jetty.util.component.ContainerLifeCycle - QueuedThreadPool[qtp773034985]@2e1393e9{STOPPED,8<=0<=50,i=0,r=-1,q=0}[NO_TRY] added {org.eclipse.jetty.util.thread.ThreadPoolBudget@5eabf21,POJO}
12:51:40.390 [nREPL-session-e6ae6d15-5885-4f3b-912b-741243dab0a0] DEBUG org.eclipse.jetty.util.component.ContainerLifeCycle - Server@4f689547{STOPPED}[10.0.9,sto=0] added {QueuedThreadPool[qtp773034985]@2e1393e9{STOPPED,8<=0<=50,i=0,r=-1,q=0}[NO_TRY],AUTO}
# 出力が続く...

{:handler {:handler #function[cljapi.component.handler/ring-handler]},
 :server
 {:handler {:handler #function[cljapi.component.handler/ring-handler]},
  :opts {:join? false, :port 8000},
  :server
  #object[org.eclipse.jetty.server.Server 0x4f689547 "Server@4f689547{STARTED}[10.0.9,sto=0]"]}}

最後に表示されているのがgoの戻り値、つまりstartしたシステムマップです。 :server に起動した( :handler が生成されている)handlerコンポーネントが注入されていることがわかります。

curlで確かめてみます。

$ curl http://localhost:8000
Hello, Clojure API

最後にmain関数も置き換えておきましょう。

;; ./src/cljapi/core.clj
(ns cljapi.core
  (:require
   [cljapi.system :as system]))

(defn -main
  [& _args]
  (system/start))

main関数はサーバー起動時に使う関数で、停止する必要がないので、goではなくstartを使っています。

おわりに

これでREPLを起動したまま、サーバーのように状態を持つものを再起動できるようになりました。

コードはGitHubのリポジトリに上げてあります。 04_ライフサイクルと依存関係を管理できるようにするというタグからご覧ください。

次は開発時に使う関数を分離して、より開発を進めやすくしていきます。


トヨクモでは一緒に働いてくれる技術が好きなエンジニアを募集しております。

採用に関する情報を公開しております。 気になった方はこちらからご応募ください。

Clojureで作るAPI Web サーバーを立ち上げる

[連載]Clojureで作るAPIの3記事目です。

前回の記事はこちらです。

tech.toyokumo.co.jp

この記事ではWebサーバーを立ち上げていきます。

Webサーバーのライブラリを追加

まずは依存関係にWebサーバーのライブラリを追加します。 deps.edn を次のようにしてください。

{:paths ["src"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}
        info.sunng/ring-jetty9-adapter {:mvn/version "0.17.6" :exclusions [org.slf4j/slf4j-api]}
        spootnik/unilog {:mvn/version "0.7.30"}}}

ClojureのWebサーバーはRingに沿って作ります。 Ringがどういうものなのかは次の記事で詳しく解説しているのでご覧ください。

tech.toyokumo.co.jp

ここで info.sunng/ring-jetty9-adapterJettyをRingの仕様に適合させてくれているライブラリです。 slf4j-api を除外したり、Clojureのロギングライブラリである spootnik/unilog を加えたりしているのはJavaの複雑なログライブラリの事情によるものなのですが、そこを解説していると別の記事になってしまうので、ここでは一旦触れないでおきます。

最初のWebサーバー

ライブラリが追加できたら core.clj を次のようにします。

;; ./src/cljapi/core.clj
(ns cljapi.core
  (:require
   [ring.adapter.jetty9 :as jetty]))

(defn ring-handler
  [_req]
  {:status 200
   :body "Hello, Clojure API"})

(defn -main
  [& _args]
  (jetty/run-jetty ring-handler {:port 8000}))

(comment
  (-main) ;; (1)
  )

ここまで書けたらREPLを立ち上げて、ファイル全体を評価した後で、(1)の式を評価します。 そうすると大量のログがREPLに出力されつつ、Webサーバーが立ち上がります。

curlを使って確認してみます。

$ curl http://localhost:8000
Hello, Clojure API

localhostの指定したポートにWebサーバーが起動していて、アクセスすると定義した関数(Ring handler)が呼び出されていることが確認できました。

おわりに

とても簡単にWebサーバーを立ち上げることができました。

コードはGitHubのリポジトリに上げてあります。 03_Webサーバーを立ち上げるというタグからご覧ください。

ClojureでAPIを作ることは、言ってしまえば上記で定義した ring-handler に機能を足していくだけの作業です。 とてもシンプルであるおかげで、一度作り上げれば継続したメンテナンスが楽になるということが少しだけでも伝わるでしょうか?

次は開発の生産性を上げるために、REPLを再起動しなくてもWebサーバーの再起動ができるようにしていきます。


トヨクモでは一緒に働いてくれる技術が好きなエンジニアを募集しております。

採用に関する情報を公開しております。 気になった方はこちらからご応募ください。

Clojure で作るAPI 開発環境を構築する

[連載]Clojureで作るAPIの2記事目です。

まずは開発環境を作るところからやっていきましょう。

要件

今回は次の要素を必須の要件として構築を進めます。

  1. REPLが動いて、エディタ上から式を評価して結果を見ることができる
  2. ファイル保存時に自動でフォーマットされる
  3. コードを書いているときに自動でリンターが走る

特にフォーマッターとリンターは最初から自動的に動作するようにしておくと後で楽です。

インストール

この4つをインストールしてください。 JDKについてはまだインストールされていなければsdkmanを使うのが便利でおすすめです。

# macなら次のようにインストールできます
$ brew install clojure/tools/clojure
$ brew install --cask cljstyle
$ brew install borkdude/brew/clj-kondo

# 確認
$ clojure -version
Clojure CLI version 1.11.1.1113
$ cljstyle version
mvxcvi/cljstyle 0.15.0
$ clj-kondo --version
clj-kondo v2022.05.31

REPLを動かす

なにはともあれREPLを動かしましょう。

REPLはClojure開発において(だけではなくLisp全般でも)とても大切な開発環境です。 エディタ上で今書いているコードはもちろん、他の名前空間の関数もその場で評価(その場で式を実行すること)できるので、実際に動かしながら開発を進めることができます。 一度慣れてしまうとREPLのない状態が逆に違和感を感じてしまうようになる程です。

エディタ自体の設定に関しては次の記事からお好みのものを選んで設定を済ませるようにしてください。

tech.toyokumo.co.jp

まずは deps.edn というファイルを作ります。 これはJavaなら build.gradlepom.xml、 JavaScriptなら package.json、PHPなら composer.jsonに当たるもので、依存するライブラリやビルド等のコマンドを定義するファイルです。 詳しくは公式のガイドをご覧ください。

;; ./deps.edn
{:paths ["src"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}}}

この状態でプロジェクトルート(以後 ./)で clj でREPLを立ち上げてみると指定したバージョンのClojureでREPLが立ち上がるのが確認できます。

$ clj
Clojure 1.11.1
user=>

確認できたらこのREPLは閉じておきます。

次に適当なソースコードを用意してエディタとREPLを接続します。

;; ./src/cljapi/core.clj
(ns cljapi.core)


(defn -main
  [& _args]
  (println "Hello, Clojure API!"))


(-main)

ここまで書けたらREPLに繋いで評価してみます。

./src/cljapi/core.clj

repl

※繰り返しになりますが、評価とは式(上記の例では (-main) )をREPLに送信して実行することを指しています。やり方はエディタによって異なりますので、事前に設定方法を参照いただき、設定を済ませておくようにお願いします。

First commit

ここまでで git init してコミットしておきましょう。

# .gitignoreの例
.lsp
.cpcache
.nrepl-port
target
classes

フォーマッターの設定

cljstyleの設定をしていきます。

cljstyleの設定についてはこちらの記事で詳細に解説しています。

tech.toyokumo.co.jp

;; ./.cljstyle
{:rules {:indentation {:list-indent 1
                       :indents {defrecord [[:inner 0] [:inner 1]]}}
         :whitespace {:remove-surrounding? true
                      :remove-trailing? true
                      :insert-missing? true}
         :blank-lines {:trim-consecutive? true
                       :insert-padding? true
                       :padding-lines 1}
         :eof-newline {:enabled? true}
         :comments {:enabled? false}
         :vars {:enabled? false}
         :functions {:enabled? false}
         :types {:enabled? false}
         :namespaces {:indent-size 1}}}

cljstyle fix やエディタとの連携がうまく行っていれば保存時にフォーマットが走ることが確認できると思います。

cljstyle demo

リンターの設定

clj-kondoの設定をしていきます。

;; ./.clj-kondo/config.edn
{:skip-comments true}

ひとまずこれだけでOKです。

エラーになるような式を書くと警告が出ることが確認できます。

clj-kondo demo

Makefileにまとめる

エディタの外からも繰り返し実行できるようにMakefileにコマンドを書いておきます。

# ./Makefile
.PHONY: format
format:
  cljstyle check

.PHONY: lint
lint:
  clj-kondo --lint src

.PHONY: static-check
static-check: format lint

おわりに

ここまでで開発環境が構築できました。コミットしておきましょう。

コードはGitHubのリポジトリに上げてあります。 02_開発環境を構築するというタグからご覧ください。

次回はさっそくWebサーバーを動かしてみましょう。


トヨクモでは一緒に働いてくれる技術が好きなエンジニアを募集しております。

採用に関する情報を公開しております。 気になった方はこちらからご応募ください。

Clojureで作るAPI はじめに

これから、「Clojureで作るAPI」と題して、Clojureを使ってAPIを作る方法をステップバイステップで解説していきたいと思います。

内容としては開発環境の構築から始め、必要な事柄を理解しながら徐々に機能を足していき、最終的には本番環境で使えるようなAPIを作れるところまで到達したいと思っています。 作っていくものも、本番で使うことができないようなものではなく、実際に弊社が本番環境で行なっていることに近い構成を取りながら、認証・認可といった機能も持ち、今後何をしていけば自分が必要な機能を足していくことができるのかわかる水準を目指します。

本筋と関係のない必要な知識も適宜リンクを提示して補足していくので、Clojureの経験がない人、Webアプリケーションサーバーを作ったことがない人であっても、記事の内容を実践していくことでClojureでAPIを作れるようになることを目指していきます。

目次は次のまとめ記事を参照してください。

tech.toyokumo.co.jp

来週の金曜日からなるべく毎週1記事の公開を目指して進めていきたいと思います。

それでは楽しんで始めていきましょう。

続きはこちら。

tech.toyokumo.co.jp


トヨクモでは一緒に働いてくれる技術が好きなエンジニアを募集しております。

採用に関する情報を公開しております。 気になった方はこちらからご応募ください。