项目作者: PetrGlad

项目描述 :
Manage lifecycle of stateful components
高级语言: Clojure
项目地址: git://github.com/PetrGlad/compost.git
创建时间: 2016-10-09T23:22:46Z
项目社区:https://github.com/PetrGlad/compost

开源协议:Eclipse Public License 1.0

下载


Compost

Library that manages lifecycle of stateful components.
This is a variation of https://github.com/stuartsierra/component project’s idea.
At the moment this library does not support ClojureScript.

Usage

Add this dependency to your project

  1. [net.readmarks/compost "0.2.0"]

See tests for examples. Component declaration has form

  1. {:requires #{:required-component-id-1 :required-component-id-2}}
  2. :this initial-state
  3. :get (fn [this] ...) ;; Returns value of this component that other components will get as dependency.
  4. :start (fn [this dependency-components-map] ...) ;; Acquire resources (open connections, start threads ...)
  5. :stop (fn [this] ...)} ;; Release resources.

All fields are optional, defaults are:

  1. {:requires #{}
  2. :this nil
  3. :get identity
  4. :start (fn [this dependency-components-map] this)
  5. :stop identity}

:start and :stop functions should return new value of component’s :this.
If component acquires resources in :start it must release them in :stop.
System declaration is a plain map

  1. {:component-1-id component-1-declaration
  2. :component-2-id component-2-declaration
  3. ...
  4. }

Lifecycle usage example

  1. (require '[net.readmarks.compost :as compost])
  2. (let [s (compost/start system-map #{:web-component :some-worker-component})]
  3. (Thread/sleep 5000)
  4. (compost/stop s))

You can salvage current system state after exception as follows:

  1. (try
  2. (compost/start system-map)
  3. (catch ExceptionInfo ex
  4. (if-let [sys (compost/ex-system ex)]
  5. (compost/stop sys) ;;; Handle this system as desired here.
  6. (throw ex))))

Error handling helpers

This feature is implemented by net.readmarks.compost.keeper namespace.
This namespace is considered experimental, it’s contents might change in any version.

The agent (“keeper”) holds current state of system along with lifecycle’s exceptions.
The usage example:

  1. (require '[net.readmarks.compost :as compost])
  2. (require '[net.readmarks.compost.keeper :as keeper])
  3. (def sys (keeper/keeper system-map))
  4. (keeper/update-keeper! sys compost/start)
  5. ;; Now sys holds current system value and errors if there are any.
  6. ;; (:system @sys) is current system map value.
  7. ;; (:errors @sys) is list of of system lifecycle errors. It is empty if system changes were successful.
  8. ;; You can send these errors into a log, for example:
  9. (keeper/flush-errors! sys println) ;; The function gets errors one by one.

Using com.stuartsierra.component components

You can adapt existing components as follows:

  1. (defn component-using [init using]
  2. {:requires (set using)
  3. :this init
  4. :start (fn [this deps]
  5. (-> (merge this deps)
  6. component/start)
  7. :stop component/stop})
  8. (def system
  9. {:conn-source (component-using
  10. (->MyConnPool)
  11. [])
  12. :dao (component-using
  13. (map->MyDbComponent {})
  14. [:conn-source])}

Note that unlike com.stuartsierra.component sequence of n.r.compost/stop might differ from reverse startup one.
Only explicitly declared dependencies are respected. If you need to account for implicit dependencies
you should add additional elements to components’ :require collections.

Motivation

I like what component provides but I also want

  • Use any value as component. E.g. often I want a function closure to be a component.
    Or, alternatively, a component be visible as a function. Besides this, I do not like the idea of
    always keeping dependency reference even though it might be needed only in start function.
  • Do not require a new type for each component. Implementing Lifecycle
    gets in the way when you only need an ad hoc component. Also requirement for component
    to be a map and implement LifeCycle effectively restricts component to be a record.
    This also means that sometimes people resort to work-arounds to avoid creating new types.
  • Use plain Clojure data structures to configure system. I think that putting configuration into metadata
    was a mistake. Instead of streamlining it actually complicates code. System configuration is also data
    that one might want to inspect or modify. Give it equal rights :)
  • Be compatible with com.stuartsierra.component/Lifecycle components.
    There are already lots of such components and this is a good thing.
    This part should require only small amount of glue code.

License

Copyright © Petr Gladkikh PetrGlad@gmail.com

Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.