Dance like NoSQL is watching - Hacking Thy Fearful Symmetry

Hacking Thy Fearful Symmetry

Hacker, hacker coding bright

Dance like NoSQL is watching

created: April 16, 2017

Here come a fast one that I have to get out of my head.

First, there was DBIx::NoSQL

Lately I've returned to DBIx::NoSQL, which provides a simple, easy way to create key/value stores. Simple? Yes, really dead simple. Like that kind of simple:


use DBIx::NoSQL;

my $store = DBIx::NoSQL->connect( 'store.sqlite' );

        #     model    id  
$store->set( Player => yenzie => {
    name => 'yenzie',
    alliance => 'Nefarious Coallition',
});

$store->set( Ship => enkidu => {
    name   => 'Enkidu',
    hull   => 10,
    coords => [ 10, 10 ],
});

In the background, DBIx::NoSQL creates a DBIx::Class schema. All data structures are serialized and dumped in a big happy table, while their ids are saved in per-model tables. Indexes can also be added to models whenever we want


$store->model('Player')->index('alliance');

When new indexes are created, DBIx::NoSQL alters the schema dynamically in consequence. And since the schema is DBIx::Class-based, its power is there for searches when needed.


my @players = $store->search('Player')->where({ 
    # needs to be the id or an indexed field
    alliance => { like => qr/evil/ }
)->order_by( 'name DESC' )->all;

In a nutshell, it's a decent tool when setting up a MongoDB or CouchDB instance -- let alone a full relational schema -- sounds like too much work.

Then came along DBIx::NoSQL::Store::Manager

When I originally played with it I upped the antes and wrote DBIx::NoSQL::Store::Manager, which uses Moose objects as models. Which let you create the class MyStore::Model::Player, and the system will figure it out from there.


package MyStore::Model::Player;

use Moose;
use MooseX::MungeHas 'is_ro';

with 'DBIx::NoSQL::Store::Manager::Model';

has name     => ( traits  => [ 'StoreKey' ] );
has alliance => ( traits  => [ 'StoreIndex' ] );

1;

And now... lights on the dancefloor...

And so last week I was musing on how easy this allows to set up a quick'n'dirty NoSQL backend. Then I thought... and what if we push that DWIMery all the way to a REST interface?

So I fired up the ol' Dancer environment, hacked for an hour or two, and now I have... no, let's show, not tell.

I'm skipping the non-important parts of the config.yml. All that matter for us right now is that we serialize using JSON, and set a database and store manager class for the store plugin.

serializer: JSON
plugins:
    StoreManager:
        db: my_store.sqlite3
        store_class: Proto::Store

As you can see, the main store class is straightforward.

Mind you, in a future iteration I might streamline things further and just build the class automatically if no customization is required. But that's a yak for a different day.

package Proto::Store;

use Moose;

extends  'DBIx::NoSQL::Store::Manager';

1;

Model class. Still short and sweet.

package Proto::Store::Model::Player;

use 5.20.0;

use Moose;
use MooseX::MungeHas { 
    has_ro => [ 'is_ro' ], 
    has_rw => [ 'is_rw' ] 
};

with 'DBIx::NoSQL::Store::Manager::Model';

has_ro name => (
    traits   => [ 'StoreKey' ],
    required => 1,
);

has_rw alliance => (
    traits   => [ 'StoreIndex' ],
);

1;

The Dancer app. All of it.

No, really, that's all of it.

package Proto;

use Dancer2;
use Dancer2::Plugin::StoreManager;

1;

And we DANCE!

Player creation.

By the by, I'm using HTTP Pie as my REST tool. It's really nice.

$ http post :5000/player name=yenzie alliance="Nefarious Coallition of Space Monkeys"
HTTP/1.0 200 OK
Content-Length: 110
Content-Type: application/json
Date: Sun, 16 Apr 2017 20:06:45 GMT
Server: HTTP::Server::PSGI
Server: Perl Dancer2 0.204004

{
    "__CLASS__": "Proto::Store::Model::Player",
    "alliance": "Nefarious Coallition of Space Monkeys",
    "name": "yenzie"
}

Yup, it's there.

$ http get :5000/player/yenzie
HTTP/1.0 200 OK
Content-Length: 110
Content-Type: application/json
Date: Sun, 16 Apr 2017 20:09:04 GMT
Server: HTTP::Server::PSGI
Server: Perl Dancer2 0.204004

{
    "__CLASS__": "Proto::Store::Model::Player",
    "alliance": "Nefarious Coallition of Space Monkeys",
    "name": "yenzie"
}

Updates work.

$ http put :5000/player/yenzie alliance="Brown Coat"
HTTP/1.0 200 OK
Content-Length: 83
Content-Type: application/json
Date: Sun, 16 Apr 2017 20:10:21 GMT
Server: HTTP::Server::PSGI
Server: Perl Dancer2 0.204004

{
    "__CLASS__": "Proto::Store::Model::Player",
    "alliance": "Brown Coat",
    "name": "yenzie"
}

... but not on ready-only values.

Right now the exception value is an ugly trace. It'll eventually get prettier.

$ http put :5000/player/yenzie name="Yoda"
HTTP/1.0 500 Internal Server Error
Content-Length: 7210
Content-Type: application/json
Date: Sun, 16 Apr 2017 20:11:21 GMT
Server: HTTP::Server::PSGI
Server: Perl Dancer2 0.204004

{
    "exception": "...",
    "message": "",
    "status": "500",
    "title": "Error 500 - Internal Server Error"
}

... or unknown attributes.

$ http put :5000/player/yenzie power=maximal
HTTP/1.0 500 Internal Server Error
Content-Length: 289
Content-Type: application/json
Date: Sun, 16 Apr 2017 20:13:36 GMT
Server: HTTP::Server::PSGI
Server: Perl Dancer2 0.204004

{
    "exception": "Can't locate object method \"power\" via package \"Proto::Store::Model::Player\" at /home/yanick/work/perl-modules/Dancer2-Plugin-StoreManager/proto/bin/../lib/Dancer2/Plugin/StoreManager.pm line 71.\n",
    "message": "",
    "status": "500",
    "title": "Error 500 - Internal Server Error"
}

We can delete too, natch.

$ http delete :5000/player/yenzie
HTTP/1.0 200 OK
Content-Length: 0
Content-Type: text/html
Date: Sun, 16 Apr 2017 20:14:30 GMT
Server: HTTP::Server::PSGI
Server: Perl Dancer2 0.204004

C'mon. You have to admit. It's all pretty sweet, isn't?

Last notes

The code for the plugin can be found on GitHub, although it's still purely in its Proof-of-Concept stage. I'll try to groom it for CPAN if there is a demand.

I'm also in the midst of updating DBIx::NoSQL::Store::Manager itself, and I'm also making inquiries about DBIx::NoSQL, which would need to have a few things updated (mostly, it's using Any::Moose, which has been deprecated since). I would also love to see DBIx::NoSQL adopting more backends. I heard smashing recommendations for Postgres and its JSON support, so the mix of the two could be interesting (well, to me, at least).

comments powered by Disqus

About the author

Yanick Champoux
Perl necrohacker , ACP writer, orchid lover. Slightly bonker all around.