Polyn

Ruby

Polyn is a dead simple service framework designed to be language agnostic while and providing a simple, yet powerful, abstraction layer for building reactive events based services. It is heavily inspired by Akka and Moleculer, and attempts to closely follow the Reactive Manifesto by adhering to the following principles:

  1. Follow the principle “do one thing, and one thing well” in defining service boundaries
  2. Isolate the services
  3. Ensure services act autonomously
  4. Embrace asynchronous message passing
  5. Stay mobile, but addressable
  6. Design for the required level of consistency

Polyn implements this pattern in a manner that can be applied to multiple programming languages, such as Ruby, Elixir, or Python, enabling you to build services that can communicate regardless of the language you use.

Rather than defining its own event schema, Polyn uses Cloud Events and strictly enforces the event format.

Installation

Add this line to your application's Gemfile:

gem 'polyn'

And then execute:

$ bundle install

Schema Creation

In order for Polyn to process and validate event schemas you will need to use Polyn CLI to create an schemas codebase. Once your schemas codebase is created you can create and manage your schemas there.

Configuration

Use a configuration block to setup Polyn and NATS for your application

domain

The Cloud Event Spec specifies that every event "SHOULD be prefixed with a reverse-DNS name." This name should be consistent throughout your organization.

source_root

The Cloud Event Spec specifies that every event MUST have a source attribute and recommends it be an absolute URI. Your application must configure the source_root to use for events produced at the application level. Each event producer can include its own source to append to the source_root if it makes sense.

Polyn.configure do |config|
  config.domain = "app.spiff"
  config.source_root= "orders.payments"
end

Usage

Publishing Messages

Use Polyn.publish to publish new events to the server

require "nats/client"
require "polyn"

nats = NATS.connect
polyn = Polyn.connect(nats)

polyn.publish("user.created.v1", { name: "Mary" })

Add :source to make the source of the event more specific

polyn.publish("user.created.v1", { name: "Mary" }, source: "new.users")

You can also include options of :header and/or :reply_to to passthrough to NATS

Consuming a Stream

require "nats/client"
require "polyn"

nats = NATS.connect
polyn = Polyn.connect(nats)

psub = Polyn.pull_subscribe("user.created.v1")

loop do
  msgs = psub.fetch(5)
  msgs.each do |msg|
    msg.ack
  end
end

Polyn assumes you've already used Polyn CLI to generate a consumer.

Add the :source option to pull_subscribe if your consumer name includes more than just the source_root. Polyn automatically finds the consumer name from the type you pass in. If your source_root was user.backend and the event type was user.created.v1 it would look for a consumer named user_backend_user_created_v1. If your consumer had a more specific destination such as notifications you could pass that as the :source option and the consumer name lookup would use user_backend_notifications_user_created_v1.

Subscribing to a message

require "nats/client"
require "polyn"

nats = NATS.connect
polyn = Polyn.connect

sub = polyn.subscribe("user.created.v1") { |msg| puts msg.data }

Polyn.subscribe will process the block you pass it asynchronously in a separate thread

Errors

For most methods, Polyn will raise if there is a validation problem. The subscribe method from nats-pure handles the callback in a separate thread and rescues any errors in an attempt to reconnect. If you want to get Polyn errors handled for subscribe you need to call nats.on_error { |e| raise e } on your connection instance to tell nats-pure how to handle those errors.

require "nats/client"

nats = NATS.connect
nats.on_error { |e| raise e }
polyn = Polyn.connect(nats)

sub = polyn.subscribe("user.created.v1") { |msg| puts msg.data }

Testing

Setup

Set an environment variable of POLYN_ENV=test or RAILS_ENV=test.

Add the following to your spec_helper.rb

require "polyn/testing"

Polyn::Testing.setup

Add the following to individual test files include_context :polyn

Test Isolation

Following the test setup instructions replaces most Polyn calls to NATS with mocks. Rather than hitting a real nats-server, the mocks will create an isolated sandbox for each test to ensure that message passing in one test is not affecting any other test. This will help prevent flaky tests and race conditions. It also makes concurrent testing possible. The tests will also all share the same schema store so that schemas aren't fetched from the nats-server repeatedly.

Despite mocking some NATS functionality you will still need a running nats-server for your testing. When the tests start it will load all your schemas. The tests themselves will also use the running server to verify stream and consumer configuration information. This hybrid mocking approach is intended to give isolation and reliability while also ensuring correct integration.

Observability

Tracing

Polyn uses OpenTelemetry to create distributed traces that will connect sent and received events in different services. Your application will need the opentelemetry-sdk gem installed to collect the trace information.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/SpiffInc/polyn-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Polyn project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.