1 Introduction
1.1 Overview
std.lib.resource is part of the standard foundation library set. This page collects the public API reference for the namespace.
2 Walkthrough
2.1 Registering resource specs
Resources are described by specs and variants. res:spec-add registers a new spec, res:variant-add adds a variant, and res:spec-list and res:variant-list query the registry.
register and inspect a resource spec
(res:spec-add {:type :demo/counter
:mode {:key :id
:allow #{:global :shared}
:default :shared}
:instance {:create (fn [config] (atom (:start config 0)))
:setup identity
:teardown identity}})
=> any?
(res:spec-list)
=> (contains [:demo/counter])
(res:variant-add :demo/counter {:id :default})
=> any?
(res:variant-list :demo/counter)
=> (contains [:default])
2.2 Inspecting specs and modes
Retrieve the merged spec/variant with res:variant-get, and query the default mode with res:mode.
inspect a registered resource
(res:mode :demo/counter)
=> :shared
(:mode (res:variant-get :demo/counter))
=> (contains {:default :shared})
2.3 Resource keys and paths
res-key computes the key used to identify an active resource, and res-path builds the full path used to store it.
compute resource keys and paths
(res-key :shared :demo/counter :default {:id :main})
=> :main
(res-path :shared :demo/counter :default {:id :main})
=> '(:shared [:demo/counter :default] :main)
2.4 Starting and stopping resources
The res function (and friends such as res:start and res:stop) manage the lifecycle of active resources.
start and stop a resource
(let [counter (res :demo/counter {:id :main :start 10})]
@counter
=> 10
(res:exists? :demo/counter {:id :main})
=> true
(res:stop :demo/counter {:id :main})
=> any?)
2.5 End-to-end: register, start, query, and tear down
A complete resource workflow: register a spec and variant, start an instance, verify it is active, then stop it and confirm it is gone.
lifecycle a counter resource
(res:spec-add {:type :demo/gauge
:mode {:key :id
:allow #{:shared}
:default :shared}
:instance {:create (fn [config] (atom (:value config 0)))
:setup identity
:teardown identity}})
(res:variant-add :demo/gauge {:id :default})
(let [g (res :demo/gauge {:id :primary :value 42})]
@g
=> 42)
(res:active :demo/gauge)
=> (contains {:shared (contains {:demo/gauge (contains {:default (contains [:primary])})})})
(res:stop :demo/gauge {:id :primary})
(res:exists? :demo/gauge {:id :primary})
=> false
3 API
- *active*
- *alias*
- *namespace*
- *registry*
- +type+
- res-access-get
- res-access-set
- res-api-fn
- res-base
- res-call-fn
- res-input
- res-key
- res-path
- res-restart
- res-setup
- res-start
- res-stop
- res-teardown
- res:active
- res:mode
- res:spec-add
- res:spec-get
- res:spec-list
- res:spec-remove
- res:variant-add
- res:variant-get
- res:variant-list
- res:variant-remove
v 3.0
(defn res-access-get
([mode type variant key]
(let [path (res-path mode type variant key)]
(at/atom:get *active* path))))
link
res-access-set ^
[mode type variant key entry]
access set function for active resource
v 3.0
(defn res-access-set
([mode type variant key entry]
(let [path (res-path mode type variant key)]
(-> (at/atom:set *active* path entry)
(at/atom:set-changed)))))
link
res-api-fn ^
[res-call extra post default]
helper function to create user friendly calls
v 3.0
(defn res-api-fn
[res-call extra post default]
(let [call-fn (res-call-fn res-call)]
(fn [& all]
(let [rall (reverse all)
rall (if (and default (== (count all) 1))
(cons default rall)
rall)
[rargs rstart] [(take extra rall) (drop extra rall)]]
(cond-> (apply call-fn (conj (vec (reverse rstart)) (reverse rargs)))
post (h/call post))))))
link
v 3.0
(defn res-base
([mode type variant key config]
(let [spec (res:variant-get type variant)
{:keys [allow]} (:mode spec)
_ (or (allow mode)
(h/error "Mode not allowed" {:mode mode
:options allow}))]
(or (:instance (res-access-get mode type variant key))
(res-start mode type variant key config)))))
link
v 3.0
(defn res-call-fn
([f]
(fn res-call
([type args]
(if-let [[type variant] (get @*alias* type)]
(res-call type variant args)
(res-call type :default args)))
([type input args]
(let [[variant input] (res-input input)]
(res-call type variant input args)))
([type variant input args]
(let [mode (res:mode type variant)
key (res-key mode type variant input args)]
(res-call mode type variant key args)))
([mode type variant key args]
(apply f mode type variant key args)))))
link
NONE
(defn- res-input
([input]
(cond (keyword? input) [input {}]
(map? input) [:default input]
(symbol? input) [:default input]
(string? input) [:default input]
:else (h/error "Not valid" {:input input}))))
link
v 3.0
(defn res-key
([mode type variant input & [args]]
(let [input (or (last args) input)
key (if (map? input)
(let [spec (res:variant-get type variant)
{:keys [key]} (:mode spec)]
(key input))
input)
to-sym (fn [x]
(.getName (the-ns x)))]
(case mode
:global nil
:namespace (to-sym (or key *namespace* *ns*))
:shared key))))
link
(res-key :shared :hara/concurrent.atom.executor :default {:id :hello}) => :hello
v 3.0
(defn res-path
([mode type variant input]
(let [key (res-key mode type variant input)
ext (cond-> [] key (conj key))]
(concat (cons mode [[type variant]]) ext))))
link
(res-path :shared :hara/concurrent.atom.executor :default {:id :hello}) => '(:shared [:hara/concurrent.atom.executor :default] :hello)
v 3.0
(defn res-restart
([mode type variant key]
(when-let [entry (res-access-get mode type variant key)]
(res-stop mode type variant key)
(res-start mode type variant key (:config entry)))))
link
v 3.0
(defn res-setup
([type variant config]
(let [{:keys [instance hook] :as spec} (res:variant-get type variant)
{:keys [create setup]} instance
{:keys [pre-setup post-setup]} hook
config (c/merge-nested (:config spec) config)]
(cond-> (create config)
pre-setup (doto pre-setup)
:then setup
post-setup (doto post-setup)))))
link
v 3.0
(defn res-start
([mode type variant key config]
(if (res-access-get mode type variant key)
(h/error "Already started" {:type type
:variant variant
:key key})
(let [instance (res-setup type variant config)]
(->> {:key key :config config :instance instance}
(res-access-set mode type variant key))
instance))))
link
v 3.0
(defn res-stop
([mode type variant key]
(when-let [entry (res-access-get mode type variant key)]
(at/atom:clear *active* (res-path mode type variant key))
(res-teardown type variant (:instance entry)))))
link
v 3.0
(defn res-teardown
([type variant instance]
(let [{:keys [hook] :as spec} (res:variant-get type variant)
{:keys [teardown]} (:instance spec)
{:keys [pre-teardown post-teardown]} hook]
(h/suppress
(cond-> instance
pre-teardown (doto pre-teardown)
:then teardown
post-teardown (doto post-teardown))))))
link
v 3.0
(defn res:active
([]
(res:active nil))
([type]
(res:active type nil))
([type variant]
(let [keys-fn (apply comp keys (partial c/filter-vals not-empty)
(cond-> []
type (conj (partial c/filter-keys (comp #{type} first)))
variant (conj (partial c/filter-keys (comp #{variant} first)))))
filter-fn (partial c/filter-vals not-empty)]
(h/-> @*active*
(update :global keys-fn)
(update :namespace (comp filter-fn (partial c/map-vals keys-fn)))
(update :shared (comp filter-fn (partial c/map-vals keys-fn)))
filter-fn))))
link
(res:active)
v 3.0
(defn res:mode
([type]
(res:mode type :default))
([type variant]
(let [spec (res:spec-get type)]
(or (get-in spec [:variant variant :mode :default])
(-> spec :mode :default)))))
link
(res:mode :hara/concurrent.atom.executor) => :global (res:mode :hara/concurrent.atom.executor :std.concurrent.print) => :global
v 3.0
(defn res:spec-add
([{:keys [type] :as spec}]
(-> (at/atom:put *registry* [type] (c/merge-nested +type+ spec))
(at/atom:put-changed))))
link
(resolve 'res:spec-add) => var?
v 3.0
(defn res:spec-get
([type]
(or (at/atom:get *registry* [type])
(h/error "No spec available" {:type type
:options (res:spec-list)}))))
link
(res:spec-get :hara/concurrent.atom.executor) => map?
v 3.0
(defn res:spec-remove
([type]
(at/atom:clear *registry* [type])))
link
(resolve 'res:spec-remove) => var?
v 3.0
(defn res:variant-add
([type {:keys [id alias] :as spec}]
(if alias
(at/atom:set *alias* [alias] [type id]))
(-> (at/atom:set *registry* [type :variant id] spec)
(at/atom:set-changed))))
link
v 3.0
(defn res:variant-get
([type]
(res:variant-get type :default))
([type id]
(let [{:keys [variant] :as spec} (res:spec-get type)
variant (or (get-in spec [:variant id])
(h/error "No variant available"
{:type type
:variant id
:options (keys variant)}))
variant (c/merge-nested (dissoc spec :variant)
variant)]
variant)))
link
4 std.lib.resource: A Comprehensive Summary
The std.lib.resource namespace provides a powerful and extensible framework for managing application resources. It defines a registry for resource specifications (specs) and their variations (variants), and offers a lifecycle management system for creating, setting up, tearing down, and accessing resource instances. This module is crucial for ensuring consistent and controlled access to external dependencies, services, or any managed components within the foundation-base project.
Key Features and Concepts:
- Resource Registry and Specifications:n
*namespace*: A dynamic var for the current namespace, used in resource key resolution.n*alias*: A global atom mapping resource aliases to their[type id]pairs.n*registry*: A global atom storing all registered resource specifications (specs).n*active*: A global atom tracking all active resource instances, categorized bymode(:global,:namespace,:shared).n+type+: A default resource specification template, defining structure formode,config,instance(create/setup/teardown functions), andvarianthooks.nres:spec-list []: Lists all registered resource specification types.nres:spec-add [spec]: Adds a new resource specification to the registry, merging with+type+.nres:spec-remove [type]: Removes a resource specification from the registry.nres:spec-get [type]: Retrieves a resource specification by itstype.nres:variant-list [& [type]]: Lists all variants for a given resourcetypeor for all types.nres:variant-add [type spec]: Adds a new variant to a resource specification, optionally creating an alias.nres:variant-remove [type id]: Removes a variant from a resource specification.nres:variant-get [type & [id]]: Retrieves a specific variant of a resource specification, merging it with the base spec.nn2. Resource Instance Management:nres:mode [type & [variant]]: Determines the default management mode (:global,:namespace,:shared) for a resource type and variant.nres:active [& [type variant]]: Lists all active resource instances, optionally filtered bytypeandvariant.nres-setup [type variant config]: Creates and sets up a resource instance based on itstype,variant, andconfig. It invokescreateandsetupfunctions defined in the spec, along withpre-setupandpost-setuphooks.nres-teardown [type variant instance]: Tears down a resource instance, invoking theteardownfunction andpre-teardown/post-teardownhooks.nn3. Resource Access and Lifecycle:nres-input [input]: Normalizes various input formats (keyword, map, symbol, string) into a[type config]pair.nres-key [mode type variant input & [args]]: Generates a unique key for a resource instance based on itsmode,type,variant, andinput.nres-path [mode type variant input]: Constructs the path within the*active*atom to store/retrieve a resource instance.nres-access-get [mode type variant key]: Retrieves an active resource entry from*active*.nres-access-set [mode type variant key entry]: Stores an active resource entry in*active*.nres-start [mode type variant key config]: Starts a new resource instance and adds it to*active*.nres-stop [mode type variant key]: Stops and removes a resource instance from*active*.nres-restart [mode type variant key]: Stops and then restarts an existing resource instance.nres-base [mode type variant key config]: The core function for getting or starting a resource instance. It ensures the resource is started only once and respects the allowed modes.nn4. API Generation (User-Friendly Functions):nres-call-fn [f]: A helper function that creates a flexible dispatch function for resource operations, allowing various argument combinations to resolve to the full[mode type variant key args]signature.nres-api-fn [res-call extra post default]: A helper function to create user-friendly API calls (e.g.,res:start,res:stop) that wrapres-call-fn.nres-api-tmpl: A template for generating resource API functions usingstd.lib.template/deftemplate.n Generated API Functions: The module usesh/template-entrieswithres-api-tmplto generate a set of high-level functions:nres:exists?: Checks if a resource is active.nres:set: Sets an active resource instance.nres:stop: Stops a resource.nres:path: Gets the path for a resource.nres:start: Starts a resource.nres:restart: Restarts a resource.n *res: The main entry point for getting or starting a resource.
Overall Importance:
The std.lib.resource module is fundamental to the foundation-base project's ability to manage its complex dependencies and runtime components. Its key contributions include:
- Centralized Resource Management: Provides a single, consistent mechanism for defining, registering, and managing all types of application resources.n Lifecycle Control: Ensures that resources are properly created, set up, used, and torn down, preventing leaks and ensuring system stability.n Extensibility: The protocol-driven design allows for easy integration of new resource types and custom lifecycle hooks.n Dynamic Configuration: Supports different resource variants and configurations, enabling flexible adaptation to various environments or use cases.n Modularity and Decoupling: Decouples resource usage from their concrete implementations, promoting a more modular and maintainable architecture.n* Namespace-Awareness: Supports namespace-specific resource instances, which is crucial for multi-tenant or modular applications.
By offering these comprehensive resource management capabilities, std.lib.resource significantly enhances the foundation-base project's ability to build and manage its sophisticated multi-language development ecosystem.