パッケージの詳細

@fabrix/spool-broadcast

fabrix-app321MIT1.6.103

Spool: broadcast for Fabrix to implement CQRS and Event Sourcing

html, broadcast, CQRS, event-sourcing

readme

spool-broadcast

Gitter NPM version Build Status Test Coverage Dependency Status Follow @FabrixApp on Twitter

Conventional Commits

:package: Broadcast Spool

A Spool that implements CQRS/Event Sourcing patterns, with extraordinary route pattern matching.

Spool-broadcast helps distribute your Fabrix Applications with a specialized broadcasting pattern over a PubSub and WebSockets.

Why?

Event sourcing your functional workflow makes building complicated or distributed apps easier. With Spool-broadcast, you can dispatch events over different broadcasters and domain lines and have the operations wrapped in a single ACID transaction that is easy to manage. This makes adding new operations to a sequence trivial and application development easier to distribute. It also measures the completion time for each operation so that you can find logical bottle necks while also validating the data transformations at every step.

Additionally, Spool-broadcast also has a very powerful retry engine, so that it can try and keep operations alive while it's waiting on third parties to complete without having to dump the entire command/event.

Spool-broadcast runs Commands, SAGAs, Events, Processors, and Socket Channels!

Install

You will need NPM or Yarn installed to install spool-broadcast (and fabrix)

$ npm install --save @fabrix/spool-broadcast

Broadcast has a few dependency Spools:

Joi, Errors, Sequelize, Realtime, Retry

$ npm install --save @fabrix/spool-realtime @fabrix/spool-joi @fabrix/spool-errors @fabrix/spool-sequelize, @fabrix/spool-realtime, @fabrix/spool-retry

Additionally, If you install the plugin sequelize-hierarchy, then it will turn the BroadcastEvent into a CTE, which is useful for debugging and creates a help table, broadcasteventancetors.

Configure

// config/main.ts
import { BroadcastSpool } from '@fabrix/spool-broadcast'
import { RealtimeSpool } from '@fabrix/spool-realtime'
import { SequelizeSpool } from '@fabrix/spool-sequelize'
import { ErrorsSpool } from '@fabrix/spool-errors'
import { JoiSpool } from '@fabrix/spool-joi'

export const main = {
  spools: [
    // ... other spools
    ErrorsSpool,
    JoiSpool,
    RealtimeSpool,
    SequelizeSpool,
    BroadcastSpool
    // ... other spools
  ]
}

Definitions

SAGA

A running pattern that acts as a smart circuit breaker. For example, when there is an error in the event loop, operations that we're called in the SAGA will be given a cancel command to "Reverse" what they did. In real life, this is something like where your operation books a flight, a hotel, and a car, if the car if the flight is not available, you would want to cancel the booking of the hotel and car.

Pipelines

An Event Emitter that runs Actions and Commands in a Sequence. They are for "Transaction Blocks", for example: when you want certain operations to happen in a sequence that have clear and determined seperations in transactions.

Hooks

Command listeners that will run before or after the SAGA. Once a command is requested, you may wish to do some validation on the command, imbue it with more data, or run some various logic or calculations. Hooks can be used before and after the SAGA, however, they are not reversed like operations in the SAGA are.

Processors

Event listeners that will trigger more events. When an event is dispatched, there may be commands that you want to correlate with the command. When a processor is called, it will return it's value before the next tick in the Broadcast Event loop. This is what allows for spool-broadcast to make exteremly in-depth trees of commands/events predictably.

Projectors

Event listener that will save data from an event into a projection. A projection are just an easy Read table(s) that make reading from aggregates faster and easier to understand.

Dispatchers

A dispatcher creates a secondary event from a primary event without running through the normal processes.

Aggregates

TODO

Channel

A list of socket subscribers that get notified when an event occurs. For example, you want people to know in a different application that something has happened on your application.

Broadcast Event Loop

Concepts

Spool-broadcast uses a transaction to make an "all or nothing" ACID transaction for the resulting Aggregate update. You should use the SAGA pattern in the command pipe to make non-acid transactions mid flight, or use Pipelines to create transaction blocks. This allows for complex trees of commands and events to be performed in a reliable and predictable way.

A full event flow example:

  • Create User (command)
    • Is User Unique (pre-hook)
    • Tell 3rd party that user is joining (saga)
      • Anything that fails after this point, Tell the 3rd party to redact that operation (saga reverse)
    • Add 3rd party response to User data (post-hook)
  • User is Created (event)
    • Add User's Profile in a database (projectors)
    • Add User to New Friends List (processor)
      • Create Friend List Entry (command)
    • Update User's friends list in the database (projectors)
    • Broadcast to a Channel that User is created (channel)

Configuration

export const broadcast = {
  /**
   * If broadcast should handle transactions, highly recommended true
   */
  auto_transaction: true,
  /**
   * Connection for eventually consistent events
   */
  connection: {
    /**
     * Connection information could also be passed via uri
     */
    uri: process.env.CLOUDAMQP_URL
  },

  /**
   * Profile for this Fabrix instance, this will only allow Broadcast in this profile to run.
   */
  profile: process.env.BROADCAST_PROFILE || 'development',
  /**
   * Broadcasters to run for profile definition
   * <profile>: [
   *   <Broadcast>
   * ]
   */
  profiles: {
    development: [
      'CartBroadcast',
      'CollectionBroadcast',
      'UserBroadcast'
    ],
  },
  /**
   * Add Special Configurations to the broadcaster such as tracing
   */
  broadcasters: {
    CartBroadcast: { trace: true }
  },
  /**
   * Pipeline subscriptions to Broadcasters
   * e.g.
   * <Pipeline>: {
   *   broadcasters: {
   *     <Broadcast>: {
   *       pipeline: {
   *         <Entry.point>: {
   *           config: {}
   *         }
   *       }
   *     }
   *   }
   * }
   */
  pipelines: {
    /**
    * The Name of the Pipeline Resource 
    */
    CollectionPipeline: {
      broadcasters: {
        /**
        * The name of the Broadcast to listend to 
        */
        CollectionBroadcast: {
          'Unique_Name': {
            'Collection.createCollection': {
              zip: {
                event_type: 'event_type',
                object: 'object',
                data: 'data'
              }
            },
            'Collection.findByPkCollection': {
              before: function (req, body, options) {
                body = {
                  params: {
                    channel_uuid: body.data.channel_uuid,
                    cart_uuid: body.data.cart_uuid
                  },
                  query: {}
                }
                return [req, body, { parent: options }]
              },
              // after: function(req, body, options) {
              //   console.log('BRK trial after 2', body.data)
              //   return [req, body, options]
              // },
              zip: {
                data: 'data'
              }
            }
          },
        }
      }
    },
  },


  channels: {
    TestChannel: {
      broadcasters: {
        /**
         * Broadcaster that the Test BroadcastChannel is listening to
         */
        TestBroadcaster: {
          /**
           * Events subscribed to
           */
          'test.created': {
           /**
            * Channel methods to run when event committed
            */
            created: {
              lifespan: 'eternal',
              config: {
                priority: 1,
                expects_input: 'TestModel'
              }
            },
          },
          'test.:crud': {
           /**
            * Channel methods to run when event committed
            */
            crud: {
              lifespan: 'eternal',
              config: {
                priority: 2,
                expects_input: 'TestModel'
              }
            },
            created2: {
              lifespan: 'ephemeral',
              config: {
                priority: 3,
                expects_input: ['TestModel', 'TestModel.list']
              }
            },
          },
        },
      }
    },
  },
  hooks: {
    /**
     * HookIns
     */
    TestHook: {
      broadcasters: {
        /**
         * Broadcaster that the Test BroadcastHookIn is hooked into
         */
        TestBroadcaster: {
          /**
           * Commands subscribed to
           */
          'create.test': {
           /**
            * Hook methods to run when command pattern is matched and dispatched
            */
            create: {
              lifecycle: 'before',
              config: {
                priority: 1,
                expects_input: 'TestModel',
                merge: true,
                expects_response: 'TestModel'
              }
            },
          },
          'create.:test_uuid.test': {
           /**
            * Hook methods to run when command pattern is matched and dispatched
            */
            create: {
              lifecycle: 'before',
              config: {
                priority: 1,
                expects_input: 'TestModel',
                merge: true,
                expects_response: 'TestModel'
              }
            },
            satisfy: {
              lifecycle: 'after',
              config: {
                priority: 1,
                expects_input: 'TestModel',
                merge: true,
                expects_response: 'TestModel'
              }
            },
          },
          '*.test': {
           /**
            * Hook methods to run when command pattern is matched and dispatched
            */
            shouldBeProper: {
              lifecycle: 'before',
              config: {
                priority: 1,
                expects_input: 'TestModel',
                merge: true,
                expects_response: 'TestModel'
              }
            },
          }
        }
      }
    }
  },
  processors: {
    /**
     * Processors
     */
    TestProcessor: {
      /**
       * ... some processor configuration if required
       */
      broadcasters: {
        /**
         * Broadcasters that the Test Processors are responding to
         */
        TestBroadcaster: {
          /**
           * Events subscribed to
           */
          'test.:test_uuid.created': {
            /**
             * Processor methods to run when event pattern is matched and dispatched
             */
            update: {
              consistency: 'strong',
              config: {
                priority: 1,
                expects_input: 'TestModel',
                dispatches_command: 'update.test.:test_uuid',
                expects_response: 'TestModel',
                expects_output: 'TestModel',
                data: {
                  merge: true,
                }
              }
            },
          },

          'test.created': {
            /**
             * Processor methods to run when event pattern is matched and dispatched
             */
            update: {
              consistency: 'strong',
              config: {
                priority: 1,
                expects_input: 'TestModel',
                dispatches_command: 'update.test.:test_uuid',
                expects_response: 'TestModel',
                expects_output: 'TestModel',
                data: {
                  merge: true,
                }
              }
            },
          },
        },
      }
    }
  },
  projectors: {
    /**
     * Projectors
     */
    TestProjector: {
      broadcasters: {
        /**
         * Broadcaster that the Test Projectors are responding to
         */
        TestBroadcaster: {
          'test.:crud': {
            /**
             * Projector methods to run when event pattern is matched and dispatched
             */
            myMethod: {
              consistency: 'strong',
              config: {
                priority: 3,
                expects_input: 'TestModel',
                data: {
                  merge: true,
                },
                expects_output: 'TestModel'
              }
            },
            myLoggerMethod: {
              consistency: 'eventual',
              config: {
                priority: 3,
                expects_input: 'TestModel',
                data: {
                  merge: true,
                },
                expects_output: 'TestLoggerModel'
              }
            },
          },
          'test.:test_uuid.created': {
            /**
             * Projector methods to run when event pattern is matched and dispatched
             */
            created: {
              consistency: 'strong',
              config: {
                priority: 1,
                expects_input: 'TestModel',
                data: {
                  merge: true,
                },
                expects_output: 'TestModel'
              }
            },
            logger: {
              consistency: 'eventual',
              config: {
                priority: 3,
                expects_input: 'TestModel',
                expects_output: 'TestLoggerModel'
              }
            },
          },

          'test.:test_uuid.test.:test_uuid.created': {
            /**
             * Projector methods to run when event pattern is matched and dispatched
             */
            created: {
              consistency: 'strong',
              config: {
                priority: 1,
                expects_input: 'TestModel',
                data: {
                  merge: true,
                },
                expects_output: 'TestModel'
              }
            },
            logger: {
              consistency: 'eventual',
              config: {
                priority: 3,
                expects_input: 'TestModel',
                expects_output: 'TestLoggerModel'
              }
            },
          },

          'test.*': {
           /**
            * Projector methods to run when event pattern is matched and dispatched
            */
            wild: {
              consistency: 'eventual',
              config: {
                priority: 255,
                expects_input: '*',
                expects_response: '*',
                expects_output: '*'
              }
            },
          },
        },
      }
    }
  }
}

Usage

BroadcastChannel

Channels are useful for letting other applications listen to your application over sockets. This is a useful pattern in micro services, but also doing things like realtime applications.

BroadcastPipeline

Pipelines are useful for separating out your logic into transactional blocks. Complicated command/event cycles in a single transaction can quickly add load to your database, so having clear lines to separate the transactions is paramount for weighty commands. Pipelines are also useful for gathering a projected view after running a series of command/events.

BroadcastHook

Hooks are useful when you want to run operations on a command before, during, and after the SAGA before a command is dispatched as event.

BroadcastProcessor

Processors are useful when you want to dispatch a subsequent command when an event happens. Each processor makes a child transaction for the command/event sequence it dispatches so that it can also rollback if something later in the sequence fails.

BroadcastProjector

Projectors are useful for saving event data in a useable way. For example, a "User Created" event was dispatched, so now it might be nice to save their PII encrypted in one table, and their other details in a different table. You can easily do this with multiple projectors on the same event.

BroadcastDispatcher

Occasionally, you may need to multi-project when a projection is run and trigger side-events. Dispatchers let you trigger these without running a processor for a new event. For example, an event happens and updates an aggregate or projection, but you have other listeners that don't need a fully new event to respond. This is different from a processor which will dispatch a new command, instead, a dispatcher is a "side effect" of an event that contains at least some data from the original event but has a different event id.

Contributing

We love contributions! Please check out our Contributor's Guide for more information on how our projects are organized and how to get started.

Release Instructions

When the master is tagged with a release, it will automatically publish to npm, updates the Changelog and bumps the version. Fabrix uses the standard-version library to manage it all.

To run a patch release:

npm run release -- --release-as patch

and then commit to master. git push --follow-tags origin master

You can also test the release by running

npm run release -- --dry-run --release-as patch

License

Usage is currently under the MIT License

TLDR; In plain language, do what ever you want with library as long as you keep the license in your code, give credit to the authors, and you accept all the responsibility!

If you are making money using this library, consider sharing some of that sweet cheddar with the authors, they work really hard and would seriously appreciate it :smile:

更新履歴

Changelog

All notable changes to this project will be documented in this file. See standard-version for commit guidelines.

1.6.103 (2020-07-20)

Bug Fixes

  • disallows null in types, continues correlation and explain (a0ed4ca)

1.6.102 (2020-07-20)

Features

  • lays ground work for explain and adds correlation_type (2ab5438)

1.6.100 (2020-07-06)

Features

  • log out full message on redelivery (d900b99)

1.6.99 (2020-06-09)

Bug Fixes

  • only allow keys that have actually changed to be added to previous (22b698f)

Features

  • adds generateEventMetadata for dispatchers (2d0cd86)

1.6.98 (2020-05-29)

Bug Fixes

  • fixes dispatchers event_type patterns (73868bc)

1.6.97 (2020-05-28)

Bug Fixes

1.6.96 (2020-05-28)

1.6.95 (2020-05-15)

Features

1.6.94 (2020-04-22)

Bug Fixes

  • fixes staging on private objects (644f4da)

1.6.93 (2020-04-21)

Features

  • always runs before and after stage (cfc6e37)

1.6.92 (2020-04-21)

Features

1.6.91 (2020-04-20)

Features

1.6.90 (2020-04-16)

Features

  • adds synthetic data types (b78cf8e)

1.6.89 (2020-04-13)

Features

  • adds previously to event metadata (824520c)

1.6.88 (2020-04-06)

Bug Fixes

1.6.87 (2020-04-06)

Features

  • only applies updatedAt if changes and createdAt if new (13bc225)

1.6.86 (2020-04-04)

Bug Fixes

  • fixes change logger to return strings on change and only false if fail (774b592)

1.6.85 (2020-04-03)

Bug Fixes

  • fixes saveOptions to only return fields in the object schema (2cc4965)

1.6.84 (2020-04-02)

Features

  • adds approveChanges (09d2a37)
  • adds helper to command.apply (8d1aa4d)

1.6.83 (2020-04-01)

Features

1.6.82 (2020-04-01)

Features

  • fixes adding the broadcaster to projectors/processors (82c5840)

1.6.81 (2020-04-01)

Bug Fixes

  • fixes can't use _options really, they should not be read/edited outside (503427f)

1.6.80 (2020-04-01)

Bug Fixes

  • fixes already reloaded data. (4abbdc9)

1.6.79 (2020-03-31)

Features

  • add projection events, and changes (7d5de0f)

1.6.78 (2020-03-25)

Features

  • adds better change detection in command (c3d1a08)

1.6.77 (2020-03-12)

Bug Fixes

Features

  • fixes previous versus change (7912ba6)
  • improves commands and testing (8687e1a)
  • make command easier to reconcile (4ac4dbd)
  • makes saga hooks apply changes (4d3b9ef)
  • makes the command not call toJSON (d93a216)
  • records changes to data, fixes apply (30f8d17)
  • removes no longer needed warning (8c4bdde), closes #8
  • removes ugly sequelize hack (ab4949e)
  • updates "prehooks" to "hooks" to better describe them as saga hooks (e772447)

1.6.76 (2020-03-05)

Features

  • makes zipping more rational (8c37f59)

1.6.75 (2020-03-05)

Bug Fixes

1.6.74 (2020-03-05)

1.6.73 (2020-03-04)

1.6.72 (2020-03-04)

Features

  • adds pipeline to options and emits steps as subprogress (a6b2179)

1.6.71 (2020-03-04)

Bug Fixes

  • fixes children traces when disabled, adds multi broadcaster test starter (3d96aea)

1.6.70 (2020-03-04)

Features

  • disable tracers by default (b6dfdaa)
  • uses bluebird from new Promises (ae4fcae)

1.6.69 (2020-03-02)

Features

  • acttually unnest children (d3b6021)

1.6.68 (2020-03-02)

Features

  • better logging for tracer (a2ed99d)

1.6.67 (2020-03-02)

Features

1.6.66 (2020-02-24)

Features

1.6.64 (2020-02-19)

Features

  • adds reloads to resolvers, and starts moving data, metadata handlers (bfd0068)

1.6.63 (2020-02-15)

Features

  • adds multi version joi validator (2bb26a2)

1.6.62 (2020-02-14)

Features

1.6.61 (2020-02-14)

Bug Fixes

  • ensures commands validators are fully compliant javascript objects (cdf62a2)

1.6.60 (2020-02-11)

Features

  • runs eventual events correctly as single message entities. (f842910)

1.6.59 (2020-02-11)

Bug Fixes

1.6.58 (2020-02-11)

Features

  • return promise for runProjector (d7f6f55)

1.6.57 (2020-02-11)

Features

  • adds promises to ack, nack, reject (8c5ce1a)

1.6.56 (2020-02-11)

Features

1.6.55 (2020-02-10)

Features

  • fix resolves, and catch completely unhandeled eventual errors (b1e773b)

1.6.54 (2020-02-10)

Bug Fixes

  • fixes active_broadcasts not getting cleared. (d9db1f7)

Features

  • allows eventual processors (6c15e9b), closes #8

1.6.53 (2020-02-05)

Features

  • proves that multiple eventual events will run (aece923)

1.6.52 (2020-02-04)

Features

  • adds handeling for wildcards (a01b0a6)

1.6.51 (2020-02-03)

Features

  • adds event ancestors placeholder table. (d14d403)

1.6.50 (2020-02-02)

Features

  • adds after transaction for subscribers (471c30b)

1.6.49 (2020-01-30)

Bug Fixes

  • fixes broadcast entity to use correct values (591b797)
  • make projector and processor run the same way. (105e342)

1.6.48 (2020-01-30)

Features

  • starts readability and metadata reflection (90c49ab)

1.6.47 (2020-01-27)

Bug Fixes

  • warns user when using an eventual processor (0fe94c5)

1.6.46 (2020-01-27)

Features

1.6.45 (2020-01-23)

Bug Fixes

  • reduces overhead of publish to client by not including _x (b31f72b)

1.6.44 (2020-01-23)

Features

  • add some warnings to nacking (01677e2)

1.6.43 (2019-11-06)

Bug Fixes

  • fixes command changes for lists (6d21738)

1.6.42 (2019-11-06)

Features

  • adds an apply method for change detection (26f0570)

1.6.41 (2019-11-05)

Bug Fixes

  • don't call toJSON as it can be destructive (3829e91)

1.6.40 (2019-11-05)

Features

  • add changes for new records (3bafdf5)

1.6.39 (2019-11-05)

Bug Fixes

1.6.38 (2019-11-05)

Features

  • adds applied changes to commands and metadata (8819971)

1.6.37 (2019-11-04)

Bug Fixes

1.6.36 (2019-11-04)

Features

1.6.35 (2019-11-04)

Features

  • add channelsubscriber table (b6acecb)

1.6.34 (2019-11-02)

Features

  • adds event params for arrays and tests pipelines (4d49b52)

1.6.33 (2019-10-30)

Bug Fixes

1.6.32 (2019-10-30)

Features

  • makes map of command prehooks (6852d73)

1.6.31 (2019-10-29)

Bug Fixes

  • fixes processRequest merge (0f820d5)

1.6.30 (2019-10-29)

Features

1.6.29 (2019-10-29)

Bug Fixes

  • fixes case with n-cases of params (2953413)

1.6.28 (2019-10-29)

Bug Fixes

1.6.27 (2019-10-28)

Bug Fixes

  • fixes possible manager mismatching (f234c78)

1.6.26 (2019-10-28)

Features

  • starts handeling eventual redeliverys (623cabe)

1.6.25 (2019-10-28)

Bug Fixes

1.6.24 (2019-10-28)

Features

  • adds manager to publisher as well (377821f)

1.6.23 (2019-10-27)

Bug Fixes

1.6.22 (2019-10-27)

Features

  • adds managers to eventual events and patterns to events (1fabcaa)

1.6.21 (2019-10-26)

Features

  • pattern match on command_type and event_type by default (1a2c944)

1.6.20 (2019-10-25)

Bug Fixes

  • fixes multiple managers firing the same publisher event (bf1b591)

Features

  • adds validator pattern matching for saga (e360a1f)

1.6.19 (2019-10-25)

1.6.18 (2019-10-25)

Bug Fixes

  • fixes broadcastSeries maps (bad4fd6)

1.6.17 (2019-10-25)

Bug Fixes

  • fixes routing of events/commands (a33ac2b)

1.6.16 (2019-10-25)

Features

  • add app extensions to make it easier to reconcile. (f4a0155)

1.6.15 (2019-10-24)

Features

  • adds pattern matching to events and commands! (dbb682f)
  • renames libraries to make them easier to distinguish in context (baed23a)

1.6.14 (2019-10-24)

Features

1.6.13 (2019-10-23)

Bug Fixes

  • fixes undefined channels (07c73ce)

1.6.12 (2019-10-23)

Features

  • starts BroadcastChannel (b2392da)

1.6.11 (2019-10-18)

1.6.10 (2019-10-18)

Features

  • adds in spool-realtime (9180763)
  • adds spool-errors, updates express tests (4afeb2e)
  • starts realtime intergration (785b3db)

1.6.8 (2019-09-24)

Features

  • exports extendable models (edaed7d)

1.6.7 (2019-09-24)

Features

1.6.6 (2019-09-24)

Bug Fixes

  • binary: exports of types (ae787c2)

1.6.5 (2019-09-23)

Features

  • uses custom binary type (4be2ffa)

1.6.4 (2019-09-18)

1.6.3 (2019-09-18)

Bug Fixes

1.6.2 (2019-09-17)

1.6.1 (2019-09-17)

Features