Circle CI

StartHer

This gem provides the basic components required for each application involved in a microservice architecture.

It is based on Redis PubSub (slighlty improved).

Microservices components:

  • Publish message to a channel (with reliable support)
  • Subscribe to a channel
  • Automatic connection retry for Publish and Subscribe
  • Comes with a logger, Logstash, which can be re-used in the parent project
  • Microservices version discovery:

When starting the subscriber it subscribes to channel hb_ping (by default).

If it receives a request (ie a ping request) it replies on channel hb_pong with the following data (by default):

  • service name
  • version
  • microservice id (hostname,... not defined yet)
  • original request

Requirements

  • Ruby >= 2.2
  • Redis >= 2.8

Installation

gem 'start_her'

Configuration

require 'start_her'

StartHer.configure do |config|
  config.redis = {
    url: 'redis://localhost:6379',
    namespace: 'namespace'
    db: 1
  }
  config.logger = StartHer::Logger.instance # default
  config.logstash_url = 'redis://localhost:6379' # if using default logger in production
end

See lib/start_her/configuration.rb for all configuration variables.

Usage

Publisher

class MyClass
  include StartHer::Publisher

  def do_something
    # ...
    reliable_publish('channel_one', 'succeed') # or publish('channel_one', 'succeed') for non persistent events
  end
end

By default Redis has no persistent PubSub, so a simple mechanism is implemented to attempt to resolve this.

  • Each day a new persistent backlog is created for a given channel
  • Each backlog lives one week according to StartHer.config.backlog_ttl

Subscriber

bundle exec starther ./lib/path/to/subscriber.rb

Note: You need to put your subscriber.rb file into the lib directory.

class Subscriber
  include StartHer::Subscriber

  # Defaults are defined by StartHer::Subscriber::DEFAULT_OPTS
  subscriber_options channels: ['channel_one', 'channel_two']

  # Customization of error
  #
  # This block is not mandatory
  subscriber_error do |error|
    # do something with error
  end

  # Perform an action when Subscriber subscribes on a channel
  #
  # This block is not mandatory
  subscriber_on_psubscribe do |channel|
    # do something at channel subscription time
  end

  # Customization of heartbeat response
  # Each instance of Subscriber listens `hb_ping' channel and sends a response on `hb_pong'
  # channel for service heartbeat
  #
  # This block is not mandatory
  subscriber_heartbeat do |response|
    # do something with response
  end

  def process_message(channel, message)
    # your custom stuff here
  end
end

Heartbeat ping message format:

{
  "id": "643976ec-fc7c-4cd8-95bb-85a74f1987de",
  "generated_at": "2015-11-23 10:34:05 UTC",
  "data": {
    "id": "058e216d5ce76492b574b39584d0676f359207e05a6a1dd30f238fc5604a66b2",
    "generated_at": "2015-11-23 10:34:05 UTC",
    "service_name": "MyPingService",
    "version": "0.0.1"
  }
}

Testing

StartHer provides a few options for testing your micro-service.

Setup

StartHer allows you to dynamically configure the testing harness with the following methods:

require 'start_her/testing'
StartHer::Testing.fake! # fake is the default mode
# StartHer::Testing.disable!

RSpec.configure do |config|
  config.before(:each) do
    StartHer::Testing.clear_stubed_redis
  end
end

To query the current state, use the following methods:

StartHer::Testing.fake?
StartHer::Testing.disable?

Testing PubSub

require 'start_her/testing'

describe Subscriber do
  let(:publisher) do
    ->(chan, msg) { Object.new.extend(StartHer::Publisher).reliable_publish(chan, msg) }
  end
  let(:chan) { 'channel_one' }
  let(:msg) { 'my message' }

  describe '#process_message' do
    it 'processes message' do
      expect(subject).to receive(:process_message).with(chan, msg)
      publisher.call(chan, msg)
    end
  end
end

TODO

  • Create an option wich enables a subscriber to receive the same message only once (avoid message replay due to reliable push)
  • Implement scafolding for create a new project (like a gem) or for initialize a new Rails project

LICENSE

MIT

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request