Proyek Clojure modern dikelola oleh dua berkas inti, yaitu deps.edn yang mendeklarasikan dependensi dan alias, serta kode program yang dijalankan melalui Clojure CLI. Topik ini membahas struktur proyek standar, nREPL untuk pengembangan interaktif dari editor, pengemasan jar dengan tools.build, dan posisi Leiningen sebagai perkakas generasi sebelumnya yang masih digunakan banyak kodebasis lama. Penutup topik berupa demo proyek mini yang benar-benar dijalankan dengan satu perintah, lengkap dengan keluarannya. Setelah topik ini selesai, proyek dapat disusun dari nol, dihubungkan dengan editor melalui nREPL, dan dikemas menjadi jar.
Struktur proyek deps.edn#
Struktur yang lazim menempatkan kode sumber di src, pengujian di test, dan hasil pengemasan di target, dengan deps.edn serta build.clj di akar proyek. Pohon berikut menggambarkan proyek loket yang digunakan sebagai contoh sepanjang topik.
loket/
├── deps.edn
├── build.clj
├── src/
│ └── loket/
│ └── core.clj
├── test/
│ └── loket/
│ └── core_test.clj
└── target/
└── loket-0.1.0.jarBerkas deps.edn proyek mendeklarasikan tiga hal utama, yaitu paths, deps, dan aliases.
{:paths ["src"]
:deps {metosin/malli {:mvn/version "0.20.3"}}
:aliases
{:nrepl
{:extra-deps {nrepl/nrepl {:mvn/version "1.8.0"}}
:main-opts ["-m" "nrepl.cmdline" "--port" "5555"]}
:build
{:deps {io.github.clojure/tools.build {:mvn/version "0.10.9"}}
:ns-default build}}}Kunci :paths menambahkan direktori ke classpath sehingga (ns loket.core) pada src/loket/core.clj dapat di-require. Kunci :deps mendaftarkan pustaka dengan koordinat Maven dan versi yang dipatok. Kunci :aliases mengelompokkan konfigurasi tambahan yang hanya aktif ketika dipanggil, yaitu :nrepl yang menyuntik nREPL beserta main-opts-nya, dan :build yang membawa tools.build dengan :ns-default build yang menunjuk build.clj. Seluruh resolusi bersumber dari deps.edn pada direktori tempat perintah dijalankan.
nREPL untuk pengembangan interaktif#
nREPL adalah protokol REPL melalui jaringan. Server nREPL membuka socket, dan editor yang terhubung mengirim form untuk dievaluasi di proses server, sehingga evaluasi REPL berlangsung di dalam konteks proyek yang sesungguhnya, lengkap dengan classpath dan state yang sudah terbentuk. Dukungan editor atas nREPL inilah yang menjadikan REPL-first pada Clojure praktis, karena evaluasi kode, pemeriksaan dokumen, dan penelusuran error berlangsung dari editor tanpa menyalin-tempel. Karena nREPL bukan bagian pustaka inti, dependensinya disuntikkan melalui alias :nrepl pada deps.edn di atas. Perintah berikut menjalankan server pada port 5555.
# tidak-dijalankan
# server nREPL tidak berhenti sendiri sehingga perintah ini tidak dieksekusi di tulisan
clojure -M -m nrepl.cmdline --port 5555Perintah tersebut setara dengan clojure -M:nrepl yang memanggil alias beserta main-opts-nya. Setelah server berjalan, editor yang mendukung nREPL terhubung ke localhost:5555 dan mengirim evaluasi form demi form. Server berjalan terus sampai dihentikan secara eksplisit, sehingga perintah di atas tidak dieksekusi pada tulisan ini. Untuk menghentikannya, kirim sinyal interupsi dari terminal dengan Ctrl-C.
Mengemas jar dengan tools.build#
tools.build adalah pustaka resmi untuk tugas pengemasan. Berkas build.clj di akar proyek memuat fungsi-fungsi build, dan alias :build pada deps.edn memuat pustakanya dengan :ns-default build, sehingga setiap fungsi publik di build.clj dapat dipanggil sebagai tool. Berkas berikut mendefinisikan target jar yang menyalin kode sumber, menulis pom, lalu membungkus hasilnya menjadi satu arsip.
; tidak-dijalankan
; memerlukan pustaka tools.build yang diunduh saat alias :build pertama kali dipakai
(ns build
(:require [clojure.tools.build.api :as b]))
(def lib 'contoh/loket)
(def version "0.1.0")
(def class-dir "target/classes")
(def basis (b/create-basis {:project "deps.edn"}))
(def jar-file (format "target/%s-%s.jar" (name lib) version))
(defn clean [_]
(b/delete {:path "target"})
nil)
(defn jar [_]
(b/write-pom {:class-dir class-dir :lib lib :version version :basis basis :src-dirs ["src"]})
(b/copy-dir {:src-dirs ["src"] :target-dir class-dir})
(b/jar {:class-dir class-dir :jar-file jar-file}))# tidak-dijalankan
# mengunduh pustaka build dan memakan waktu di atas batas eksekusi tulisan
clojure -T:build jarOpsi -T membangun classpath dari alias :build lalu memanggil fungsi jar sebagai tool, berbeda dari -M yang menjalankan main program. Hasilnya adalah target/loket-0.1.0.jar yang siap dikirim, dan fungsi clean menghapus target sebelum pengemasan ulang. Nilai basis dibaca dari deps.edn proyek, sehingga isi jar mengikuti dependensi yang sama dengan yang digunakan saat pengembangan. Perintah di atas tidak dieksekusi pada tulisan ini karena pengunduhan pustaka build pada pemanggilan pertama memakan waktu di atas batas eksekusi verifikasinya.
Leiningen sebagai konteks warisan#
Sebelum deps.edn diperkenalkan pada 2017, proyek Clojure hampir seluruhnya dikelola oleh Leiningen. Leiningen menyatukan dependensi, pengujian, dan pengemasan pada satu berkas project.clj, dan kodebasis lama yang menggunakannya masih banyak ditemukan. Kemampuan membaca project.clj tetap diperlukan, misalnya ketika proyek lama dinilai ulang atau dimigrasikan. Berikut bentuk project.clj untuk proyek loket yang sama, dipotong agar kontras gayanya terlihat.
; tidak-dijalankan
; defproject adalah macro milik Leiningen dan tidak dievaluasi oleh Clojure biasa
(defproject loket "0.1.0"
:description "Pencatat antrean loket"
:dependencies [[org.clojure/clojure "1.12.0"]
[metosin/malli "0.20.3"]]
:main loket.core)Kolom :dependencies menggunakan vector berisi vector untuk setiap pustaka, sedangkan :main menunjuk namespace yang membawa -main, keduanya berbeda dari bentuk map pada deps.edn. Proyek baru lazim menggunakan deps.edn karena menjadi perkakas resmi Clojure, sedangkan Leiningen tetap dirawat untuk proyek yang sudah ada. Migrasi tidak perlu dilakukan tergesa-gesa tanpa alasan, karena mengenali kedua bentuk deklarasi membuat kodebasis dari generasi perkakas mana pun dapat diteruskan.
Demo proyek mini yang dijalankan#
Untuk memperlihatkan penggunaan perkakas di atas, proyek mini diratakan menjadi dua berkas yang ditempatkan berdampingan, yaitu core.clj dan deps.edn. Berkas deps.edn demo memuat dependensi yang sama dengan proyek loket tanpa :paths dan :aliases, karena demo dijalankan dari satu direktori dan tidak memerlukan alias. Berkas core.clj membawa namespace loket.core, schema antrean dari topik Pengenalan Malli, validator terkompilasi, dan fungsi -main yang mencetak status setiap entri antrean.
(ns loket.core
(:require [malli.core :as m]))
(def SkemaAntrean
[:map
[:tiket string?]
[:nomor int?]
[:layanan [:enum "ktp" "kk" "akta"]]
[:prioritas boolean?]])
(def antrean-valid? (m/validator SkemaAntrean))
(def antrean
[{:tiket "A-01" :nomor 1 :layanan "ktp" :prioritas true}
{:tiket "A-02" :nomor 2 :layanan "kk" :prioritas false}
{:tiket "B-01" :nomor "tiga" :layanan "akta" :prioritas false}])
(defn -main
[& _args]
(doseq [{:keys [tiket] :as entri} antrean]
(println tiket "->" (if (antrean-valid? entri) "valid" "tidak valid"))))
(-main)A-01 -> valid
A-02 -> valid
B-01 -> tidak validEntri ketiga dinyatakan tidak valid karena :nomor berisi string “tiga”, bukti bahwa schema bekerja pada jalur utama program. Program dijalankan dengan perintah berikut dari direktori yang memuat kedua berkas.
{:deps {metosin/malli {:mvn/version "0.20.3"}}}# jalankan program dari direktori yang memuat deps.edn dan core.clj
clojure -M core.cljA-01 -> valid
A-02 -> valid
B-01 -> tidak validPerintah clojure -M core.clj mengeksekusi berkas secara berurutan dalam mode skrip, dan pemanggilan (-main) pada baris terakhir berkas itulah yang mencetak laporan. Mode skrip tidak mencetak nilai setiap form, sehingga pencetakan dilakukan secara eksplisit. Pada proyek dengan struktur src, program yang sama dijalankan dengan clojure -M -m loket.core karena :paths sudah mencakup kode sumbernya dan namespace loket.core membawa -main.
Kesalahan umum#
- Menjalankan
clojure -M -m nrepl.cmdlinetanpa dependensi nrepl di classpath. Resolusinya gagal dengan pesan berkasnrepl/cmdlinetidak ditemukan, karena nREPL memang bukan bagian pustaka inti. Perbaikannya dengan alias:nreplseperti padadeps.edndi atas atau dengan opsi-Sdepsyang menyuntikkan dependensi sekali pakai. - Menulis
build.cljlalu memanggilclojure -T:buildtanpa alias:buildpadadeps.edn. Opsi-Ttidak menemukan namespace build sehingga fungsijartidak terpanggil. Perbaikannya dengan mendaftarkan alias:buildyang memuat tools.build dan:ns-default build. - Mencampur aduk
-Mdan-T. Opsi-Mmenyiapkan classpath lalu menjalankan main program, sedangkan-Tmenjalankan fungsi tool dengan argumen bernilai dari aliasnya. Perintah yang salah opsi gagal dengan cara yang membingungkan, sehingga pilihannya perlu disengaja, yaitu-Muntuk program dan-Tuntuk perkakas.
Ringkasan#
- Proyek berbasis deps.edn menyimpan dependensi dan alias pada satu berkas, dengan kode sumber di
srcdan perintah yang diketik dari akar proyek. - nREPL menjadikan editor sebagai klien REPL proyek melalui alias yang menyuntikkan pustaka dan main-opts pada satu port tetap.
- tools.build mengeksekusi fungsi di
build.cljmelaluiclojure -T:builduntuk menghasilkan jar, dan-Tberbeda peran dari-M. - Leiningen dengan
project.cljmerupakan perkakas warisan yang bentuk deklarasinya berbeda, tetapi masih digunakan banyak kodebasis lama.
Referensi#
- deps.edn Reference — Clojure
— Rujukan resmi bentuk
deps.edn, alias, dan resolusi dependensi Clojure CLI. - Deps and CLI Guide — Clojure
— Panduan resmi Clojure CLI, mulai dari deklarasi dependensi sampai opsi
-M. - Building jars & more (tools.build) — Clojure
— Panduan resmi
build.cljdan pengemasan jar denganclojure -T:build. - nREPL — nrepl.org — Dokumentasi nREPL: server, middleware, dan penghubungan editor ke proyek.
- Leiningen — leiningen.org
— Situs resmi Leiningen beserta dokumentasi
project.clj.