NAME
EntityModel - manage entity model definitions
VERSION
version 0.003
SYNOPSIS
use EntityModel;
# Define model
my $model = EntityModel->new->load_from(
JSON => { entity : [
{ name : 'article', field : [
{ name : 'idarticle', type : 'bigserial' },
{ name : 'title', type : 'varchar' },
{ name : 'content', type : 'text' }
], primary => { field : [ 'idarticle' ], separator : ':' }
] }
);
# Apply PostgreSQL schema (optional, only needed if the model changes)
$model->apply('PostgreSQL' => { schema => 'datamodel', host => 'localhost', user => 'testuser' });
# Create Perl classes
$model->apply('Perl' => { namespace => 'Entity', baseclass => 'EntityModel::EntityBase' });
my $article = Entity::Article->create(
title => 'Test article',
content => 'Article content'
);
say "ID was " . $article->id;
my ($match) = Entity::Article->find(
title => 'Test article'
);
$match->title('Revised title');
die "Instances of the same object should always be linked, consistent and up-to-date"
unless $article->title eq $match->title;
DESCRIPTION
This module provides a data storage abstraction system (in the form of an Object Relational Model) for accessing backend storage from Perl and other languages. The intent is to take a model definition and generate or update database tables, caching layer and the corresponding code (Perl/C++/JS) for accessing data.
A brief comparison and list of alternatives is in the "MOTIVATION" and "SEE ALSO" sections, please check there before investing any time into using this module.
METHODS
new
Constructor. Given a set of options, will load any plugins specified (and/or the defaults), applying other config options via the appropriate plugins.
load_from
Read in a model definition from the given EntityModel::Definition-based source.
Parameters:
Type - must be a valid EntityModel::Definition subclass, such as 'Perl', 'JSON' or 'XML'.
Definition - dependent on the subclass, typically the filename or raw string data.
save_to
Saves the current model definition to a definition.
Parameters:
Type - must be a valid EntityModel::Definition subclass, such as 'Perl', 'JSON' or 'XML'.
Definition - dependent on the subclass, typically the filename or scalarref to hold raw string data.
load_component
Brings in the given component if it hasn't already been loaded.
add_support
Bring in a new EntityModel::Support class for this EntityModel::Model.
Example:
$model->add_support(Perl => { namespace => 'Entity' });
add_storage
Add backend storage provided by an EntityModel::Storage subclass.
Example:
$model->add_storage(PostgreSQL => { service => ... });
transaction
Run the coderef in a transaction.
Notifies all the attached EntityModel::Storage instances that we want a transaction, runs the code, then signals end-of-transaction.
load_plugin
Load a new plugin. You don't want this (yet).
handler_for
Returns the handler for a given entry in the EntityModel::Definition.
DESTROY
Unload all plugins on exit.
ENTITY MODELS
A model contains metadata and zero or more entities.
Each entity typically represents something that is able to be instantiated as an object, such as a row in a table. Since this module is heavily biased towards SQL-style applications, most of the entity definition is similar to SQL-92, with some additional features suitable for ORM-style access via other languages (Perl, JS, C++).
An entity definition primarily contains the following information - see EntityModel::Entity for more details:
name - the name of the entity, must be unique in the model
type - typically 'table'
description - a more detailed description of the entity and purpose
primary - information about the primary key
Each entity may have zero or more fields:
name - the unique name for this field
type - standard SQL type for the field
null - true if this can be null
reference - foreign key information
Additional metadata can be defined for entities and fields. Indexes apply at an entity level. They are used in table construction and updates to ensure that common queries can be optimised, and are also checked in query validation to highlight potential performance issues.
name - unique name for the index
type - index type, typically one of 'gin', 'gist', or 'btree'
fields - list of fields that are indexed
The fields in an index can be defined as functions.
Constraints include attributes such as unique column values.
Models can also contain additional information as defined by plugins - see EntityModel::Plugin for more details on this.
USAGE
An entity model can be loaded from several sources. If you have a database definition:
create table test ( id int, name varchar(255) );
then loading the SQL plugin with the database name will create a single entity holding two fields.
If you also load EntityModel::Plugin::Apply::Perl, you can access this table as follows:
my $tbl = Entity::Test->create({ name => 'Test', url => '/there' })->commit;
my ($entity) = Entity::Test->find({ name => 'Test' });
is($orig->id, $entity->id, 'found same id');
IMPLEMENTATION
Nearly all classes use EntityModel::Class to provide basic structure including accessors and helper functions and methods. This also enables strict, warnings and Perl 5.10 features.
Logging is handled through EntityModel::Log, which imports functions such as logDebug.
Arrays and hashes are typically wrapped using EntityModel::Array and EntityModel::Hash respectively, these provide similar functionality to autobox at the cost of a slight performance hit.
For error handling, an EntityModel::Error object is returned - this allows chained method calling without having to wrap in eval or check the result of each step when you don't care about failure. The last method in the chain will return false in boolean context.
OVERVIEW
The current EntityModel::Model can be read from a number of sources:
EntityModel::Definition::XML - XML structured definition holding the entities, fields and any additional plugin-specific data. All information is held in the content - no attributes are used, allowing this format to be interchangeable with JSON and internal Perl datastructures.
EntityModel::Definition::JSON - standard Javascript Object Notation format.
EntityModel::Definition::Perl - nested Perl datastructures, with the top level being a hashref. Note that this is distinct from the Perl class/entity structure described later.
Aside from entities, models can also contain plugin-specific information such as site definition or database schema. It is also possible - but not recommended - to store credentials such as database user and password.
Once a model definition has been loaded, it can be applied to one or more of the following:
EntityModel::Support::SQL - database schema
EntityModel::Support::Perl - Perl classes
EntityModel::Support::CPP - C++ classes
EntityModel::Support::JS - Javascript code
The SQL handling is provided as a generic DBI-compatible layer with additional support in subclasses for specific databases. Again, Note that the EntityModel::Support::SQL is intended for applying the model to the database schema, rather than accessing the backend storage. The EntityModel::Support classes apply the model to the API, so in the case of the database this involves creating and updating tables. For Perl, this dynamically creates a class structure in memory, and for C++ or JS this will export the required support code for inclusion in other projects.
In terms of accessing backend storage, ach of the language-specific support options provides an API which can communicate with one or more backend storage implementations, rather than being tightly coupled to a data storage method. Typically the Perl backend would interact directly with the database, and C++/JS would use a REST API against a Perl server.
BACKEND STORAGE
Backend storage services are provideed by subclasses of EntityModel::Storage.
EntityModel::Storage::PostgreSQL - PostgreSQL database support
EntityModel::Storage::MySQL - MySQL database support
EntityModel::Storage::SQLite - SQLite3 database support
CACHING
Cache layers are handled by EntityModel::Cache subclasses.
EntityModel::Cache::MemcachedFast - memcached layer using Cache::Memcached::Fast.
EntityModel::Cache::Perl - cache via Perl variables
USAGE EXAMPLE
Given a simple JSON model definition:
{ entity : [
{ name : 'article', field : [
{ name : 'idarticle', type : 'bigserial' },
{ name : 'title', type : 'varchar' },
{ name : 'content', type : 'text' }
], primary => { field : [ 'idarticle' ], separator : ':' }
] }
this would create or alter the article
table to meet this definition:
create table "article" (
idarticle bigserial,
title varchar,
content text,
primary key (idarticle)
)
Enabling the Perl plugin would grant access via Perl code:
my $article = Entity::Article->create(title => 'Test article', content => 'Article content')
say "ID was " . $article->id;
my ($match) = Entity::Article->find(title => 'Test article');
$match->title('Revised title');
die "Instances of the same object should always be linked, consistent and up-to-date"
unless $article->title eq $match->title;
with the equivalent through Javascript being:
var article = Entity.Article.create({ title : 'Test article', 'content' : 'Article content' });
alert("ID was " + article.id());
var match = Entity.Article.find({ title : 'Test article' })[0];
match.title('Revised title');
if(article.title() != match.title())
alert("Instances of the same object should always be linked, consistent and up-to-date");
or in C++:
Entity::Article article = new Entity::Article().title('Test article').content('Article content');
std::cout << "ID was a.id() << std::endl;
Entity::Article *match = Entity::Article::find().title('Test article').begin();
match->title('Revised title');
if(article->title() != match->title())
throw new std::string("Instances of the same object should always be linked, consistent and up-to-date");
The actual backend implementation may vary between these, but the intention is to maintain a recognisable, autogenerated API across all supported languages. The C++ implementation may inherit from a class that writes directly to the database, for example, and the Javascript code could be designed to run in a web browser accessing the resources through HTTP or as a node.js implementation linked directly to the database, but the top-level code should not need to care which underlying storage method is being used.
ASYNCHRONOUS MODEL ACCESS
Since backend storage response times can vary, it may help to use an asynchronous API for accessing entities. Given the 'article' example from earlier, the Perl code is now:
my $article = Entity::Article->create(
title => 'Test article',
content => 'Article content'
);
$article->on_create(sub {
my $a = shift;
say "ID was " . $a->id;
});
Entity::Article->find(title => 'Test article')->on_result(sub {
my $match = shift;
$match->title('Revised title');
die "Instances of the same object should always be linked, consistent and up-to-date"
unless $article->title eq $match->title;
});
EXPORTING MODEL DEFINITIONS
Although it is possible to reverse-engineer the model in some cases, such as SQL, normally this is not advised. This may be useful however for a one-off database structure import, by writing the results to a model config file in JSON or XML format:
my $model = EntityModel::Plugin::Apply::SQL->loadFrom(
db => $db,
schema => $schema
);
$model->export(xml => 'model.xml');
Once the model has been exported any further updates should be done in the model definition file rather than directly to the database if possible, since this would allow the generation of suitable upgrade/downgrade scripts.
Currently there is support for SQL and Perl model export, but not for Javascript or C++.
AUDITING
Audit tables are generated by default in the _audit schema, following the same naming convention as the audited tables with the addition of the following columns:
audit_action - one of:
Insert - regular insert or mass import (such as PostgreSQL COPY statement)
Update - updates directly through database or through the EntityModel API
Delete - manual or API removal
and indicates the action that generated this audit entry.
audit_date - timestamp of this action
audit_context - description of the action, typically the command that was called
CLASS STRUCTURE
The primary classes used for interaction with models include:
EntityModel - top level class providing helper methods
EntityModel::Definition - classes for dealing with model definitions
EntityModel::Support - language-specific support
The following classes provide features that are used throughout the code:
EntityModel::DB - wrapper for DBI providing additional support for transaction handling
EntityModel::Query - database query handling
EntityModel::Template - wrapper around Template Toolkit
EntityModel::Cache - simple cache implementation using Cache::Memcached::Fast by default
MOTIVATION
Some of the primary motivations for this distribution over any of the existing approaches (see next section for some alternatives):
Support for languages other than Perl - most projects end up using at least one other language, e.g. web-based projects typically have some Javascript on the frontend talking to the Perl backend.
Single configuration file - I like to be able to export, backup and diff the entity layout and having this in a single file as JSON or XML is more convenient for me than using multiple Perl packages, this also allows the configuration to be used by non-Perl code.
Backend abstraction - the conceptual model generally doesn't need to be tied to a particular backend, for example the Perl side of things could either talk directly to a database or use a webservice.
Easy editing and visualisation - I like to have the option of using other tools such as diagram editors to add/modify the entity layout, rather than having to create or edit Perl files. This helps to separate actual code changes from configuration / layout issues; the EntityModel distribution can be installed once and for many common applications no further custom code should be required.
Flexibility - although too much interdependence is generally a bad thing, being able to include other concepts in the configuration file has been useful for tasks such as building websites with common features.
Clearly most, if not all, alternative systems could be adapted to support the above requirements.
SEE ALSO
There are plenty of other, better ORM implementations available on CPAN:
DBIx::Class - appears to be the most highly regarded and actively developed one out there, and the available features, code quality and general stability are far in advance of this module so unless you need the EntityModel multi-language export features I would encourage people to look here first.
Rose::DB::Object - written for speed, appears to cover most of the usual requirements, personally found the API less intuitive than other options but it appears to be widely deployed.
Fey::ORM - newer than the other options, also appears to be reasonably flexible.
DBIx::DataModel - UML-based Object-Relational Mapping (ORM) framework.
Class::DBI - generally considered to be superceded by DBIx::Class, which provides a compatibility layer for existing applications.
Distributions which provide class structure and wrappers around the Perl OO mechanism are likewise covered by several other CPAN modules, with the clear winner here in the form of Moose and derivatives.
AUTHOR
Tom Molesworth <cpan@entitymodel.com>
LICENSE
Copyright Tom Molesworth 2008-2011. Licensed under the same terms as Perl itself.