message-in-a-bottle
message-in-a-bottle
is an extension of Node's EventEmitter that adds a dispatcher and a plugin system.
UPDATE
Version 3 of this library is published under the "next" tag. The API is simplified and fairly different than version 2. If you are just getting started with this library, start with version 3.
npm install message-in-a-bottle@next
If installing the next version, disregard the README here and use the one in the git repo.
Goals
- A pattern for business logic with inversion of control and dependency injection
- Same pattern for business logic on front-end and back-end
- Easy communication between machines (front-end to back-end and microservice to microservice)
- Reusable code (plugins)
- Centralized error handling
- Easy debugging
- Monolith/Microservices architecture flexability
Install
npm install message-in-a-bottle
Basic Usage
const { Dispatcher } = require('message-in-a-bottle')
function userRole ($) {
$.addCommand('user_get', getUser)
}
function getUser ($, id) {
return $.db.users.get(id)
}
function todoRole ($) {
$.addCommand('todo_add', addTodo)
}
async function addTodo ($, todo) {
await $.do('user_get', todo.author).catch(err => {
throw new Error(`Invalid author ${todo.author}`)
})
await $.db.todos.put({ ...todo, id: new Date().toISOString() })
}
const dispatcher = new Dispatcher({
dependencies: {
db: myDbClient
}
})
dispatcher.use(userRole)
dispatcher.use(todoRole)
dispatcher.on('error', err => {
console.error(err)
})
const todo = { status: 'INCOMPLETE', text: 'Send invitations', author: userId }
dispatcher.do({ $command: 'todo_add', $catch: 'error' }, todo)
Review
In the above code, there are 2 "roles". Although not necessary, the idea is that you group your
domain logic into roles. Roles are added as plugins. Plugins can be for local code, like above,
or they can be a transport plugin to connect to a dispatcher on another machine, allowing you to
dispatch commands to remote machines as if they were local. This design is heavily influenced
by Seneca, but message-in-a-bottle
can also be used on the front-end.
In the example, there's one "dependency", db
. The term
dependency is used a little loosely, but it's anything that you want to be available to functions
called by the dispatcher. Some of these are options, prefixed with $
, that influence the behavior
of the dispatcher. There are 3 opportunities to pass in dependencies:
- creating dispatcher - global dependencies that should be available everywhere
- adding command - dependencies for this command only, that are consistent with each call
- calling command - info like user-context or options like
$catch
Plugins and command handlers receive a Proxy as the first argument. This proxy gives you access to all the dependencies you've added, as well as all of the dispatcher methods. Dependencies passed in later, like at call time, will override those passed in earlier if the key is the same.
Commands above are called in 2 different ways. Normally, you'll use the form $.do('user_get', todo.author)
.
If you need to pass dependencies or options, you can pass an object instead,
such as $.do({ $command: 'todo_add', $catch: 'error' }, todo)
. This example uses the $catch
option,
which catches any error and emits an event. The string "error" indicates that an "error" event
should be emitted.
Plugins
Plugins are functions that are invoked with the dispatcher-proxy and any arguments that are passed
to the use
method; however, plugins can return an object adhering to an interface that allows
the dispatcher to interact with it. Mainly, a plugin can claim a command at invocation time, and listen
for events. The "role" plugins in the example above don't use this, but it enables transport plugins
to send commands and events to other machines.
Included plugins
message-in-a-bottle
ships with these plugins. All plugins work in back-end and front-end.
- DuplexMessages - A plugin for bi-directional messaging between machines, based on the Worker API. This can be used to connect a front-end dispatcher with a web-worker dispatcher.
- Logger - A plugin for logging
- WebSocketPlugin - Wraps the
DuplexMessages
plugin for WebSockets
There's no HTTP plugin because it wouldn't save much typing compared to the example above, and due to the variety of HTTP clients, server frameworks, and authentication strategies, it's better left to the user, or an ecosystem library.
Dispatcher API
More docs and examples
Contributing
Open an issue for all feature requests and bug reports.