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.
- key/value store. All Hyperion implementations, even for relational databases, conform to the simple key/value store API.
- values are maps. Every 'value' the goes in or out of a Hyperion datastore is map.
- :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.
- Search with data. All searches are described by data. See find-by-kind below.
Hyperion Implementations:
- memory - an in-memory datastore, ideal for testing, included in hyperion-api.jar
- gae - Google App Engine Datastore
- mongo - Mongo DB
- mysql - MySQL
- postgres - PostgreSQL
- riak - Riak
- sqlite - SQLite
- redis - Redis
:dependencies [[hyperion/hyperion-<impl here> "3.2.0"]]
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))
; 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.
(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" ...}>
(let [record (save {:kind :foo :name "Sue"})
new-record (assoc record :name "John")]
(save new-record))
;=> {:kind "foo" :name "John" :key "generated key"}
; 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
; 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"
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> ...)
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.