Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
183 lines (135 sloc) 7.91 KB

Hyperion logo

Hyperion Build Status

1 API, multiple database backends.

Hyperion provides you with a simple API for data persistence allowing you to delay the choice of database without delaying your development.

There are a few guiding principles for Hyperion.

  1. key/value store. All Hyperion implementations, even for relational databases, conform to the simple key/value store API.
  2. values are maps. Every 'value' the goes in or out of a Hyperion datastore is map.
  3. :key and :kind. Every 'value' must have a :kind entry; a short string like "user" or "product". Persisted 'value's will have a :key entry; strings generated by the datastore.
  4. Search with data. All searches are described by data. See find-by-kind below.

Hyperion Implementations:

Installation

Leiningen

:dependencies [[hyperion/hyperion-<impl here> "3.2.0"]]

Usage

Creating a datastore

hyperion.api provides a convenient factory function for instantiating any datastore implementation

(use 'hyperion.api)
(new-datastore :implementation :memory)
(new-datastore :implementation :mysql :connection-url "jdbc:mysql://localhost:3306/myapp?user=root" :database "myapp")
(new-datastore :implementation :mongo :host "localhost" :port 27017 :database "myapp" :username "test" :password "test")

Each implementation provides their own facilities our course:

(use 'hyperion.mongo)
(new-mongo-datastore :host "localhost" :port 27017 :database "myapp" :username "test" :password "test")
;or
(let [mongo (open-mongo :host "127.0.0.1" :port 27017)
      db (open-database mongo "myapp" :username "test" :password "test")]
   (new-mongo-datastore db))

Installing a datastore

; with brute force
(set-ds! (new-datastore ...))
; with elegance
(binding [*ds* (new-datastore ...)]
  ; persistence stuff here)

If you can bind the datastore once at high level in your application that's ideal. Otherwise use the brute force set-ds! technique.

Saving a value:

(save {:kind :foo})
;=> {:kind "foo" :key "generated key"}
(save {:kind :foo} {:value :bar})
;=> {:kind "foo" :value :bar :key "generated key"}
(save {:kind :foo} :value :bar)
;=> {:kind "foo" :value :bar :key "generated key"}
(save {:kind :foo} {:value :bar} :another :fizz)
;=> {:kind "foo" :value :bar :another :fizz :key "generated key"}
(save (citizen) :name "Joe" :age 21 :country "France")
;=> #<{:kind "citizen" :name "Joe" :age 21 :country "France" ...}>

Updating a value:

(let [record (save {:kind :foo :name "Sue"})
      new-record (assoc record :name "John")]
  (save new-record))
;=> {:kind "foo" :name "John" :key "generated key"}

Loading a value:

; if you have a key...
(find-by-key my-key)

; otherwise
(find-by-kind :dog) ; returns all records with :kind of "dog"
(find-by-kind :dog :filters [:= :name "Fido"]) ; returns all dogs whos name is Fido
(find-by-kind :dog :filters [[:> :age 2][:< :age 5]]) ; returns all dogs between the age of 2 and 5 (exclusive)
(find-by-kind :dog :sorts [:name :asc]) ; returns all dogs in alphebetical order of their name
(find-by-kind :dog :sorts [[:age :desc][:name :asc]]) ; returns all dogs ordered from oldest to youngest, and gos of the same age ordered by name
(find-by-kind :dog :limit 10) ; returns upto 10 dogs in undefined order
(find-by-kind :dog :sorts [:name :asc] :limit 10) ; returns upto the first 10 dogs in alphebetical order of their name
(find-by-kind :dog :sorts [:name :asc] :limit 10 :offset 10) ; returns the second set of 10 dogs in alphebetical order of their name

Deleting a value:

; if you have a key...
(delete-by-key my-key)

; otherwise
(delete-by-kind :dog) ; deletes all records with :kind of "dog"
(delete-by-kind :dog :filters [:= :name "Fido"]) ; deletes all dogs whos name is Fido
(delete-by-kind :dog :filters [[:> :age 2][:< :age 5]]) ; deletes all dogs between the age of 2 and 5 (exclusive)

Filter operations and acceptable syntax:

  • := "=" "eq"
  • :< "<" "lt"
  • :<= "<=" "lte"
  • :> ">" "gt"
  • :>= ">=" "gte"
  • :!= "!=" "not"
  • :contains? "contains?" :contains "contains" :in? "in?" :in "in"

Sort orders and acceptable syntax:

  • :asc "asc" :ascending "ascending"
  • :desc "desc" :descending "descending"

Entities

Used to define entities. An entity is simply an encapulation of data that is persisted. The advantage of using entities are:

  • they limit the fields persisted to only what is specified in their definition.
  • default values can be assigned to fields
  • types, packers, and unpackers can be assigned to fields. Packers allow you to manipulate a field (perhaps serialize it) before it is persisted. Unpacker conversly manipulate fields when loaded. Packers and unpackers maybe a fn (which will be excuted) or an object used to pivot the pack and unpack multimethods. A type (object) is simply a combined packer and unpacker.
  • constructors are provided

Example:

(defentity UsAddress
    [line-1]
    [line-2]
    [city]
    [state :packer abbreviate-state]
    [zip-code]
    [created-at] ; populated automaticaly
    [updated-at] ; also populated automatically
    )

(use 'hyperion.types)

(defentity Citizen
    [name]
    [age :packer ->int] ; ->int is a function defined in your code.
    [gender :unpacker ->string] ; ->string is a customer function too.
    [occupation :type my.ns.Occupation] ; and then we define pack/unpack for my.ns.Occupation
    [spouse-key :type (foreign-key :citizen)] ; :key is a special type that pack string keys into implementation-specific keys
    [country :default "USA"] ; newly created records will use the default if no value is provided
    [address :type :us-address] ; an embedded map; uses the packing strategy for a UsAddress defined above
    [created-at] ; populated automaticaly
    [updated-at] ; also populated automatically
    )

(save (citizen :name "John" :age "21" :gender :male :occupation coder :spouse-key "abc123"))

;=> #<{:kind "citizen" :key "some generated key" :country "USA" :created-at #<java.util.Date just-now> :updated-at #<java.util.Date just-now> ...)

Full API

To learn more, downlaod hyperion.api and load up the REPL.

user=> (keys (ns-publics 'hyperion.api))
(delete-by-key save* count-all-kinds save find-by-key reload pack create-entity-with-defaults delete-by-kind defentity *ds*
before-save find-by-kind count-by-kind after-load after-create new-datastore ds unpack create-entity set-ds! find-all-kinds new?)

user=> (doc delete-by-key)
-------------------------
hyperion.api/delete-by-key
([key])
  Removes the record stored with the given key.
  Returns nil no matter what.
Something went wrong with that request. Please try again.