selleo
Published in

selleo

Pub and Sub… what do those have in common ?

Essential RubyOnRails patterns —part 5: Pub/Sub

Foreword

What is Pub(lish)/Sub(scribe) pattern?

Why should we use Pub/Sub?

One of the simplest means to better implement SRP

Facilitates spaghetti-code reduction

Makes applications more modular

Renders breaking large tasks into smaller ones easier

Makes logging significant events in the system easy

Simplifies data migrations

When to use Pub/Sub?

  • medium-sized to large-sized
  • with easy-to-identify domains/modules that need to communicate with each other
  • integrating with one or more external systems, especially if those integrations are broad
  • huge, entangled monoliths in need of refactoring
  • applications in need of modularisation for scaling or other purposes

When NOT to use Pub/Sub?

  • small to medium sized
  • with a scarce amount of domains/modules or ones that are difficult to identify
  • not integrating with external systems
  • already implementing some specific/dedicated, well thought-through architectural solutions that would neither benefit nor play nice with Pub/Sub
  • prototypes that are just experimental, in which case Pub/Sub would just add unnecessary overhead

A word on nomenclature

BDD the Pub/Sub way

Identify problem domains

Identifying events

Identifying events’ payload

  1. Including irrelevant data — usually happens when planning payload for subscribers and can result in a payload that is irrelevant in the context of the given event. Usually, this should be included in a different event or retrieved with different means (i.e. fetched from the database when handling event)
  2. Including too much data — similar to the one above — only data that makes sense to be included in the scope of a given event should be included
  3. Including too little data — especially if missing data is necessary to describe the context of the event in full
  4. Improperly nesting data — in particular when some data that should be nested is not. A good example is including all of the object attributes as separate fields of event payload instead of wrapping them in the attributes field.
  • CustomerCreated: customer_id, customer_attributes
  • OrderNotificationSent: order_id, recipient_id, recipient_email
  • DoorOpened: door_id, keycard_uuid
  • NewOrderReceived: order_id, order_placed_at
  • PricingMismatchIdentified: product_id, local_price, remote_price

Time to TDD!

Integration testing

TDD Publisher(s)

TDD Event(s)

TDD Subscription(s)

TDD Subscriber(s)

Controlling flow with events

Bundle event handlers together

Process events synchronously

Process events asynchronously

Chain of events

Events bus

  1. Synchronous — in-process, handled fully synchronously — can be effective when we prefer the order of running event handlers to be preserved and when event handlers are lightweight. Also great as a starting point when pub/sub is used as a means for refactoring large, legacy applications.
  2. Asynchronous — handled in a separate process/thread within the same environment — non-blocking, usually based on some sort of queue (i.e. Faktory or Sidekiq). Can benefit from all features offered by the queue, like statistics, logging, retries, fine-grained error handling etc. Should be considered as the first choice.
  3. External — based on separate service like an external message broker and/or notification service (i.e. AWS SQS / AWS SNS, RabbitMQ). A step toward a solution working in microservice oriented architecture. Requires additional integration and may introduce a small performance overhead, so it should be introduced only when necessary. Still, it can be used as a secondary event bus within an application for handling some special cases / integrations.
  4. Logger — a simple medium when it is the user who acts as a subscriber of events. Such events data can be stored in a file, redirected to STDOUT or even some external service like Cloudwatch for further investigation. In some emergencies, such persisted events stores can even be used to restore or revert the state of the system.

Special uses of Pub/Sub

Refactoring monoliths

Auditing/Logging

Costs and risks of introducing Pub/Sub

Requires mindset change

Correlations visibility

Inflexibilities in changing payload structure

Pub/Sub and Ruby on Rails

Implementing Pub/Sub using Wisper gem

Other recommendations when using Pub/Sub in RoR

  1. Use “bang!” methods a lot and let stuff fail in runtime the correct way. This is especially useful for tracking problems when payloads of events are not validated and we forget to provide some identifiers when emitting those events. Adhering to this rule benefits the most in the context of ActiveRecord finders.
  2. Use external system ids, if those are unique — as Pub/Sub excels for integrating many external systems it is useful to use identifiers provided by those external systems even as primary keys of objects we persist locally. Further using them in event payloads will undoubtedly make it easier to audit events that were broadcasted in the system.
  3. Consider maintaining a clear separation of domains — regardless of the technique used, be it dependency injection, decorators or even CBRA / Packwerk, try to introduce interface segregation for models/objects that might share the same database table but are used in different domain contexts. Asking yourself the following question: “If I remove this whole domain from the application, will it still work?” can help you decide on whether it needs more isolation.

Summary

--

--

Experienced Ruby On Rails, Elixir, Node.js, Ember.js, React & React Native developers ready to join your team or build your next project.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store