agile data concept introduction
TRANSCRIPT
Design your business logic correctly
with Agile Data
Agile Data‣ Based on "Model" class from Agile Toolkit (est. 2011)
‣ Refactored as independent library under MIT license
‣ Framework for your Business Logic
‣ Persistence and mapping library
‣ Contains Query Builder and DataSet concept
‣ Implements Active Record
‣ Supports SQL and NoSQL vendors. Extensible
DataSets
like "VIEW" in SQL
DataSet
‣ Always has "ID" field and primary table/collection
‣ Structured. Has set of fields.
‣ Apply conditions
DataSetUser
Customer Admin
Field Type Label
idname string Name
surname Take advantage Surname
type enum [client, admin, other]last_access timestamp
Field Type Label
client_ref string
total_order_amount int Ordered
Field Type Label
admin_level int
addCondition('type', 'client') addCondition('type', 'admin')
class Model_User extends atk4\data\Model { public $table='user'; function init() { parent::init(); $this->addField('name'); $this->addField('surname'); $this->addFiled('type')->enum(['client','admin','other']); }
}
class Model_Client extends Model_User { function init() { parent::init(); $this->addField('client_ref'); $this->addCondition('type','client'); } }
DataSet
Client Admin
User
$a = $db->add('Model_Client'); $a -> load(10);
// Success!
$u = $db->add('Model_Admin'); $u -> load(10);
// Failure.
Record Access Control
‣ Conditions are always enforced
$a = $db->add('Model_Client'); $a -> load(10);
// Success!
$a['type'] = 'admin'; $a->save();
// Failure.
Record Access Control
‣ Record cannot accidentally go outside.
$a = $db->add('Model_User'); $a -> load(10);
// Success!
$a['type'] = 'client'; $a->save();
// Success.
Record Access Control
‣ But it's ok if member of same DataSet
Persistence Mapping
save / load
Persistence Logic - SQLUser
Customer Admin
table user left join session on session.user_id=user.idand session.ts > '$ts_1h_ago'
join user_admin_permissions `uap` on user.id = `uap`.id where user.type = 'admin'
(select sum(amount) from `order ̀ where `order`.user_id = user.id) as total_order_amount
Persistence Logic - MongoUser
Customer Admin
collection.user
collection.admincollection.customer
cache 'total_order_amount'
Model Features implemented as separate objects
Field‣ Maps to regular column or property in database
‣ Set type, caption, default value, etc
‣ Only save if updated
‣ Cast into SQL expression => `table`.`field`
$model->addField('type')->enum(['client','admin','other']);
SQL\Expression‣ Extends Model\Field
‣ Read-only.
‣ Set callback and return your custom expression
$model->addExpression('total_order_amount') ->set($db->dsql()->table('order')->field('sum("amount")'));
$model->addExpression('full_name') ->set($db->expr( "concat([],' ',[])", [$first_name_field, $last_name_field] ));
SQL\Join‣ Adds few hooks to insert into 2 tables
‣ supports delete, update.
‣ strong or weak join
if ($model->db->supports('join')) { $join = $model->join('user_admin_permissions'); $join->addField('admin_level')->type('int'); }
SoftDelete
‣ Feature built as extension
‣ Alters DataSet
$model->addField('is_deleted')->type('boolean'); $model->addCondition('is_deleted', false);
// plus a hook
Relation‣ Records relation between DataSets
‣ Can jump between DBs
‣ Traversing maps "record" into "DataSet"
$user->hasMany(['Order', 'connection'=>$mongo]);
$user->load(10);$user_orders = $user->ref('Order');
SQL\Relation‣ Records relation between DataSets
‣ Uses same persistence object
‣ Traversing maps "DataSet" into "DataSet"
$user->addCondition('last_access', '>', $time_1h_ago;$online_user_orders = $user->ref('Order');
$user->hasMany('Order');
***‣ Many other features can be implemented
‣ Native or 3rd party support
‣ Example meta-field for image uploading and external storage
$user->add('upload/Image', ['photo_id', 'storage'=>$s3]);
Model Expressiveness
Cast Model into SQL‣ hasMany() - defines one-to-many relation
‣ $model->sum('field') - returns SQL Expression
‣ No hidden query requests. Uses sub-query.
$user->hasMany('Order'); $user->addExpression('total_order_amount')->set( $user->refSQL('Order')->sum('amount') );
Traverse for expressions‣ hasOne() defines many-to-one expression.
‣ fieldQuery() returns expression for the field
‣ No hidden query requests. Uses sub-query.
$user->hasOne('Currency'); $user->addExpression('rate')->set( $user->refSQL('Currency')->fieldQuery('rate') );
Mix - n - Match
// Create DataSet for online users $user->addCondition('last_access', '>', $time_1h_ago);
// Get sum of total orders $total_order_by_online_users = $user->sum('amount')->getOne();
class Model_CurrentOrder extends Model_Order {
function init() { parent::init(); $this->addCondition('user_id', $this->app->getLoggedUserId()); $this->tryLoadAny(); }
// store order and related records into $db function placeOrder($db) { $db->save($this); foreach($this->ref('Order_Item') as $item){ $db->save($item); $item->delete(); } $this->delete(); // removes from memory } }
$memory_order = $session->add('Model_CurrentOrder'); .. $memory_order->placeOrder($mysql_db);
Derived Models
Derived Model‣ Read-only model, that uses another model as a source.
‣ Can aggregate data
‣ Can use UNION across multiple models
‣ Ideal for Report Generation
‣ Build advanced query logic on top of your Business Model
‣ inherits conditions, joins etc,
‣ inherits expressions
class Model_Report_Profit extends atk4\data\Union {
function init() { parent::init(); $this->addModel('Order', ['amount'=>'-amount']); $this->addModel('Payment', ['amount']);
$this->addAggregate('sum', 'amount'); } }
$report = $db->add('Model_Report_Profit') ->group('month', 'month(date)');
$report->addCondition('date','>',$date_1y_ago); $data = $report->getData(['month','amount']);
Profit Report
$on_line_users = $db->add('User'); $on_line_users->addCondition('last_access', '>', $time_1h_ago);
$report->addCondition('user_id',$on_line_users->fieldQuery('id')); $data = $report->getData(['month','amount']);
Profit Report for On-line Users
Not powerful enough?
Extending
‣ Add Domain Model features
‣ Add Persistence features
‣ use Hooks (beforeSave, afterLoad)
‣ Add more NoSQL vendors
Persistence Support‣ SQL
‣ MySQL, PostgreSQL
‣ NoSQL
‣ MongoDB, Memache
‣ Others
‣ RESTful
‣ Session