├── example ├── villains.edn └── heroes.edn ├── src └── partsbin │ ├── immutant │ ├── web │ │ ├── websocket │ │ │ ├── alpha.clj │ │ │ └── core.clj │ │ ├── core.clj │ │ ├── v2.clj │ │ └── alpha.clj │ └── scheduling │ │ ├── alpha.clj │ │ └── core.clj │ ├── middleware.clj │ ├── datomic │ └── api │ │ ├── core.clj │ │ └── alpha.clj │ ├── datascript │ └── core │ │ ├── alpha.clj │ │ └── core.clj │ ├── etaoin │ └── api │ │ ├── core.clj │ │ └── alpha.clj │ ├── aleph │ └── http │ │ ├── core.clj │ │ └── alpha.clj │ ├── crux │ └── api │ │ ├── alpha.clj │ │ └── core.clj │ ├── clojure │ └── java │ │ └── jdbc │ │ ├── core.clj │ │ ├── v0.clj │ │ └── alpha.clj │ ├── examples │ ├── immutant.clj │ ├── simple_web_app.clj │ └── example.clj │ ├── jdbc │ └── pool │ │ └── c3p0 │ │ ├── alpha.clj │ │ └── core.clj │ ├── hikari_cp │ └── core │ │ ├── alpha.clj │ │ └── core.clj │ ├── ring │ └── adapter │ │ └── jetty │ │ ├── core.clj │ │ └── alpha.clj │ ├── durable_queue │ ├── core.clj │ └── alpha.clj │ ├── monger │ └── core │ │ ├── core.clj │ │ └── alpha.clj │ ├── hawk │ └── core │ │ ├── core.clj │ │ └── alpha.clj │ ├── next │ └── jdbc │ │ ├── alpha.clj │ │ └── core.clj │ ├── datahike │ └── api │ │ ├── core.clj │ │ └── alpha.clj │ ├── system.clj │ └── core.clj ├── doc └── intro.md ├── .gitignore ├── test └── partsbin │ └── core_test.clj ├── CHANGELOG.md ├── project.clj ├── LICENSE └── README.md /example/villains.edn: -------------------------------------------------------------------------------- 1 | [{:name "Magneto" :powers [:magnetism] :universe "Marvel"}] -------------------------------------------------------------------------------- /src/partsbin/immutant/web/websocket/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.immutant.web.websocket.alpha) 2 | -------------------------------------------------------------------------------- /src/partsbin/immutant/web/websocket/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.immutant.web.websocket.core) 2 | -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to partsbin 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /src/partsbin/middleware.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.middleware) 2 | 3 | (defn wrap-component [handler component] 4 | (fn [request] (handler (into component request)))) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | *.iml 13 | .idea/ 14 | -------------------------------------------------------------------------------- /src/partsbin/datomic/api/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.datomic.api.core 2 | (:require [partsbin.datomic.api.alpha :as datomic])) 3 | 4 | (derive ::database ::datomic/database) 5 | (derive ::connection ::datomic/connection) 6 | -------------------------------------------------------------------------------- /test/partsbin/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.core-test 2 | (:require [clojure.test :refer :all] 3 | [partsbin.example :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "I pass." 7 | (is (= 1 1)))) 8 | -------------------------------------------------------------------------------- /example/heroes.edn: -------------------------------------------------------------------------------- 1 | [{:name "Batman" :powers [:money :belt] :universe "DC"} 2 | {:name "Superman" :powers [:flight :speed :strength] :universe "DC"} 3 | {:name "Wonderwoman" :powers [:flight :speed :strength] :universe "DC"} 4 | {:name "Wolverine" :powers [:regeneration :heightened-senses :claws] :universe "Marvel"}] -------------------------------------------------------------------------------- /src/partsbin/datascript/core/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.datascript.core.alpha 2 | (:require [datascript.core :as d] 3 | [integrant.core :as ig] 4 | [taoensso.timbre :as timbre])) 5 | 6 | (defmethod ig/init-key ::connection [_ m] 7 | (timbre/debug "Creating Datascript DB connection.") 8 | (d/create-conn m)) -------------------------------------------------------------------------------- /src/partsbin/etaoin/api/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.etaoin.api.core 2 | (:require [partsbin.etaoin.api.alpha :as webdriver] 3 | [integrant.core :as ig] 4 | [datascript.core :as d])) 5 | 6 | (derive ::firefox ::webdriver/firefox) 7 | (derive ::chrome ::webdriver/chrome) 8 | 9 | (def config {::firefox {:headless true}}) 10 | 11 | (comment 12 | (def sys (ig/init config)) 13 | (ig/halt! sys)) -------------------------------------------------------------------------------- /src/partsbin/immutant/web/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.immutant.web.core 2 | (:require [partsbin.immutant.web.v2 :as web] 3 | [integrant.core :as ig])) 4 | 5 | (derive ::server ::web/server) 6 | 7 | (def config {::server {:host "0.0.0.0" 8 | :port 3000 9 | :handler (constantly {:status 200 :body "OK"})}}) 10 | 11 | (comment 12 | (def system (ig/init config)) 13 | (ig/halt! system)) -------------------------------------------------------------------------------- /src/partsbin/aleph/http/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.aleph.http.core 2 | (:require [partsbin.aleph.http.alpha :as web] 3 | [integrant.core :as ig])) 4 | 5 | (derive ::server ::web/server) 6 | 7 | (def config {::server {:host "0.0.0.0" 8 | :port 3000 9 | :handler (constantly {:status 200 :body "OK"})}}) 10 | 11 | (comment 12 | (defonce system (ig/init config)) 13 | (ig/halt! system)) -------------------------------------------------------------------------------- /src/partsbin/crux/api/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.crux.api.alpha 2 | (:require [taoensso.timbre :as timbre] 3 | [crux.api :as crux] 4 | [integrant.core :as ig]) 5 | (:import (crux.api ICruxAPI))) 6 | 7 | (defmethod ig/init-key ::node [_ config] 8 | (timbre/debug "Starting crux node.") 9 | ^ICruxAPI (crux/start-node config)) 10 | 11 | (defmethod ig/halt-key! ::node [_ ^ICruxAPI node] 12 | (timbre/debug "Stopping crux node") 13 | (.close node)) -------------------------------------------------------------------------------- /src/partsbin/clojure/java/jdbc/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.clojure.java.jdbc.core 2 | (:require [partsbin.clojure.java.jdbc.v0 :as jdbc] 3 | [clojure.java.jdbc :as j] 4 | [integrant.core :as ig])) 5 | 6 | (derive ::connection ::jdbc/connection) 7 | 8 | (def config 9 | {::connection {:connection-uri "jdbc:h2:mem:mem_only"}}) 10 | 11 | (comment 12 | (def system (ig/init config)) 13 | (let [{conn ::connection} system] 14 | (j/query conn "SELECT 1")) 15 | (ig/halt! system)) -------------------------------------------------------------------------------- /src/partsbin/clojure/java/jdbc/v0.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.clojure.java.jdbc.v0 2 | (:require [integrant.core :as ig] 3 | [clojure.java.jdbc :as j] 4 | [taoensso.timbre :as timbre]) 5 | (:import (java.sql Connection))) 6 | 7 | (defmethod ig/init-key ::connection [_ spec] 8 | (timbre/debug "Establishing jdbc connection.") 9 | {:connection (j/get-connection spec)}) 10 | 11 | (defmethod ig/halt-key! ::connection [_ {:keys [^Connection connection]}] 12 | (timbre/debug "Closing jdbc connection.") 13 | (.close connection)) 14 | -------------------------------------------------------------------------------- /src/partsbin/examples/immutant.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.examples.immutant 2 | (:require [partsbin.system :refer [create-system]] 3 | [partsbin.immutant.web.core :as web] 4 | [clojure.pprint :as pp])) 5 | 6 | (defn handler [request] 7 | {:status 200 8 | :body (with-out-str (pp/pprint request))}) 9 | 10 | (defn config [] 11 | {::web/server {:host "0.0.0.0" 12 | :port 3000 13 | :handler #'handler}}) 14 | 15 | (macroexpand 16 | '(create-system (config))) 17 | 18 | (create-system (config)) -------------------------------------------------------------------------------- /src/partsbin/examples/simple_web_app.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.examples.simple-web-app 2 | (:require [partsbin.core :as partsbin] 3 | [partsbin.immutant.web.core :as web] 4 | [clojure.pprint :as pp])) 5 | 6 | (defn app [request] 7 | {:status 200 8 | :body (with-out-str (pp/pprint request))}) 9 | 10 | (def config 11 | {::web/server {:custom-key "This is a custom key" 12 | :host "0.0.0.0" 13 | :port 3000 14 | :handler #'app}}) 15 | 16 | (defonce sys (partsbin/create config)) -------------------------------------------------------------------------------- /src/partsbin/jdbc/pool/c3p0/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.jdbc.pool.c3p0.alpha 2 | "Note - alpha namespace is subject to change." 3 | (:require [integrant.core :as ig] 4 | [jdbc.pool.c3p0 :as pool] 5 | [taoensso.timbre :as timbre]) 6 | (:import (java.io Closeable))) 7 | 8 | (defmethod ig/init-key ::datasource [_ opts] 9 | (timbre/debug "Launching c3p0 connection pool.") 10 | (pool/make-datasource-spec opts)) 11 | 12 | (defmethod ig/halt-key! ::datasource [_ ^Closeable datasource] 13 | (timbre/debug "Closing c3p0 datasource.") 14 | (.close datasource)) -------------------------------------------------------------------------------- /src/partsbin/aleph/http/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.aleph.http.alpha 2 | (:require [aleph.http :as http] 3 | [integrant.core :as ig] 4 | [partsbin.middleware :refer [wrap-component]] 5 | [taoensso.timbre :as timbre]) 6 | (:import (java.lang AutoCloseable))) 7 | 8 | (defmethod ig/init-key ::server [_ {:keys [handler] :as m}] 9 | (timbre/debug "Launching Aleph web server.") 10 | (http/start-server (wrap-component handler m) m)) 11 | 12 | (defmethod ig/halt-key! ::server [_ ^AutoCloseable server] 13 | (timbre/debug "Stopping Aleph web server.") 14 | (.close server)) 15 | -------------------------------------------------------------------------------- /src/partsbin/clojure/java/jdbc/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.clojure.java.jdbc.alpha 2 | "Note - alpha namespace is subject to change." 3 | (:require [integrant.core :as ig] 4 | [clojure.java.jdbc :as j] 5 | [taoensso.timbre :as timbre]) 6 | (:import (java.sql Connection))) 7 | 8 | (defmethod ig/init-key ::connection [_ spec] 9 | (timbre/debug "Establishing jdbc connection.") 10 | {:connection (j/get-connection spec)}) 11 | 12 | (defmethod ig/halt-key! ::connection [_ {:keys [^Connection connection]}] 13 | (timbre/debug "Closing jdbc connection.") 14 | (.close connection)) 15 | -------------------------------------------------------------------------------- /src/partsbin/hikari_cp/core/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.hikari-cp.core.alpha 2 | "Note - alpha namespace is subject to change." 3 | (:require [integrant.core :as ig] 4 | [hikari-cp.core :as hcp] 5 | [taoensso.timbre :as timbre])) 6 | 7 | ;TODO - Should this be keyed as :datasouce for clojure.java.jdbc? 8 | (defmethod ig/init-key ::datasource [_ opts] 9 | (timbre/debug "Creating datasource.") 10 | (hcp/make-datasource opts)) 11 | 12 | (defmethod ig/halt-key! ::datasource [_ datasource] 13 | (timbre/debug "Closing datasource.") 14 | (hcp/close-datasource datasource)) 15 | 16 | -------------------------------------------------------------------------------- /src/partsbin/ring/adapter/jetty/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.ring.adapter.jetty.core 2 | (:require [partsbin.ring.adapter.jetty.alpha :as web] 3 | [integrant.core :as ig])) 4 | 5 | (derive ::server ::web/server) 6 | 7 | 8 | (def config {::server {:host "0.0.0.0" 9 | :port 3000 10 | ;https://stackoverflow.com/questions/2706044/how-do-i-stop-jetty-server-in-clojure 11 | :join? false 12 | :handler (constantly {:status 200 :body "OK"})}}) 13 | 14 | (comment 15 | (def system (ig/init config)) 16 | (ig/halt! system)) 17 | -------------------------------------------------------------------------------- /src/partsbin/ring/adapter/jetty/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.ring.adapter.jetty.alpha 2 | (:require [ring.adapter.jetty :as jetty] 3 | [integrant.core :as ig] 4 | [partsbin.middleware :refer [wrap-component]] 5 | [taoensso.timbre :as timbre]) 6 | (:import [org.eclipse.jetty.server Server])) 7 | 8 | (defmethod ig/init-key ::server [_ {:keys [handler] :as m}] 9 | (timbre/debug "Launching Jetty web server.") 10 | (jetty/run-jetty (wrap-component handler m) m)) 11 | 12 | (defmethod ig/halt-key! ::server [_ ^Server server] 13 | (timbre/debug "Stopping Jetty web server.") 14 | (.stop server)) -------------------------------------------------------------------------------- /src/partsbin/durable_queue/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.durable-queue.core 2 | (:require [partsbin.durable-queue.alpha :as durable] 3 | [durable-queue :refer :all] 4 | [integrant.core :as ig])) 5 | 6 | (derive ::queues ::durable/queues) 7 | 8 | (def config 9 | {::queues {:delete-on-halt? true 10 | :directory "/tmp"}}) 11 | 12 | (comment 13 | (def sys (ig/init config)) 14 | (def q (::queues sys)) 15 | (take! q :my-queue 10 :timed-out!) 16 | (put! q :my-queue "a task") 17 | (def task (take! q :my-queue)) 18 | @task 19 | (complete! task) 20 | (stats q) 21 | (ig/halt! sys)) 22 | -------------------------------------------------------------------------------- /src/partsbin/durable_queue/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.durable-queue.alpha 2 | (:require [durable-queue :refer :all] 3 | [integrant.core :as ig] 4 | [taoensso.timbre :as timbre])) 5 | 6 | (defmethod ig/init-key ::queues [_ {:keys [delete-on-halt? directory config]}] 7 | (timbre/debug (format "Creating queues in directory %s." directory)) 8 | (cond-> (queues directory config) 9 | delete-on-halt? 10 | (with-meta {:delete-on-halt? true}))) 11 | 12 | (defmethod ig/halt-key! ::queues [_ queue] 13 | (when (:delete-on-halt? (meta queue)) 14 | (do 15 | (timbre/debug "Deleting queues.") 16 | (delete! queue)))) -------------------------------------------------------------------------------- /src/partsbin/monger/core/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.monger.core.core 2 | (:require [partsbin.monger.core.alpha :as monger] 3 | [integrant.core :as ig])) 4 | 5 | (derive ::connection ::monger/connection) 6 | (derive ::uri-connection ::monger/uri-connection) 7 | (derive ::database ::monger/database) 8 | 9 | (def config 10 | {::connection {:host "localhost"} 11 | [::config ::database] {:conn (ig/ref ::connection) 12 | :db-name "config"} 13 | [::admin ::database] {:conn (ig/ref ::connection) 14 | :db-name "admin"}}) 15 | 16 | (comment 17 | (def sys (ig/init config)) 18 | (ig/halt! sys)) -------------------------------------------------------------------------------- /src/partsbin/etaoin/api/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.etaoin.api.alpha 2 | (:require [integrant.core :as ig] 3 | [etaoin.api :as etaoin] 4 | [taoensso.timbre :as timbre])) 5 | 6 | (derive ::firefox ::webdriver) 7 | (derive ::chrome ::webdriver) 8 | 9 | (defmethod ig/init-key ::firefox [_ config] 10 | (timbre/debug "Launching Firefox web driver.") 11 | (etaoin/firefox config)) 12 | 13 | (defmethod ig/init-key ::chrome [_ config] 14 | (timbre/debug "Launching Chrome web driver.") 15 | (etaoin/chrome config)) 16 | 17 | (defmethod ig/halt-key! ::webdriver [_ config] 18 | (timbre/debug "Quitting web driver.") 19 | (etaoin/quit config)) 20 | -------------------------------------------------------------------------------- /src/partsbin/jdbc/pool/c3p0/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.jdbc.pool.c3p0.core 2 | (:require [partsbin.jdbc.pool.c3p0.alpha :as c3p0] 3 | [integrant.core :as ig])) 4 | 5 | (derive ::datasource ::c3p0/datasource) 6 | 7 | (def config {::datasource {:connection-uri "jdbc:h2:mem:mem_only"}}) 8 | 9 | (comment 10 | (require '[clojure.java.jdbc :as j]) 11 | 12 | (defonce sys (ig/init config)) 13 | 14 | (let [{ds ::datasource} sys] 15 | (j/query ds ["SELECT 1"])) 16 | 17 | (ig/halt! sys) 18 | 19 | (let [{:keys [::datasource] :as temp-sys} (ig/init config)] 20 | (println (j/query datasource "SELECT 1")) 21 | (ig/halt! temp-sys)) 22 | ) 23 | -------------------------------------------------------------------------------- /src/partsbin/hawk/core/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.hawk.core.core 2 | (:require [integrant.core :as ig] 3 | [partsbin.hawk.core.alpha :as hawk])) 4 | 5 | (derive ::watch ::hawk/watch) 6 | 7 | (def config 8 | {::watch {:example-key :example-value 9 | :opts {:a 1 :b 2} 10 | :groups [{:paths ["src"] 11 | :context (constantly {:context 42}) 12 | :handler (fn example-handler [ctx e] 13 | (println "event: " e) 14 | (println "context: " ctx) 15 | ctx)}]}}) 16 | 17 | (comment 18 | (def sys (ig/init config)) 19 | (ig/halt! sys)) -------------------------------------------------------------------------------- /src/partsbin/next/jdbc/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.next.jdbc.alpha 2 | (:require [integrant.core :as ig] 3 | [next.jdbc :as jdbc] 4 | [taoensso.timbre :as timbre]) 5 | (:import (java.sql Connection))) 6 | 7 | (defmethod ig/init-key ::connection [_ opts] 8 | (timbre/debug "Getting jdbc connection.") 9 | (jdbc/get-connection opts)) 10 | 11 | (defmethod ig/halt-key! ::connection [_ ^Connection connection] 12 | (timbre/debug "Closing jdbc connection.") 13 | (.close connection)) 14 | 15 | (defmethod ig/init-key ::datasource [_ opts] 16 | (timbre/debug "Getting jdbc datasource.") 17 | (jdbc/get-datasource opts)) 18 | 19 | ;Cleanup required? 20 | ;(defmethod ig/halt-key! ::datasource [_ datasource] 21 | ; (jdbc/ datasource)) -------------------------------------------------------------------------------- /src/partsbin/next/jdbc/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.next.jdbc.core 2 | (:require [partsbin.next.jdbc.alpha :as jdbc] 3 | [next.jdbc :as j] 4 | [next.jdbc.sql :as sql] 5 | [integrant.core :as ig])) 6 | 7 | (derive ::connection ::jdbc/connection) 8 | (derive ::datasource ::jdbc/datasource) 9 | 10 | (def config 11 | {::connection {:dbtype "h2:mem" :dbname "mem_only"} 12 | ::datasource {:dbtype "h2:mem" :dbname "mem_only_ds"}}) 13 | 14 | (comment 15 | (def system (ig/init config)) 16 | 17 | (let [{conn ::connection} system] 18 | (j/execute! conn ["SELECT 1"])) 19 | 20 | (let [{conn ::connection} system] 21 | (sql/query conn ["SELECT 1"])) 22 | 23 | (let [{conn ::datasource} system] 24 | (sql/query conn ["SELECT 1"])) 25 | 26 | (ig/halt! system)) -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2018-06-16 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2018-06-16 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [Unreleased]: https://github.com/your-name/partsbin/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/your-name/partsbin/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /src/partsbin/datomic/api/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.datomic.api.alpha 2 | (:require [integrant.core :as ig] 3 | [datomic.api :as d] 4 | [taoensso.timbre :as timbre])) 5 | 6 | (defmethod ig/init-key ::database [_ {:keys [db-uri] :as m}] 7 | (timbre/debug "Creating Datomic database.") 8 | (let [created (d/create-database db-uri)] 9 | (if created 10 | (timbre/debug "Created Datomic database.") 11 | (timbre/debug "Did not create Datomic database (Probably already exists).")) 12 | (assoc m :created? created))) 13 | 14 | (defmethod ig/halt-key! ::database [_ {:keys [db-uri delete?]}] 15 | (when delete? 16 | (do 17 | (timbre/debug "Deleting Datomic database.") 18 | (d/delete-database db-uri)))) 19 | 20 | (defmethod ig/init-key ::connection [_ {:keys [db-uri]}] 21 | (timbre/debug "Connecting to Datomic database.") 22 | (d/connect db-uri)) 23 | -------------------------------------------------------------------------------- /src/partsbin/immutant/web/v2.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.immutant.web.v2 2 | (:require [immutant.web :as immutant] 3 | [integrant.core :as ig] 4 | [partsbin.middleware :refer [wrap-component]] 5 | [taoensso.timbre :as timbre])) 6 | 7 | (def valid-options 8 | #{:path :dispatch? :trust-managers :key-managers :keystore :buffer-size :auto-start :buffers-per-region :static-dir 9 | :worker-threads :port :host :ssl-context :io-threads :client-auth :ajp-port :direct-buffers? :trust-password 10 | :virtual-host :key-password :truststore :configuration :contexts :http2? :servlet-name :filter-map :ssl-port}) 11 | 12 | (defmethod ig/init-key ::server [_ {:keys [handler] :as m}] 13 | (timbre/debug "Launching Immutant web server.") 14 | (immutant/run (wrap-component handler m) (select-keys m valid-options))) 15 | 16 | (defmethod ig/halt-key! ::server [_ server] 17 | (timbre/debug "Stopping Immutant web server.") 18 | (immutant/stop server)) 19 | -------------------------------------------------------------------------------- /src/partsbin/immutant/web/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.immutant.web.alpha 2 | (:require [immutant.web :as immutant] 3 | [integrant.core :as ig] 4 | [partsbin.middleware :refer [wrap-component]] 5 | [taoensso.timbre :as timbre])) 6 | 7 | (def valid-options 8 | #{:path :dispatch? :trust-managers :key-managers :keystore :buffer-size :auto-start :buffers-per-region :static-dir 9 | :worker-threads :port :host :ssl-context :io-threads :client-auth :ajp-port :direct-buffers? :trust-password 10 | :virtual-host :key-password :truststore :configuration :contexts :http2? :servlet-name :filter-map :ssl-port}) 11 | 12 | (defmethod ig/init-key ::server [_ {:keys [handler] :as m}] 13 | (timbre/debug "Launching Immutant web server.") 14 | (immutant/run (wrap-component handler m) (select-keys m valid-options))) 15 | 16 | (defmethod ig/halt-key! ::server [_ server] 17 | (timbre/debug "Stopping Immutant web server.") 18 | (immutant/stop server)) 19 | 20 | -------------------------------------------------------------------------------- /src/partsbin/immutant/scheduling/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.immutant.scheduling.alpha 2 | (:require [immutant.scheduling :as sched] 3 | [immutant.scheduling.joda :as schedj] 4 | [integrant.core :as ig] 5 | [taoensso.timbre :as timbre])) 6 | 7 | (defmethod ig/init-key ::job [_ {:keys [job schedule schedule-seq-fn] :as config}] 8 | (timbre/debug "Launching Immutant scheduling.") 9 | (let [job (fn [] (job config))] 10 | (cond 11 | schedule (sched/schedule job schedule) 12 | schedule-seq-fn (schedj/schedule-seq job (take 10 (schedule-seq-fn)))))) 13 | 14 | (defmethod ig/halt-key! ::job [_ job] 15 | (timbre/debug "Stopping Immutant scheduling.") 16 | (sched/stop job)) 17 | 18 | (comment 19 | (defn job [] 20 | (prn 'fire!)) 21 | 22 | (schedule 23 | job 24 | (-> (in 5 :seconds) 25 | (every :second) 26 | (limit 10))) 27 | (cron "0 0 10 ? * MON-FRI") 28 | (schedule job {:in [5 :seconds] :every :second :limit 3}) 29 | ) -------------------------------------------------------------------------------- /src/partsbin/immutant/scheduling/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.immutant.scheduling.core 2 | (:require [integrant.core :as ig] 3 | [partsbin.immutant.scheduling.alpha :as scheduling] 4 | [clj-time.core :as t] 5 | [clj-time.periodic :refer [periodic-seq]]) 6 | (:import (java.util Date))) 7 | 8 | (derive ::job ::scheduling/job) 9 | 10 | (defn job [{:keys [job-name] :as context}] 11 | (prn (str job-name ": " (Date.)))) 12 | 13 | (def config 14 | {[::job1 ::job] {:job-name "Job 1" 15 | :job job 16 | :schedule {:in [5 :seconds] :every :second :limit 3}} 17 | [::job2 ::job] {:job-name "Job 2" 18 | :job job 19 | :schedule {:cron "0 0 10 ? * MON-FRI"}} 20 | [::job3 ::job] {:job-name "Job 3" 21 | :job job 22 | :schedule-seq-fn (fn [] (periodic-seq (t/now) (t/seconds 1)))}}) 23 | 24 | (comment 25 | (def sys (ig/init config)) 26 | (ig/halt! sys)) 27 | -------------------------------------------------------------------------------- /src/partsbin/datahike/api/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.datahike.api.core 2 | (:require [partsbin.datahike.api.alpha :as datahike] 3 | [integrant.core :as ig] 4 | [datahike.api :as d])) 5 | 6 | (derive ::database ::datahike/database) 7 | (derive ::connection ::datahike/connection) 8 | 9 | (comment 10 | (require '[partsbin.system :refer [with-system]]) 11 | 12 | (def schema 13 | [{:db/ident :name 14 | :db/valueType :db.type/string 15 | :db/cardinality :db.cardinality/one}]) 16 | 17 | (def config {::database {:db-file "tmp/datahike" 18 | :delete-on-halt? true 19 | :initial-tx schema} 20 | ::connection {:db-config (ig/ref ::database)}}) 21 | 22 | (with-system 23 | [{conn ::connection} config] 24 | (d/transact conn [{:name "Mark"} 25 | {:name "Becky"}]) 26 | (d/q 27 | '[:find [?n ...] 28 | :in $ 29 | :where 30 | [_ :name ?n]] 31 | @conn)) 32 | ) -------------------------------------------------------------------------------- /src/partsbin/hawk/core/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.hawk.core.alpha 2 | (:require [integrant.core :as ig] 3 | [hawk.core :as hawk] 4 | [taoensso.timbre :as timbre])) 5 | 6 | (defn wrap-handler [handler component] 7 | (let [component-ctx (dissoc component :opts :groups)] 8 | (fn [ctx event] 9 | (if (or (nil? ctx) (map? ctx)) 10 | (handler (into component-ctx ctx) event) 11 | (handler ctx event))))) 12 | 13 | (defn wrap-watch [{:keys [handler filter] :as m} component] 14 | (cond-> m 15 | handler (assoc :handler (wrap-handler handler component)) 16 | filter (assoc :filter (wrap-handler filter component)))) 17 | 18 | (defmethod ig/init-key ::watch [_ {:keys [paths opts groups] :or {opts {}} :as component}] 19 | (timbre/debug (format "Launching file system watch on paths: %s" paths)) 20 | (hawk/watch! opts (map #(wrap-watch % component) groups))) 21 | 22 | (defmethod ig/halt-key! ::watch [_ watcher] 23 | (timbre/debug "Stopping file system watch.") 24 | (hawk/stop! watcher)) -------------------------------------------------------------------------------- /src/partsbin/hikari_cp/core/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.hikari-cp.core.core 2 | (:require [partsbin.hikari-cp.core.alpha :as cp] 3 | [integrant.core :as ig])) 4 | 5 | ;Note that this is linked to an alpha ns and could change. 6 | (derive ::datasource ::cp/datasource) 7 | 8 | (def config 9 | {::datasource {:adapter "h2" 10 | :url "jdbc:h2:mem:mem_only"}}) 11 | 12 | (comment 13 | (require '[next.jdbc.sql :as sql]) 14 | (require '[clojure.java.jdbc :as j]) 15 | 16 | (def sys (ig/init config)) 17 | 18 | (let [{conn ::datasource} sys] 19 | (sql/query conn ["SELECT 1"])) 20 | 21 | (let [{conn ::datasource} sys] 22 | (j/query {:datasource conn} ["SELECT 1"])) 23 | 24 | (ig/halt! sys) 25 | 26 | ;Hikari is an 'unwrapped' connection pool and is natively compatible with next.jdbc. 27 | ;You must put it in a :datasource key for clojure.jdbc. 28 | (let [{:keys [::datasource] :as temp-sys} (ig/init config)] 29 | (println (j/query {:datasource datasource} "SELECT 1")) 30 | (ig/halt! temp-sys)) 31 | ) -------------------------------------------------------------------------------- /src/partsbin/monger/core/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.monger.core.alpha 2 | (:require [monger.core :as mg] 3 | [integrant.core :as ig] 4 | [taoensso.timbre :as timbre] 5 | [clojure.pprint :as pp]) 6 | (:import (com.mongodb.client MongoClient))) 7 | 8 | (defmethod ig/init-key ::connection [_ spec] 9 | (timbre/debug (format "Initializing Mongo connection with params %s" spec)) 10 | (mg/connect spec)) 11 | 12 | (defmethod ig/halt-key! ::connection [_ connection] 13 | (timbre/debug "Disconnecting from Mongo connection...") 14 | (mg/disconnect connection)) 15 | 16 | (defmethod ig/init-key ::database [_ {:keys [^MongoClient conn db-name]}] 17 | (timbre/debug (format "Getting Mongo DB: %s" db-name)) 18 | (mg/get-db conn db-name)) 19 | 20 | ;TODO - Determine: 21 | ; 1. Do we need to disconnect from a uri connection? 22 | ; 2. The correct thing to return. The conn, the map, etc. 23 | ;(defmethod ig/init-key ::uri-connection [_ {:keys [uri]}] 24 | ; (let [{:keys [db conn]} (mg/connect-via-uri uri)] 25 | ; conn)) -------------------------------------------------------------------------------- /src/partsbin/datascript/core/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.datascript.core.core 2 | (:require [partsbin.datascript.core.alpha :as datascript] 3 | [integrant.core :as ig] 4 | [datascript.core :as d])) 5 | 6 | (derive ::connection ::datascript/connection) 7 | 8 | (def config {::connection {:name {:db/unique :db.unique/identity} 9 | :ingredients {:db/cardinality :db.cardinality/many}}}) 10 | 11 | (comment 12 | (defonce system (ig/init config)) 13 | (let [{:keys [::connection]} system] 14 | (d/transact! connection [{:name "BLT" 15 | :ingredients [:bacon :lettuce :tomato]} 16 | {:name "taco" 17 | :ingredients [:beef :lettuce :tomato]}])) 18 | (let [{:keys [::connection]} system] 19 | (d/q 20 | '[:find ?n ?nm ?i 21 | :in $ 22 | :where 23 | [?e :name ?n] 24 | [?e :ingredients ?i] 25 | [?f :ingredients ?i] 26 | [(not= ?f ?e)] 27 | [?f :name ?nm]] 28 | @connection)) 29 | (ig/halt! system)) -------------------------------------------------------------------------------- /src/partsbin/system.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.system 2 | "Utility ns for creating a ns-global reloadable system" 3 | (:require [integrant.core :as ig])) 4 | 5 | (defmacro create-system 6 | "Create a ns-global system. Takes code that evaluates to a system configuration. 7 | The following items will be created: 8 | * *system* dynamic variable to hold the system. 9 | * 'system' function for viewing the current value of the system. 10 | * Functions start, stop, and restart which will do those actions on the system." 11 | [config] 12 | `(do 13 | (defonce ~(with-meta '*system* {:dynamic true}) nil) 14 | 15 | (defn ~'system [] ~'*system*) 16 | 17 | (defn ~'start [] 18 | (alter-var-root ~'#'*system* (fn [~'s] (if-not ~'s (ig/init ~config) ~'s)))) 19 | 20 | (defn ~'stop [] 21 | (alter-var-root ~'#'*system* (fn [~'s] (when ~'s (do (ig/halt! ~'s) nil))))) 22 | 23 | (defn ~'restart [] (do (~'stop) (~'start))))) 24 | 25 | (defmacro with-system [[bindings config] & body] 26 | `(let [system# (ig/init ~config) 27 | ~bindings system#] 28 | (try 29 | ~@body 30 | (finally (ig/halt! system#))))) -------------------------------------------------------------------------------- /src/partsbin/crux/api/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.crux.api.core 2 | (:require [partsbin.crux.api.alpha :as crux] 3 | [crux.api :as crux-api] 4 | [integrant.core :as ig])) 5 | 6 | (derive ::node ::crux/node) 7 | 8 | ;https://juxt.pro/crux/docs/configuration.html 9 | ;https://juxt.pro/crux/docs/get_started.html#_get_started 10 | (def config 11 | {::node {:crux.node/topology :crux.standalone/topology 12 | :crux.node/kv-store "crux.kv.memdb/kv" 13 | :crux.kv/db-dir "data/db-dir-1" 14 | :crux.standalone/event-log-dir "data/eventlog-1" 15 | :crux.standalone/event-log-kv-store "crux.kv.memdb/kv"}}) 16 | 17 | (comment 18 | (def system (ig/init config)) 19 | 20 | (let [{node ::node} system] 21 | (crux-api/submit-tx 22 | node 23 | [[:crux.tx/put 24 | {:crux.db/id :dbpedia.resource/Pablo-Picasso 25 | :name "Pablo" 26 | :last-name "Picasso"} 27 | #inst "2018-05-18T09:20:27.966-00:00"]])) 28 | 29 | (let [{node ::node} system 30 | db (crux-api/db node)] 31 | (crux-api/q 32 | db 33 | '{:find [e] 34 | :where [[e :name "Pablo"]]})) 35 | 36 | (let [{node ::node} system 37 | db (crux-api/db node)] 38 | (crux-api/entity 39 | db :dbpedia.resource/Pablo-Picasso)) 40 | 41 | (ig/halt! system)) -------------------------------------------------------------------------------- /src/partsbin/core.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.core 2 | "A variant of the reloaded system idea. However, in this case the system is captured in an atom instead of a dynamic 3 | var. This makes it more flexible and easy to have multiple independent systems if needed." 4 | (:require [integrant.core :as ig])) 5 | 6 | (defprotocol IGSys 7 | (system [this]) 8 | (config [this]) 9 | (start [this]) 10 | (stop [this]) 11 | (restart [this]) 12 | (restart-with [this new-config]) 13 | (swap-config! [this f]) 14 | (reset-config! [this new-config])) 15 | 16 | (defn create [cfg] 17 | (let [config (atom cfg) 18 | state (atom nil)] 19 | (reify IGSys 20 | (system [this] @state) 21 | 22 | (config [this] @config) 23 | 24 | (start [this] 25 | (if @state 26 | @state 27 | (swap! state (fn [_] (ig/init @config)))) 28 | this) 29 | 30 | (stop [this] 31 | (when @state 32 | (swap! state ig/halt!)) 33 | this) 34 | 35 | (restart [this] 36 | (doto this stop start)) 37 | 38 | (restart-with [this new-config] 39 | (reset! config new-config) 40 | (restart this)) 41 | 42 | (swap-config! [this f] 43 | (do 44 | (stop this) 45 | (swap! config f))) 46 | 47 | (reset-config! [this new-config] 48 | (do 49 | (stop this) 50 | (reset! config new-config)))))) -------------------------------------------------------------------------------- /src/partsbin/datahike/api/alpha.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.datahike.api.alpha 2 | (:require [datahike.api :as d] 3 | [integrant.core :as ig] 4 | [taoensso.timbre :as timbre] 5 | [clojure.java.io :as io])) 6 | 7 | (defn file->datahike-db-uri [file] 8 | (let [f (io/file file) 9 | _ (io/make-parents f)] 10 | (str "datahike:" (io/as-url f)))) 11 | 12 | (defmethod ig/init-key ::database 13 | [_ {:keys [db-uri db-file initial-tx schema-on-read temporal-index] 14 | :as config}] 15 | (if-some [uri (or db-uri (file->datahike-db-uri db-file))] 16 | (let [args (cond-> [uri] 17 | initial-tx (conj :initial-tx initial-tx) 18 | schema-on-read (conj :schema-on-read schema-on-read) 19 | temporal-index (conj :temporal-index temporal-index))] 20 | (timbre/debug "Creating Datahike DB database.") 21 | (when-not (d/database-exists? uri) 22 | (apply d/create-database args)) 23 | (assoc config :db-uri uri)) 24 | (timbre/error "No uri provided for database"))) 25 | 26 | (defmethod ig/halt-key! ::database [_ {:keys [db-uri delete-on-halt?]}] 27 | (when delete-on-halt? 28 | (timbre/debug "Deleting Datahike DB database.") 29 | (d/delete-database db-uri))) 30 | 31 | (defmethod ig/init-key ::connection [_ {:keys [db-uri db-config]}] 32 | (if-some [uri (or db-uri (:db-uri db-config))] 33 | (do 34 | (timbre/debug "Creating Datahike DB connection.") 35 | (d/connect uri)) 36 | (timbre/error "No db-uri provided for Datahike connection"))) 37 | 38 | (defmethod ig/halt-key! ::connection [_ connection] 39 | (if connection 40 | (do 41 | (timbre/debug "Releasing Datahike DB connection.") 42 | (d/release connection)) 43 | (timbre/warn "No Datahike connection to release!"))) 44 | -------------------------------------------------------------------------------- /src/partsbin/examples/example.clj: -------------------------------------------------------------------------------- 1 | (ns partsbin.examples.example 2 | (:require [partsbin.core :refer [create start stop restart system reset-config!]] 3 | [partsbin.datomic.api.core :as datomic] 4 | [partsbin.hawk.core.core :as hawk] 5 | [partsbin.immutant.web.core :as web] 6 | [partsbin.clojure.java.jdbc.core :as jdbc] 7 | [partsbin.datascript.core.core :as datascript] 8 | [partsbin.durable-queue.core :as durable] 9 | [partsbin.immutant.scheduling.core :as scheduling] 10 | [durable-queue :as dq] 11 | [clojure.java.jdbc :as j] 12 | [integrant.core :as ig] 13 | [datascript.core :as d] 14 | [clojure.edn :as edn] 15 | [clojure.string :as cs] 16 | [clojure.pprint :as pp]) 17 | (:import (java.io File) 18 | (java.util Date))) 19 | 20 | (defn heroes-by-universe [dsdb] 21 | (d/q 22 | '[:find ?name ?universe 23 | :in $ 24 | :where 25 | [?e :name ?name] 26 | [?e :universe ?universe]] 27 | dsdb)) 28 | 29 | (defn app [{:keys [sql-conn dsdb path-info] :as request}] 30 | (case (cs/lower-case (cs/replace path-info #"/" "")) 31 | "supers" {:status 200 :body (with-out-str (pp/pprint (heroes-by-universe @dsdb)))} 32 | "files" {:status 200 :body (with-out-str (pp/pprint (j/query sql-conn "SELECT * FROM FILES")))} 33 | {:status 404 :body (format "No match for %s" path-info)})) 34 | 35 | (def file-table-ddl 36 | (j/create-table-ddl 37 | :files 38 | [[:name "varchar"] 39 | [:processed :time]])) 40 | 41 | (defn setup [conn] 42 | (j/db-do-commands 43 | conn 44 | [file-table-ddl 45 | "CREATE INDEX name_ix ON files ( name );"])) 46 | 47 | (defmethod ig/init-key ::jdbc/init [_ {:keys [conn]}] 48 | (setup conn)) 49 | 50 | (defn file-handler [{:keys [queue conn] :as ctx} {:keys [^File file kind] :as event}] 51 | (when (and (= kind :modify) 52 | (.exists file) 53 | (.isFile file) 54 | (cs/ends-with? (.getName file) ".edn")) 55 | (do 56 | (println (str "Detected change to file: " (.getName file))) 57 | (let [data (edn/read-string (slurp file))] 58 | (println "Adding data to queue.") 59 | (doseq [d data] 60 | (dq/put! queue :my-queue d))) 61 | (j/insert! conn :files {:name (.getName file) :processed (Date.)}))) 62 | ctx) 63 | 64 | (defn deque-job [{:keys [queue dsdb]}] 65 | ;(println "Checking for new items in queue...") 66 | (when-some [task (dq/take! queue :my-queue 10 nil)] 67 | (do 68 | (println "Putting data into datascript") 69 | (d/transact! dsdb [@task]) 70 | (dq/complete! task)))) 71 | 72 | (def config 73 | {::jdbc/connection {:connection-uri "jdbc:h2:mem:mem_only"} 74 | ::jdbc/init {:conn (ig/ref ::jdbc/connection)} 75 | ::datascript/connection {:name {:db/unique :db.unique/identity} 76 | :powers {:db/cardinality :db.cardinality/many}} 77 | ::hawk/watch {:groups [{:paths ["example"] 78 | :handler #'file-handler}] 79 | :queue (ig/ref ::durable/queues) 80 | :conn (ig/ref ::jdbc/connection)} 81 | ::durable/queues {:delete-on-halt? true 82 | :directory "/tmp"} 83 | ::scheduling/job {:job #'deque-job 84 | :schedule {:in [5 :seconds] :every :second} 85 | :queue (ig/ref ::durable/queues) 86 | :dsdb (ig/ref ::datascript/connection)} 87 | ::web/server {:host "0.0.0.0" 88 | :port 3000 89 | :sql-conn (ig/ref ::jdbc/connection) 90 | :dsdb (ig/ref ::datascript/connection) 91 | :handler #'app}}) 92 | 93 | (defonce sys (create config)) 94 | 95 | (comment 96 | (let [{:keys [::jdbc/connection]} (system sys)] 97 | (j/query connection "SELECT 1"))) -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject markbastian/partsbin "0.1.3-SNAPSHOT" 2 | :description "A project for creating functional, data-driven systems." 3 | :url "https://github.com/markbastian/partsbin" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :managed-dependencies [[aleph "0.4.6"] 8 | [args4j "2.33"] 9 | [byte-streams "0.2.4"] 10 | [clj-time "0.15.2"] 11 | [commons-codec "1.14"] 12 | [commons-fileupload "1.4"] 13 | [commons-io "2.6"] 14 | [datascript "0.18.11"] 15 | [etaoin "0.3.6"] 16 | [hawk "0.2.11"] 17 | [hikari-cp "2.11.0"] 18 | [integrant "0.8.0"] 19 | [joda-time "2.10.6"] 20 | [primitive-math "0.1.6"] 21 | [clojure.jdbc/clojure.jdbc-c3p0 "0.3.3"] 22 | [com.datomic/datomic-free "0.9.5697"] 23 | [com.fasterxml.jackson.core/jackson-core "2.11.0"] 24 | [com.fzakaria/slf4j-timbre "0.3.19"] 25 | [com.google.code.findbugs/jsr305 "3.0.2"] 26 | [com.google.errorprone/error_prone_annotations "2.3.4"] 27 | [com.google.guava/guava "23.0"] 28 | [com.google.guava/guava "28.2-jre"] 29 | [com.google.javascript/closure-compiler-externs "v20200406"] 30 | [com.google.javascript/closure-compiler-unshaded "v20200406"] 31 | [com.h2database/h2 "1.4.200"] 32 | [com.novemberain/monger "3.5.0"] 33 | [com.taoensso/encore "2.119.0"] 34 | [com.taoensso/timbre "4.10.0"] 35 | [factual/durable-queue "0.1.6"] 36 | [io.replikativ/datahike "0.2.1"] 37 | [juxt/crux-core "19.09-1.5.0-alpha"] 38 | [org.clojure/clojure "1.10.1"] 39 | [org.clojure/clojurescript "1.10.742"] 40 | [org.clojure/core.async "1.1.587"] 41 | [org.clojure/core.cache "1.0.207"] 42 | [org.clojure/core.memoize "1.0.236"] 43 | [org.clojure/data.codec "0.1.1"] 44 | [org.clojure/java.jdbc "0.7.11"] 45 | [org.clojure/tools.logging "1.1.0"] 46 | [org.clojure/tools.reader "1.3.2"] 47 | [org.immutant/scheduling "2.1.10"] 48 | [org.immutant/web "2.1.10"] 49 | [org.jboss.logging/jboss-logging "3.4.1.Final"] 50 | [org.slf4j/slf4j-api "1.7.30"] 51 | [ring/ring-codec "1.1.2"] 52 | [ring/ring-core "1.8.0"] 53 | [ring/ring-jetty-adapter "1.8.0"] 54 | [seancorfield/next.jdbc "1.0.424"]] 55 | 56 | :dependencies [[org.clojure/clojure "1.10.1"] 57 | [integrant "0.8.0"] 58 | [com.taoensso/timbre "4.10.0"] 59 | [com.fzakaria/slf4j-timbre "0.3.19" :scope "provided"] 60 | [datascript "0.18.11" :scope "provided"] 61 | [etaoin "0.3.6" :scope "provided"] 62 | [hawk "0.2.11" :scope "provided"] 63 | [hikari-cp "2.11.0" :scope "provided"] 64 | [io.replikativ/datahike "0.2.1" :scope "provided"] 65 | [juxt/crux-core "19.09-1.5.0-alpha" :scope "provided"] 66 | [clojure.jdbc/clojure.jdbc-c3p0 "0.3.3" :scope "provided"] 67 | ;[com.datomic/datomic-free "0.9.5697" :scope "provided"] 68 | [com.h2database/h2 "1.4.200" :scope "provided"] 69 | [com.novemberain/monger "3.5.0" :scope "provided"] 70 | [factual/durable-queue "0.1.6" :scope "provided"] 71 | [org.clojure/java.jdbc "0.7.11" :scope "provided"] 72 | [org.immutant/scheduling "2.1.10" :scope "provided" 73 | :exclusions [ch.qos.logback/logback-classic]] 74 | [org.immutant/web "2.1.10" :scope "provided"] 75 | [ring/ring-jetty-adapter "1.8.0" :scope "provided"] 76 | [seancorfield/next.jdbc "1.0.424" :scope "provided"] 77 | [aleph "0.4.6" :scope "provided"]] 78 | 79 | ;Note that these instructions may need to be followed to release on OS X. 80 | ;https://stackoverflow.com/questions/39494631/gpg-failed-to-sign-the-data-fatal-failed-to-write-commit-object-git-2-10-0 81 | :repositories [["releases" {:url "https://repo.clojars.org" :creds :gpg}] 82 | ["snapshots" {:url "https://repo.clojars.org" :creds :gpg}]]) 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor to control, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # partsbin 2 | 3 | You see a tray full of interchangeable parts. You reach in and grab some and start putting things together. Something starts to form. You mostly like what you see but you need to swap out a few things. No problem, it's all super easy. You tinker a bit more and the next thing you know you have a working system. 4 | 5 | This is partsbin - a set of reusable components and a philosophy for building simple systems. 6 | 7 | The goal of this project is to: 8 | 9 | 1. Provide some best practices for designing simple, composable systems. 10 | 1. Provide a few useful functions for working with [Integrant](https://github.com/weavejester/integrant) or similar libraries. As such, when the namespace alias `ig` is used I am referring to `[integrant.core :as ig]`. 11 | 1. Provide reference implementations for some common parts. 12 | 13 | As such, it can be used in a few ways: 14 | 15 | 1. Read it for good advice on how to build composable, data-driven systems (no need to use any of the code here, or just vendor what you will). 16 | 1. Use the top-level utility methods found in `partsbin.system`, `partsbin.middleware`, etc. but define your own `ig/init-key` methods. 17 | 1. Use the provided implementations of `ig/init-key` as well. 18 | 19 | ## Installation 20 | Add the following dependency to your project.clj: 21 | 22 | [![Clojars Project](https://img.shields.io/clojars/v/markbastian/partsbin.svg)](https://clojars.org/markbastian/partsbin) 23 | 24 | ## Definitions 25 | 26 | Since there are multiple frameworks out there for reloadable systems (e.g. [Integrant](https://github.com/weavejester/integrant), [Component](https://github.com/stuartsierra/component), [Mount](https://github.com/tolitius/mount), I need to clarify the following for this discussion: 27 | 28 | * A **system** is the top level set of components to be used. For this project, it is the result of calling `integrant.core/init`. 29 | * A **component** is a sub-element of a system which may have both init-time and runtime dependencies on other components in the system. I may also use 'part' interchangeably with component in this document. 30 | 31 | 32 | To illustrate, here is a sample system with the various aspects labelled: 33 | ``` 34 | ;The pre-initialized system configuration 35 | (def config 36 | {::jdbc/connection {:connection-uri "jdbc:h2:mem:mem_only"} 37 | ::datomic/database {:db-uri "datomic:mem://example" 38 | :delete? true} 39 | ::datomic/connection {:database (ig/ref ::datomic/database) 40 | :db-uri "datomic:mem://example"} 41 | ::web/server {:host "0.0.0.0" 42 | :port 3000 43 | :sql-conn (ig/ref ::jdbc/connection) 44 | :datomic-conn (ig/ref ::datomic/connection) 45 | :handler #'app}}) 46 | 47 | ;The initialized configuration is a system 48 | (def system (ig/init config)) 49 | 50 | ;Component or part refers to the same concept 51 | (def web-component (system ::web/server)) 52 | (def web-part (system ::web/server)) 53 | ``` 54 | 55 | ## Philosophy & Best Practices 56 | 57 | The overarching aim of partsbin is to provide a conceptual framework that facilitates functional, data-driven architectures. As such, here are some guiding principles and practices that will help to achieve this goal: 58 | 59 | * Develop your API bottom-up with simple functions. Functions should be written with a _logical_ component in the first position and then a small number of arguments after. If more than 1 argument to the function is required beyond the component itself, consider passing a map in as the second argument instead. 60 | * Do NOT write your APIs with partsbin or any other API, framework, or system that will consume it in mind. Write your functions as if you had no knowledge of the system(s) that will use it. This keeps your interfaces simple. This includes not using system keys in your functions. 61 | * Good: `(myfunction datomic-conn arg)` - Just takes a connection and arguments with no regard to partsbin or the system map. 62 | * Good: `(myfunction {:keys[datomic-conn]} arg)` - If it makes sense for the component to be a map, ensure that the keys are generic to what is being done in the calling function. `datomic-conn` implies that this is a generic Datomic connection and is not system or config specific. 63 | * Bad: `(myfunction {:keys[project-x/conn]} arg)` - relies on knowing the configuration/system keys of your system. 64 | * When consuming other APIs from partsbin, consider initializing your components to be in the correct format to be used for those APIs. For example, [Clojure JDBC](http://clojure.github.io/java.jdbc/#clojure.java.jdbc) functions take a map in their first position and many [Monger](http://reference.clojuremongodb.info/) functions take a connection. By initializing your components in this fashion you can use those APIs directly without having to use wrappers or restructure your arguments. 65 | * Try to use one part/component at a time in functions. If you need more than one part for a particular functionality, consider using an api such as core.async or rx so that data flows through your system and uses only one part at a time at each stage. Many parts in a function may be a code smell for bad, non-reconfigurable architecture and one-part-per-function is a sign of a good design. If you do need multiple components in a function, combine them into a single map with generic keys. 66 | * Good: `(myfunction datomic-conn args)` - Only the datomic connection is used here. 67 | * Bad: `(myfunction datomic-conn sql-conn args)` - Two different parts are used in two positions. 68 | * Better: `(myfunction {:keys[datomic-conn sql-conn]} args)` - If multiple system components are needed for a single function, combine them using ig/ref to create a single, logical component with all required component. 69 | * Terrible: `(myfunction {:keys[project-x/datomic-connection project-x/hornetq]} arg1 arg2)` - You are conflating your system with your function. This is making your code system-specific and non-generic. 70 | * If you want multiple parts in your component (e.g. a database and a queue), combine them using a middleware [described below](../../../partsbin#partsbinmiddleware). Alternatively, do the combination inline when you define your handler. Functionally, these are identical except in the first case you are externalizing the behavior as a standalone function. 71 | * Var quote (e.g. #'(foo) vs foo)) lambdas/handlers when they are passed in as config params. This will prevent you from having to reload your system when the function changes. 72 | * Ensure that ig/init *always* succeeds. If exceptional or failure behavior occurs (e.g. a DB is unavailable) it is ok to return a failed or broken state rather than just blowing up initialization. This facilitates investigation and recovery. For example, if you have a repl server running you could jack in and inspect the state or try to restart. If, on the other hand, the application has failed due to your exceptions you are in an irrecoverable and non-debuggable state. 73 | 74 | ### Challenges 75 | 76 | * If you detect cycles in your dependency graph you are likely attempting to expose your _system_ in some aspect of your API downstream. For example, you want your system viewable from your web server API code. If this happens, this is your code telling you that you need to revisit your architecture since you are trying to make your system global. Instead, do this: 77 | * Change your API to use only the required capabilities of your system (e.g. a db or a web server) 78 | * Put the required capabilities into a component (e.g. put a db reference in your web component) 79 | * The handler for that component should now have the required capabilites available to it 80 | * Remember the above best practices when doing this. Only put what is needed in each part and design APIs to be parstbin/integrant agnostic. 81 | * Systems in the REPL. Sometimes it can seem kludgly to have to reference a system object in the repl. You will often be wrapping your code in a let form that destructures a db connection, for example. See the following example for what I mean as well as what you can do to work in this situation. 82 | 83 | Suppose you want to test the following function. Note that it is not system-aware, but what you have is a system. 84 | ``` 85 | (defn doit [datomic-conn arg] 86 | (let[db (d/db datomic-conn)] 87 | (d/q query db arg))) 88 | ``` 89 | 90 | The right way to test this could be one of the following: 91 | ``` 92 | ;Option 1: Doing this repeatedly in the REPL might be tedious. 93 | (let[{conn ::datomic/connection} system] 94 | (doit conn arg)) 95 | 96 | :Option 2: Do this once 97 | (def datomic-conn (::datomic/connection system)) 98 | ;Do this as much as you want. 99 | (doit conn arg) 100 | ``` 101 | 102 | ## Top Level Utility Methods 103 | 104 | Currently, there are two small nses that I use pervasively when building systems that I've captured here. 105 | 106 | ### partsbin.core 107 | The first is `partsbin.core`, which declares a simple protocol along the lines of what Component does along with some helper methods for modifying the default system configuration. For a similar effort see [integrant-repl](https://github.com/weavejester/integrant-repl). The main difference I take in my approach is that I use an atom along with a simple protocol to manage the system rather than a dynamic var so that it becomes easier to localize the system (or have many) versus a single (start), (stop), etc. set of functions. If you like those other systems better, feel free to use them. 108 | 109 | Should you want to try out my approach, it is as simple as what you see here: 110 | 111 | ``` 112 | (ns partsbin.example.example 113 | (:require [partsbin.core :refer [create start stop restart system]] 114 | [partsbin.datomic.api.core :as datomic] 115 | [partsbin.immutant.web.core :as web] 116 | [partsbin.clojure.java.jdbc.core :as jdbc] 117 | [clojure.java.jdbc :as j] 118 | [integrant.core :as ig])) 119 | 120 | (defn app [{:keys [sql-conn] :as request}] 121 | (let [res (j/query sql-conn "SELECT 1")] 122 | {:status 200 :body (str "OK - " (into [] res))})) 123 | 124 | (def config 125 | {::jdbc/connection {:connection-uri "jdbc:h2:mem:mem_only"} 126 | ::datomic/database {:db-uri "datomic:mem://example" 127 | :delete? true} 128 | ::datomic/connection {:database (ig/ref ::datomic/database) 129 | :db-uri "datomic:mem://example"} 130 | ::web/server {:host "0.0.0.0" 131 | :port 3000 132 | :sql-conn (ig/ref ::jdbc/connection) 133 | :datomic-conn (ig/ref ::datomic/connection) 134 | :handler #'app}}) 135 | 136 | ;Note that this is a different approach than what most reloadable systems do. 137 | (defonce sys (create config)) 138 | 139 | (start sys) 140 | ;(stop sys) 141 | ``` 142 | 143 | ### partsbin.middleware 144 | The second ns I provide, `partsbin.middleware`, provides an elegant and simple solution to the problem of making components available to your handlers. 145 | 146 | As an example, the typical web handler always boils down to something like this (be it hand-rolled, compojure, or reitit): 147 | 148 | ``` 149 | ;Simple hand-rolled handler 150 | (defn handler [request] 151 | {:status 200 :body "OK"}) 152 | 153 | ;Compojure fanciness 154 | (defroutes app 155 | (GET "/" [] (ok (html5 [:h1 "Hello World"]))) 156 | (GET "/time" [] (str "The time is: " (time/now))) 157 | (route/not-found "

Page not found

")) 158 | ``` 159 | 160 | There may be fancy routes and so on, but ultimately it is a function that takes a request and returns a response. This is challenging to composable apps because you may need a db or other part of your application made available to you. I've seen solutions along the lines of: 161 | 162 | ``` 163 | (defn system-handler[component] 164 | (routes 165 | (GET "/" [] ...some sort of logic that uses the component...))) 166 | ``` 167 | 168 | This solution is pretty ugly. It still isn't clear how to inject the component into the system and your wrapped function is going to be created at every invocation. 169 | 170 | Instead, consider this simple middleware and implementation (in this case using immutant): 171 | 172 | ``` 173 | (defn wrap-component [handler component] 174 | (fn [request] (handler (into component request)))) 175 | 176 | (defmethod ig/init-key ::server [_ {:keys [handler host port] :as m}] 177 | (immutant/run (wrap-component handler m) {:host host :port port})) 178 | ``` 179 | 180 | This solution pours the request into your configured component (the map used to create the server). I pour the request into the component so that if there are key collisions the request wins. 181 | 182 | The result of this middleware is that when you declare keys like `:datomic-conn (ig/ref ::datomic/connection)` the referenced component is now injected into your request and is available to your handler. Awesome, right! 183 | 184 | Here's what it looks like: 185 | ``` 186 | (defn handler [{:keys [datomic-conn] :as request}] 187 | (let [db (d/db datomic-conn)] 188 | {:status 200 :body (d/q some-query db))})) 189 | ``` 190 | 191 | Notice that this follows the guiding principles outlined above. This is just a simple function in which I expect a request with a connection that I specified. There's no particular knowledge of partsbin or anything else. I just write the function and let some other aspect of the system worry about creating the right request. 192 | 193 | ## Implementations 194 | 195 | An aim of partsbin is to provide an ever-accreting set of implementations of `ig/init-key` and `ig/halt-key!` for a variety of libraries that you might use in your systems. To ensure that this is done thoughtfully the following strategy has been taken: 196 | 197 | * For each wrapped library, a package is created under partsbin.package.of.wrapped.lib, where the path corresponds to the same ns that would be imported from the library itself. For example, to obtain a jdbc connection using `clojure.java.jdbc`, the function `clojure.java.jdbc/get-connection` is called from the `clojure.java.jdbc` ns. In this case, I would create the package `partsbin.clojure.java.jdbc`. 198 | * Within the package I use versioned namespaces for each implementation. The following namespaces will be used: 199 | * alpha: The alpha namespace is the latest non-stable version of the implementation. It is subject to change at any time. 200 | * core: The core namespace contains a link to the latest stable (unless alpha is all that exists) version of the api. This is done by deriving the keys from the latest stable api in core. 201 | * vX, where X is the latest major version of the API being wrapped. For example, `clojure.java.jdbc.v0` corresponds to the latest released major version of this library (0.7.10 as of this writing). This version will not be produced until it is deemed stable. If a stable API version is determined to be wanting, a new ns should be created (vX_0 or whatever). If it corresponds to the latest version of the API, the core derivation should also be updated. 202 | * Note that this has a few consequences. Alpha can be unstable. Core can change but should be relatively stable. vX should always be stable/accrete-only. If you want guaranteed behavior, use a vX ns or just vendor and modify your own implementation. 203 | * I often provide a basic config in each core ns as well as a comment that contains an example to launch the config, this making each implementation self-documenting. 204 | * All libraries are set to :scope "provided" in the project.clj file. This is done to prevent users of the project from depending on every single part. This does mean that you, the user, must identify which parts you want and which jars you must include in your path to make everything work. Any ns that you require must have its dependencies explicitly included by you. 205 | * I will happily examine PRs if you want to add more part implementations. 206 | 207 |
208 | 209 | Copyright © 2019 Mark Bastian 210 | 211 | Distributed under the Eclipse Public License either version 1.0 or (at 212 | your option) any later version. 213 | --------------------------------------------------------------------------------