Liking cljdoc? Tell your friends :D

Objective Comparison: Clojure Lifecycle & Migration Libraries

The Landscape

These libraries solve different problems that are often conflated:

ProblemLibraries
Runtime lifecycle (start/stop every boot)Component, Integrant, Mount
Database migrationsMigratus, Ragtime, Joplin
One-time setup + component versioningPatcho

Runtime Lifecycle Libraries

Component (Stuart Sierra, 2013)

Approach: Protocol-based. Components implement Lifecycle protocol with start/stop.

(defrecord Database [host port connection]
  component/Lifecycle
  (start [this] (assoc this :connection (connect host port)))
  (stop [this] (disconnect connection) (assoc this :connection nil)))
ProsCons
Explicit dependenciesAll-or-nothing adoption
Testable (swap components)Boilerplate-heavy
Multiple instances possibleChanges code structure significantly
Industry standardSteep learning curve

Integrant (James Reeves)

Approach: Data-driven. Config is EDN, components are multimethods.

;; config.edn
{:db/postgres {:host "localhost" :port 5432}
 :app/handler {:db #ig/ref :db/postgres}}

;; code
(defmethod ig/init-key :db/postgres [_ {:keys [host port]}]
  (connect host port))
ProsCons
Config as data (EDN)Less explicit than Component
Easier REPL workflowMagic via multimethods
Suspend/resume supportStill requires buy-in
Foundation for Duct framework

Mount (tolitius)

Approach: Macro-based. defstate with :start/:stop expressions.

(defstate db
  :start (connect (env :db-url))
  :stop (disconnect db))
ProsCons
Minimal boilerplateGlobal state (vars)
Easy to add to existing codeCan't have multiple instances
No protocol ceremonyImplicit dependencies
Familiar (just vars)Couples namespaces to resources

Comparison Matrix: Runtime Lifecycle

FeatureComponentIntegrantMount
Adoption effortHighMediumLow
Config styleCodeData (EDN)Code
Multiple instancesYesYesNo
Dependency trackingExplicitExplicitImplicit (ns order)
REPL workflowManualGoodGood
TestabilityExcellentGoodHarder
BoilerplateHighMediumLow

Database Migration Libraries

Migratus

Approach: Timestamp-based SQL files with --;; separator.

resources/migrations/
├── 20240101120000-create-users.up.sql
├── 20240101120000-create-users.down.sql
ProsCons
Handles branch conflicts wellSQL-only
Lein plugin with generatorsDatabase-specific
Used by LuminusOnly migrations, no setup/teardown
Tracks all applied migrations

Ragtime

Approach: Database-independent migration interface.

ProsCons
Database agnostic interfaceSequential numbering by default
Multiple strategies availableLess tooling than Migratus
Foundation for Joplin

Joplin

Approach: Built on Ragtime, supports multiple datastores.

ProsCons
SQL, Datomic, ES, Cassandra, etc.More complex
Seeding supportLess active development
Multi-datastore

Where Patcho Fits

Patcho is orthogonal to all of the above.

AspectComponent/Integrant/MountMigratus/RagtimePatcho
WhenEvery bootSchema changesOnce per version
WhatStart/stop servicesDatabase DDLAny one-time operation
ScopeRuntime connectionsDatabase onlyAny component
VersioningNoYes (DB only)Yes (any component)
Setup/CleanupNoNoYes
BidirectionalStop/StartUp/DownUpgrade/Downgrade + Setup/Cleanup

Patcho's Unique Value

1. Component-level versioning (not just database)

;; Version your cache, your search index, your API...
(patch/current-version :myapp/elasticsearch "2.1.0")
(patch/current-version :myapp/redis-cache "1.5.0")
(patch/current-version :myapp/database "3.0.0")

Migratus/Ragtime only version the database.

2. Setup/Cleanup lifecycle (not just migrations)

(lifecycle/register-module! :myapp/database
  {:setup   (fn [] (create-database-from-env!))   ;; Once ever
   :cleanup (fn [] (drop-database!))              ;; Reverse of setup
   :start   (fn [] (connect!) (patch/level! :myapp/database))
   :stop    (fn [] (disconnect!))})

Component/Integrant/Mount don't distinguish "create resource" from "connect to resource".

3. Works WITH other libraries

;; Patcho + Integrant
(defmethod ig/init-key :myapp/database [_ config]
  (let [conn (connect config)]
    (patch/level! :myapp/database)  ;; Apply pending patches
    conn))

;; Patcho + Mount
(defstate db
  :start (do (connect!) (patch/level! :myapp/database) db-conn)
  :stop (disconnect!))

4. CLI queryable (unique to Patcho)

clj -X:patcho versions :require myapp.core
# => {:myapp/database "3.0.0", :myapp/cache "1.5.0"}

No other library exposes component versions to CLI/CI.


Summary: When to Use What

Use CaseLibrary
Managing service start/stop with explicit depsComponent or Integrant
Simple state management, existing codebaseMount
Database schema migrations (SQL)Migratus
Multi-datastore migrationsJoplin
One-time setup that persists across restartsPatcho
Component versioning beyond databasePatcho
Bidirectional upgrades/downgrades for any componentPatcho
CI/CD version visibilityPatcho

The Complete Stack

For a production Clojure app, you might use:

┌─────────────────────────────────────────────┐
│            Your Application                 │
├─────────────────────────────────────────────┤
│  Integrant/Component/Mount                  │
│  (start/stop every boot)                    │
├─────────────────────────────────────────────┤
│  Patcho                                     │
│  (setup once, patch per version)            │
├─────────────────────────────────────────────┤
│  Infrastructure                             │
│  (Database, Cache, Search, etc.)            │
└─────────────────────────────────────────────┘

Patcho doesn't replace Component/Integrant/Mount—it fills the gap they leave.


Sources

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close