chopin-composer
v1.1.0
Published
small library that abstracts class composition concepts
Downloads
53
Readme
Chopin Composer
Introduction
What is Chopin?
Chopin is a minimalistic library thats abstracts high-level concepts of class compostion. Following class compostion techniqs provides better code reusability and easier scaling for apps with big code.
What is class composition?
While "classes" rooted in OOP paradigm and compositing is rooted in FOP paradigm, with no doubt class composition is a concept that lays in the gray area in between those two. Imagine we have a base DAO (data access object) that supports basic CRUD (create, read, update, delete) operations against our DB.
// base.js
module.exports = class BaseDao {
constructor(tableData = []){
this.ai = 1; // auto incerement
this.tableData = tableData; // fake data
}
get(id){
return Promise.resolve(this.tableData.find(item => item.id === id));
}
create(model){
this.tableData.push(Object.assign(model, { id: ++this.ai });
return Promise.resolve(model);
}
update(model){
if(!model.id) throw Error('no id on model. cannet update');
this.tableData.filter(item => item.id !== model.id);
this.tableData.push(model);
return Promise.resolve(model);
}
delete(id){
this.tableData.filter(item => item.id !== id);
return Promise.resolve(true)
}
}
now we can create concrete classes from our base:
// ConcreteA.js
module.exports = class ConcreteA extends BaseDao {}
// ConcreteB.js
module.exports = class ConcreteB extends BaseDao {}
// etc...
but wait! what happens if we want to create a new ConcreteX class that extends our base dao but can't update? well, we can overide the update method like this:
module.exports = class ConcreteX extends BaseDao {
update(){throw Error('update is not supported on ConcreteX') }
}
but yet again, if we'll have bunch of concrete's who doesnt support update we will have to remember to override the BaseClass update each time we extend it. it points out to deeper flaw in our design: our base class doesnt represent the real "base", the common thing, about our models. here comes into play Chopin and the class composition techniq.
But.. How?
Once again i want you to close your eyes and imagine. Imagine a world were BaseClasses are nothing but state and minimalistic functionality related to it. a world were ConcreateClasses are totally seperated from the logic they expose and they deal only with matters related to their "Concreatecy". a world where we (developers) put most of our time in writing new features, and compose them into our new\existing classes insead of strugling with making our previous code work in a new place. I'm happy to introduce you to Chopin:
// base.js
module.exports = class BaseDao {
constructor(tableData = []){
this.ai = 1; // auto incerement
this.tableData = tableData; // fake data
}
}
remove all logic from BaseDao
// features/get.js
const Chopin = require('chopin-composer');
module.exports = Chopin.compose(({feature}) => feature
('get').method(function (id){
return Promise.resolve(this.tableData.find(item => item.id === id));
}))
// features/create.js
const Chopin = require('chopin-composer');
module.exports = Chopin.compose(({feature}) => feature
('create').method(function (model){
this.tableData.push(Object.assign(model, { id: ++this.ai }));
return Promise.resolve(model);
}))
// features/update.js
const Chopin = require('chopin-composer');
module.exports = Chopin.compose(({feature}) => feature
('update').method(function (model){
if(!model.id) throw Error('no id on model. cannet update');
this.tableData.filter(item => item.id !== model.id);
this.tableData.push(model);
return Promise.resolve(model);
}))
// features/delete.js
const Chopin = require('chopin-composer');
module.exports = Chopin.compose(({feature}) => feature
('delete).method(function (id){
this.tableData.filter(item => item.id !== id);
return Promise.resolve(true)
}
seperate features to files and sae them in 'features' dir.
// models/ConcreteA.js
const BaseDao = require('../base');
const features = [ //all features are presented.
require('features/get'),
require('features/create'),
require('features/update'),
require('features/delete'),
];
module.exports = Chopin.compose(({model}) => model('ConcreteA').on(BaseDao).from(features));
// models/ConcreteX.js
const BaseDao = require('../base');
const features = [ // doesnt support update
require('features/get'),
require('features/create'),
require('features/delete'),
];
module.exports = Chopin.compose(({model}) => model('ConcreteA').on(BaseDao).from(features));
compose models from created features. for further reading about about class compositing I recommend this great articale.
Should I use Chopin?
Chopin can be used in any js\node project. yet, in existing code bases it will require you to make adjustments in order to achieve the benefits of using it.
Documentation
Chopin
- .compose(compositionInitializer : function)
- .use(toolInitializer : function)
Tools
ModelPlanner
- constructor(name : string)
- .on(baseclass : class)
- .from(features : array)
- .construct(cb : function(...constructorArgs))
FeaturePlanner
- constructor(name : string)
- .on(featureInitializer : function(feature : FeaturePlanner))
- .method(name : string, cb : function | cb : named function)
- .methods(methods : object)
- .construct(cb : function(...constructorArgs))
- .initializer(cb : function)
- .setData(data : obj)
- .appendData(data : obj | key : string, data: any)