Models
The model tier is declarative: a model class names its table and fields, the app names its databases, and every model on one database shares a single connection per worker.
A model class
package MyApp::Model::Book;
use Punk::Model;
table 'books';
field id => { type => 'integer', primary => 1 };
field title => { type => 'string', required => 1, minLength => 1 };
field author => { type => 'string' };
field created => { type => 'string' };
1;
Field definitions double as validation - create and update check
their input against the declared types and constraints.
Registration
database dsn => 'dbi:SQLite:dbname=myapp.db';
model 'Book';
model 'Book' resolves against MyApp::Model:: at boot. Several
databases can be configured by name; a model picks its own with a
database declaration of its own, defaulting to the unnamed one:
database dsn => 'dbi:SQLite:dbname=myapp.db'; # the default
database analytics => { dsn => 'dbi:Pg:dbname=warehouse' };
The contract
Every backend speaks the same six methods, with the same SQL and the same result shapes:
my $book = $c->model('Book')->get(id => 42);
my $page = $c->model('Book')->search({ author => 'Gibson' },
{ limit => 20, offset => 40 });
my $all = $c->model('Book')->all;
my $made = $c->model('Book')->create({ title => 'Neuromancer' });
my $fresh = $c->model('Book')->update(42, { author => 'W. Gibson' });
$c->model('Book')->delete(42);
Punk::Model::DBI, the default backend, memoises quote_identifier
and the fixed-shape get/delete SQL on the pooled connection - a get is
2.06 microseconds against 3.61 rebuilt every call.
The async backend
Punk::Model::DBIx::Loop is the same contract, the same SQL and the
same result shapes, but every method returns a
Punk::Future and the statement runs on
DBIx::Loop over the worker's own
event loop:
database backend => 'DBIx::Loop', dsn => 'dbi:Pg:dbname=myapp';
get '/books/:id' => sub {
my ($c) = @_;
return $c->model('Book')->get(id => $c->param('id'))->then(sub {
my ($book) = @_;
$c->render('book/view', { book => $book });
});
};
On a Hyperman worker the query does not block the loop - the worker serves other requests while the database round-trips. Swapping a model between backends is a configuration change, not a rewrite.