message-in-a-bottle v2.2.1
message-in-a-bottle
message-in-a-bottle
is a small JavaScript library for organizing business logic.
It's an extension of Node's EventEmitter that adds a dispatcher and a plugin system.
Goals
- Easily tested business logic
- Easy communication
- Reusable code (plugins)
- Centralized error handling
- Easy debugging
Install
npm install message-in-a-bottle@next
Overview
message-in-a-bottle
is a dispatcher that provides inversion of control and dependency injection and
allows you to write code in a testable way. It is similar to front-end state management libraries,
except that it has no opinion on how you save state. You can expose your
persistence API to your functions via dependency injection. On the back-end, this
might be a database client. On the front-end, this could be an observable.
message-in-a-bottle
is a full-stack JavaScript communication library. It can be used on the front-end
, the back-end, or both. If used in the front-end and back-end, plugins can connect the two.
Plugins can also enable microservices to talk to each other.
Usage
Create the dispatcher
import { Dispatcher } from 'message-in-a-bottle'
// const { Dispatcher } = require('message-in-a-bottle')
export const dispatcher = new Dispatcher({
dependencies: {
db: myDbClient
}
})
dispatcher.on('error', err => {
console.error(err)
})
Group related business logic as roles
todo role
export function todoRole (ctx) {
ctx.addCommand('todo:create', createTodo)
}
export async function createTodo (ctx, todo) {
const user = await ctx.do('user:get', todo.userId)
if (!user.verified) throw new Error('Unauthorized')
return ctx.db.todos.create(todo)
...
}
user role
export function userRole (ctx) {
ctx.addCommand('user:get', getUser)
}
export function getUser (ctx, userId) {
return ctx.db.users.get(userId)
}
Add roles as plugins
dispatcher.use(userRole)
dispatcher.use(todoRole)
Recap
Plugin functions (todoRole
) and command handlers (createTodo
) have the same signature.
The first argument is a JS Proxy (more on that below). The rest of the arguments are custom,
and are passed when the plugin is added or the command is dispatched.
dispatcher.use(pluginFunction, arg1, arg2, arg3)
dispatcher.do('command', arg1, arg2, arg3)
About ctx
ctx
from the examples above is a JS Proxy. It gives preference first to any dependencies
that you've passed in, then to the dispatcher's API. Naming it ctx
is just the convention
in this documentation.
API
new Dispatcher(options)
addCommand(command, handler)
command argument
The command argument can be a string or object. If it's an object, it must have a $command
property.
Passing an object allows you to add dependencies and options. Options are prefixed with $
.
Options $catch and $throw are exclusive. If $catch is present, $throw will be disregarded.
Options and dependencies passed here will override those passed in the constructor.
command object
- $command String
- $catch String - When this command handler throws an error, catch the error and emit the
event indicated by the string value of the
$catch
option. - $throw String - When this command handler throws an error, emit an event indicated by the value of
the
$throw
option, and then re-throw the error. (default is 'warn') - $logArguments Boolean - attach the invocation arguments to the error (default is true)
- myDep - any custom dependency that should be available on the proxy in the handler
broadcast(eventName, ...args)
- eventName String
Same API as Node.js EventEmitter.emit. The difference is that broadcast
will emit the
events to other machines (via plugins). Use dispatcher.emit
to emit events on the same machine.
Use broadcast
to emit events locally and to other machines.
do(command, ...args)
Pass a string to call a command added with addCommand
. Pass a function to invoke that function
with the proxy object as the first argument. Pass an object to call a command added with addCommand
,
while also passing options or dependencies. See the command object from the addComand API.
Options and dependencies passed here will override those set in addCommand
or in the constructor.
use(plugin, ...args)
- plugin Function
plugin return value
A plugin can optionally return an Object that adheres to the interface for a plugin's API.
claimCommand(cmd)
- command String
When dispatcher.do(command)
is called with a string argument, and the command hasn't been added
with addCommand
, the dispatcher will loop through the plugins, allowing a plugin to claim the command.
The claimCommand
function receives the command (String) and returns a handler Function.
To not claim a command, the plugin should not return anything. Claiming a command allows plugins to
dispatch commands to other machines. Which commands a plugin should claim is configured when adding the plugin.
If a handler is on the same machine, a plugin should use addCommand
and not claimCommand
.
emit(eventName, ...args)
- eventName String
If a plugin's return value has an emit function, it will be called by the dispatcher.broadcast
method.
Plugin ecosystem
This repository has some plugin reference implementations. These are not yet tested and will be moved to individual repositories as they mature.
Contributing
V2 of message-in-a-bottle
is a work in progress. The core library is complete, but the API may change slightly.
Developing the plugin ecosystem is where most work remains. Open an issue for any bug reports or feature requests.
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago