@or-change/duck
v0.6.2
Published
Duck is an extensible, lightweight, flexible, progressive software development frameworks for building software product.
Downloads
14
Maintainers
Readme
Duck is an extensible, lightweight, flexible, progressive software development frameworks for building software product. It uses dependency injection and control inversion just a little like "Java Spring". It help developers to create, use, manage various runtime objects to inject to whereever required. Everyone can extend framework directly in low cost.
In the past, we often have to face to inflexible, confused project directory problems in development in Node.js. Everyone could disgust require modules from parent directory like require('../../')
. Because it represent some coupling out of control. Duck resolve the problems above and provide a programming models to help developer avoid incorrect design.
Duck does not impose any restrictions on your coding style. But it will still provide the necessary guidance to advance the right design. Be brave enough to use your imagination to solve your problems.
On the one hand Duck is a useful framework & pattern for architect or tech leader to take apart development tasks. Developers working at the top will feel comfortable with structuring their work. Duck hopes every developer does NOT need to learn any framework practices. Duck respects, supports and maintains the development team's own practices. What does it mean?
The DUCK knows first when the river becomes warm in SPRING.
Duck is used to build product!
Installation
npm install @or-change/duck
Usage
To build a very simple product,
const Duck = require('@or-change/duck');
const meta = require('./package.json');
// To create the product factory named Simple.
function Simple() {
const simple = {};
Duck({
id: 'com.orchange.duck.demo',
name: 'Simple Product',
version: meta.version
}, ({ product }) => {
// There will be 2 methods on a Simple product instance.
simple.getMeta = function getMeta() {
return product.meta;
};
simple.getComponents = function getComponents() {
return product.components;
}
});
return simple;
}
// To create a Simple product instance then use it.
const simple = Simple();
console.log(simple.getMeta());
// => {name: "Simple Product", namespace: "", version: "0.0.0", description: "Default product descrition"}
console.log(simple.getComponents());
// => []
// Because there is no component in the product.
API Reference
Duck
A duck instance is just an EventEmitter
instance without any preset event. Everyone can emit necessary event by injection.product
.
[RECOMMANDED] Avoid using classes or constructors if there are no special requirements. For a framework for assembly resources like duck, class
& constructor
cause too much programming burden and are not flexible enough. There is almost no such need for inctanceof
. "Duck typing" always be useful.
Duck(options[, callback])
options
is an object. callback
is a function. Return a product instance. The product instance is extended from EventEmitter
.
Options
| Property | Type | Default value | Description | | ------------ | --------- | ----------------------- | ---------------------------- | | id | String | | Product id | | name | String | 'Default Product Name' | Product name | | namespace | String | '' | Namespace component may use | | version | String | '0.0.0' | Current version | | description | String | 'No descrition' | What am I? | | installed | Object | () => {} | Invoking after components | | injection | Object | {} | Initial dependences | | components | Array | [] | Product components list |
[RECOMMANDED] Some dependencies just for current product can be injected into by options.injection
to avoid defining components (see components). Because dependencies from components is universal and reusable across products.
[RECOMMANDED] In hook options.installed
, some direct dependencies using component dependencies could be created and injected into injection. In other words, injection could still be changed here and then to be non-extensible.
Callback
Only one formal parameter could be accessed is injection
. [RECOMMANDED] Using object destructuring assignment syntax to access dependencies of injection can make the code more clear. There is no meaningful context here (this === null
). Arrow function is also accepted here if you like.
Injection is very IMPORTANT to be careful for using. Avoid to change injection outside of the Duck instance lifecycle. Because incorrect state may cause fatal runtime errors. This will not help reduce R&D costs.
[RECOMMANDED] Accessing injection & dependencies in callback and providing external accessing by indirect way.
Best practice example,
const Duck = require('@or-change/duck');
const http = require('http');
function MyProduct() {
const product = {};
Duck({
id: 'com.xxx.yyy.zzz',
component: [
Duck.Web()
]
}, function callback({ Web }) {
// Access all dependences safely here.
// Be careful and responsible for providing external indirect access to injected dependencies.
product.start = function startServer() {
return http.createServer(Web.Application.Default()).listen(8080);
};
});
return product;
}
// External access.
const myProduct = MyProduct();
myProduct.start(); // Use injected dependence 'Web' indirectly.
// Use arrow callback function
function MyProductB() {
Duck({
id: 'com.xxx.yyy.zzz',
component: [
Duck.Web()
]
}, ({ Web }) => {
// There is no meaningful context here. (this === null)
// Arrow function is also accepted here if you like will make the code more clear.
console.log(Web);
});
}
Lifecycle
- Initialization
- Normalizing options
- Preparing product instance
- Creating injection instance with
options.injection
Mixin
options.injection
- Installing components
- Collecting all component configuration items
- Invoking all installers of components
- Setting dependencies or accessing dependencies unsafely
- Preset product dependence
- Setting product instance into injection as a dependence
- Then freezing injection
Invoking
options.installed(injection)
- Components created
- Invoking all created hooks of components
- Accessing dependencies safely but can NOT changing injection any more
Invoking
callback(injection)
Product dependence
A product
dependence will be injected into injection after components have been installed. The project
provides meta
, components
and duck
getters to reflect the final abstract of the duck instance.
Use project dependence,
const Duck = require('../');
Duck({
id: 'com.xxx.yy.zz',
}, ({ project }) => {
console.log(project.meta);
console.log(project.components);
console.log(project.duck);
});
project.meta
returns a new plain object every time. Pproperties,
| Property | Type | Example Value | Description | | ------------ | --------- | ----------------------- | ---------------------------- | | id | String | 'com.xx.yy.zz' | options.id | | name | String | 'Default Product Name' | options.name | | namespace | String | '' | options.namespace | | version | String | '0.0.0' | options.version | | description | String | 'No descrition' | options.description |
project.components
returns a new array about used components. Each element properties,
| Property | Type | Example Value | Description |
| ------------ | --------- | ----------------------- | ---------------------------- |
| id | String | 'com.xx.yy.zz' | component.id
|
| name | String | 'Component Name' | component.name
|
| description | String | 'No descrition' | component.description
|
| details | any | null | component.getDetails()
|
project.duck
returns a new plain object about duck. Properties,
| Property | Type | Example Value | Description | | ---------------- | --------- | ----------------------- | ---------------------------- | | version | String | '0.0.0' | duck version | | peerDependencies | object | {} | duck peerDependencies |
Component
Component
is use to append some runtime dependencies into injection. Each component instance MUST include 3 items to describe the features & meta of itself. They are id
, name
, install
.
In addition, component.description
is a string for describing what the component is. component.created
a hook function will be called after the duck has been created.
[RECOMMENDED] Appending dependence only in component.install
. Accessing dependence only after duck instance created, like in component.created
and Duck(options[, callback
]).
[NOTICE] Defining a component means that there are some widespread jobs need to be decoupled. Just use options.injection
if there is no special need for flexibility.
Althought accessing when component.install
is ok, it means "Component_A depends Component_B" that cause coulping between components. Developers can still handle these problems with care and "Put Component_A before Component_B" to ensure Component_B can use Component_A in install.
Instance
Properties Table
| Property | Type | Default value | Description | | ------------ | --------- | ----------------- | ---------------------------- | | id | String | | Unique component id | | name | String | | Component name for display | | install | Function | | Installer | | description | String | 'No description' | Description | | created | Function | () => {} | Calling after duck created | | getDetails | Function | () => null | Custom Details Snapshot |
Create a component directly,
const Duck = require('@or-change/duck');
Duck({
id: 'com.orchange.duck.demo',
components: [
{
id: 'com.example.duck.literal',
name: 'DirectSample',
install(injection) {
// Append a dependence named 'foo'
injection.foo = function () {
return 'bar';
}
},
}
]
}, ({ foo }) => {
console.log(foo());
// => 'bar'
});
The example is just want to tell developers that create a component by literal is be allowed. There is NO magic about component. Everyone can do everything in this pattern. [RECOMMANDED] Defining a factory to build a component instance like a provider can make the code clear and maintainable. Many native components have been defined in @or-change/duck
. Enjoy them!
Simple web application example,
const http = require('http');
const Duck = require('@or-change/duck');
Duck({
id: 'com.orchange.duck.demo',
components: [
Duck.Web()
]
}, ({ Web }) => {
// Access Web.Application.Default to get the default application factory.
// Call the factory then return the request listener instance of application.
// The "Default" Application is provided by Web Component default options.
http.createServer(Web.Application.Default()).listen(8080);
});
// Now, open your browser and navigate to http://localhost:8080
/**
{
"meta": {
"name": "Default Product Name",
"namespace": "",
"version": "0.0.0",
"description": "No descrition"
},
"components": [
{
"id": "com.oc.duck.web",
"name": "WebApplication",
"description": "Used to guide developer to create a web application.",
"details": null
}
]
}
*/
Native Components
- Web - How to build a web application product?
- Webpack - Manage webpack configs.
- Datahub - Use data middle layer & define models.
- Electron6 - How to build a integrated Electron application?
- Log //TODO - Build & manage log channel.
Injection
Injection is the core function to manage dependencies regularly. Dependencies could be in any form. After product dependence
injection will be freezed (see lifecycle). Setting new dependence will become invalid.
Instance
An injection is use to set, get, transmit and manage dependencies to everywhere in a product. Each duck instance will create only one injection for itself.
About injection, some facts MUST be known,
- Cannot get a dependence NOT existed.
- Cannot set a new dependence override a existed.
- The injection of a duck will be freezen after duck created.
- Inline Dependences - There are 2 dependencies on injection when created.
injection.injection
reference itself for convenience.