/sumo_db

Erlang Persistency Framework

Primary LanguageErlangApache License 2.0Apache-2.0

Stories in Ready

sumo_db

Build Status

About

This is a work in progress. There's also an article about sumo_db. This articles might be a little outdated by now, but can still provide some basic information on how to get started.

sumo_db aims to ease db access for erlang applications. It offers a very simple persistance layer capable of interacting with different db's, while offering a consistent api to your code.

Contact Us

If you find any bugs or have a problem while using this library, please open an issue in this repo (or a pull request :)).

Overview

  • sumo_db gives you a standard way to define your db schema, regardless of the db implementation (mongo, mysql, redis, elasticsearch, etc.).

  • Your entities encapsulate behavior in code (i.e. functions in a module) and state in a sumo:doc() implementation.

  • sumo is the main module. It translates to and from sumo internal records into your own state.

  • Each store is managed by a worker pool of processes, each one using a module that implements sumo_store and calls the actual db driver (e.g: sumo_store_mnesia).

  • Some native domain events are supported, that are dispatched through a gen_event:notify/2 automatically when an entity is created, updated, deleted. Also when a schema is created and when all entities of a given type are deleted. Events are described in this article.

  • Full conditional logic support when using find_by/2 and delete_by/2 function. You can find more information about the syntax of this conditional logic operators here.

  • Support for sorting (asc or desc) based on multiple fields unsing find_by/5 and find_all/4 functions. For example this [{age, desc}, {name, asc}]] will sort descendently by age and ascendently by name.

  • Support for docs/models validations through sumo_changeset (check out the Changeset section).

Backends, Stores and Repositories modules

These three concepts have a specific meaning in the context of sumo_db.

  • Backend: establishes and holds a single Database instance connection.

  • Store: implements the specific operations that modify the contents of the backend and retrieves the information it holds.

  • Repository: the application that uses sumo_db should implement one repository for each entity that's defined in it. The repository is the module that bridges the model and the store.

Supported Backends/Adapters

Implementing an Adapter

To implement an adapter, you must implement two specific behaviours:

You can check the implemented adapters mentioned above in order to have a better idea about how to build a custom adapter from scratch.

Events

Sumo dispatches events when things happen. An Event has this structure:

{EventId, Model, Event, Args}
  • EventId is a reference() which identifies the event
  • Model is the model where the event happend, for example, if we are creating a new entitiy in the model people the value of Model would be people.
  • Event is the type of the event.
  • Args extra data sent.

Supported types of events:

  • pre_persisted just before persisting some entity. This event has the entity we want to persist as Args. It is dispatched on this function:
    • sumo:persist/2
  • persisted just after persisting some entity. This event has the persisted entity as Args. This Event has the same EventId as its pre_persisted event. It is dispatched on this function:
    • sumo:persist/2
  • pre_delete_all just before deleting all entities for a model. This event has no Args. It is dispatched on this function:
    • sumo:delete_all/1
  • deleted_all just after deleting all entities for a model. This event has no Args. This Event has the same EventId as its pre_delete_all event. It is dispatched on this function:
    • sumo:delete_all/1
  • pre_deleted just before deleting an entity. This event has the entity id as Args. It is dispatched on this function:
    • sumo:delete/2
  • deleted just after deleting an entity. This event has the entity id as Args. This Event has the same EventId as its pre_deleted event. It is dispatched on this function:
    • sumo:delete/2
  • pre_deleted_total just before deleting by some delete conditions. This event has the sumo conditions as Args. It is dispatched on this function:
    • sumo:delete_by/2
  • deleted_total just after deleting by some delete conditions. This event has a list with the number of entities deleted and the delete conditions as Args. This Event has the same EventId as its pre_deleted_total event. It is dispatched on this function:
    • sumo:delete_by/2
  • pre_schema_created just before creating a sumo schema. This event has no Args. It is dispatched on this function:
    • sumo:create_schema/2
  • schema_created just after creating a sumo schema. This event has no Args. This Event has the same EventId as its pre_schema_created event. It is dispatched on this function:
    • sumo:create_schema/2

Sumo requires users to add their own gen_event's in order to handle those events. In order to add them Users have to configure sumo properly. In the config file we can add them like this under sumo_db configuration:

{events, [
   {'_', sumo_test_people_events_manager},
   {people, sumo_test_people_events_manager}
 ]}

Sumo allows us to add a gen_event to one type of model (i.e. people) or for all ('_').

Changeset

This feature is inspired by Elixir Ecto.Changeset, and the module that implements this feature is sumo_changeset.

Changeset Usage Example

NOTE: This example uses FancyFlow in order to pipe changeset functions in a nicer way.

%% suppose you have a model/doc `person`, and that module provides a function
%% to encapsulate model/doc creation
Person = person:new(<<"John">>, <<"Doe">>),

%% create the set of params/changes
Params = #{age => 33, id => 1, <<"last_name">> => <<"other">>},

%% run the changeset
Changeset = [pipe](people,
  sumo_changeset:cast(_, Person, Params, [id, first_name, last_name, age, status]),
  sumo_changeset:validate_required(_, [id, last_name, status]),
  sumo_changeset:validate_inclusion(_, status, [<<"active">>, <<"blocked">>]),
  sumo_changeset:validate_number(_, age, [{less_than_or_equal_to, 18}]),
  sumo_changeset:validate_length(_, last_name, [{min, 3}]),
  sumo_changeset:validate_format(_, last_name, <<"^[a-zA-Z]">>)),

Examples

See: examples/blog for a full example. To run it, while being in the top level directory:

make all blog

See: examples/custom_store for creating your own Store. To run it, follow the instructions in this README

Running Dialyzer

$ rebar3 dialyzer

Running Tests

$ rebar3 ct

Change Log

All notable changes to this project will be documented in the CHANGELOG.md.

Contributors

We want to thank all of our contributors for their hard work :muscle:.