Skip to content

matthusby/boss_db

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BossDB: A sharded, caching, pooling, evented ORM for Erlang

Supported databases

  • Mnesia
  • MongoDB
  • MySQL
  • PostgreSQL
  • Riak (experimental)
  • Tokyo Tyrant

Complete API references

Querying: http://www.chicagoboss.org/api-db.html

Records: http://www.chicagoboss.org/api-record.html

BossNews: http://chicagoboss.org/api-news.html

Write an adapter: https://github.com/evanmiller/ChicagoBoss/wiki/DB-Adapter-Quickstart

Usage

boss_db:start(DBOptions),
boss_cache:start(CacheOptions), % If you want cacheing with Memcached
boss_news:start() % If you want events

DBOptions = [
    {adapter, mock | tyrant | riak | mysql | pgsql | mnesia | mongodb},
    {db_host, HostName::string()},
    {db_port, PortNumber::integer()},
    {db_username, UserName::string()},
    {db_password, Password::string()},
    {shards, [
        {db_shard_models, [ModelName::atom()]},
        {db_shard_id, ShardId::atom()},
        {db_host, _}, {db_port, _}, ...
    ]},
    {cache_enable, true | false},
    {cache_exp_time, TTLSeconds::integer()}
]

CacheOptions = [
    {adapter, memcached_bin}, % More in the future
    {cache_servers, [{HostName::string(), Port::integer(), Weight::integer()}]}
]

Introduction

BossDB is a compiler chain and run-time library for accessing a database via Erlang parameterized modules. It solves the age-old problem of retrieving named fields without resorting to verbosities like proplists:get_value/2 or dict:find/2. For example, if you want to look up a puppy by ID and print its name, you would write:

Puppy = boss_db:find("puppy-1"),
io:format("Puppy's name: ~p~n", [Puppy:name()]).

Functions for accessing field names are generated automatically. All you need to do is create a model file and compile it with boss_record_compiler. Example:

The model file, call it puppy.erl:

-module(puppy, [Id, Name, BreedId]).

Then compile it like:

{ok, puppy} = boss_record_compiler:compile("puppy.erl")

...and you're ready to go.

Associations

BossDB supports database associations. Suppose you want to model the dog breed (golden retriever, poodle, etc). You would create a model file with a special "-has" attribute, like:

-module(breed, [Id, Name]).
-has({puppies, many}).

Then back in puppy.erl you'd add a "-belongs_to" attribute:

-module(puppy, [Id, Name, BreedId]).
-belongs_to(breed).

Once you've compiled breed.erl with boss_record_compiler, you can print a puppy's associated breed like:

Breed = Puppy:breed(),
io:format("Puppy's breed: ~p~n", [Breed:name()]).

Similarly, you could iterate over all the puppies of a particular breed:

Breed = boss_db:find("breed-47"),
lists:map(fun(Puppy) -> 
        io:format("Puppy: ~p~n", [Puppy:name()]) 
    end, Breed:puppies())

Querying

You can search the database with the boss_db:find functions. Example:

Puppies = boss_db:find(puppy, [{breed_id, 'equals', "breed-47"}])

This is somewhat verbose. If you compile the source file with boss_compiler, you'll be able to write the more simple expression:

Puppies = boss_db:find(puppy, [breed_id = "breed-47"])

BossDB supports many query operators, as well as sorting, offsets, and limits; see the API references at the top.

Validating and saving

To create and save a new record, you would write:

Breed = breed:new(id, "Golden Retriever"),
{ok, SavedBreed} = Breed:save()

You can provide validation logic by adding a validation_tests/0 function to your model file, e.g.

-module(breed, [Id, Name]).
-has({puppies, many}).
-export([validation_tests/0]).

validation_tests() ->
    [{fun() -> length(Name) > 0 end,
        "Name must not be empty!"}].

If validation fails, the save/0 function will return a list of error messages instead of the saved record.

You can also provide spec strings in the parameter declaration if you want to validate the attribute types before saving, e.g.

-module(puppy, [Id, Name::string(), BirthDate::datetime()]).

Accepted types are:

  • string()
  • binary()
  • datetime()
  • timestamp() [e.g. returned by erlang:now()]
  • integer()
  • float()

If the type validation fails, then validation_tests/0 will not be called.

Events

BossDB provides two kinds of model events: synchronous save hooks, and asynchronous notifications via BossNews. Save hooks are simple; just define one or more of these functions in your model file:

before_create/0 -> ok | {ok, ModifiedRecord} | {error, Reason}
before_update/0 -> ok | {ok, ModifiedRecord} | {error, Reason}
after_create/0 
after_update/0
before_delete/0 -> ok | {error, Reason}

BossNews is more complicated but also more powerful. It is a notification system that executes asynchronously, so the code that calls "save" does not have to wait for callbacks to complete. The central concept in BossNews is a "watch", which is an event observer. You can create and destroy watches programmatically:

{ok, WatchId} = boss_news:watch(TopicString, CallBack),
boss_news:cancel_watch(WatchId)

Four kinds of topic strings are supported:

"puppies" => watch for new and deleted Puppy records
"puppy-42.*" => watch all attributes of Puppy #42
"puppy-*.name" => watch the "name" attribute of all Puppy records
"puppy-*.*" => watch all attributes of all Puppy records

The callback is passed two or three arguments: the event name (created/updated/deleted), information about the event (i.e. the new and old values of the watched record), and optionally user information passed as the third argument to boss_news:watch/3.

BossNews is suited to providing real-time notifications and alerts. For example, if you want to log each time a puppy's name is changed,

boss_news:watch("puppy-*.name", 
        fun(updated, {Puppy, 'name', OldName, NewName}) ->
            error_logger:info_msg("Puppy's name changed from ~p to ~p", [OldName, NewName])
        end)

For more details see the documentation at http://www.chicagoboss.org/api-news.html

Caching

If caching is enabled, queries and records are automatically cached. BossDB uses BossNews events to automatically invalidate out-of-date cache entries; you do not need to write any cache logic in your save hooks.

Sharding

Vertical sharding is supported via the db_shards config option. Simply add shard-specific configuration in a proplist along with an extra config parameter called db_shard_models, which should be a list of models (atoms) in the shard.

Pooling

BossDB uses Poolboy to create a connection pool to the database. Connection pooling is supported with all databases.

About

BossDB: a sharded, caching, pooling, evented ORM for Erlang

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Erlang 100.0%