message-in-a-bottle

2.2.1 • Public • Published

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

Dispatcher API Documentation

More docs and examples

Contributing

Open an issue for all feature requests and bug reports.

Package Sidebar

Install

npm i message-in-a-bottle

Weekly Downloads

2

Version

2.2.1

License

MIT

Unpacked Size

46.6 kB

Total Files

12

Last publish

Collaborators

  • nbaroni