Project

leopard

0.0
A long-lived project that still receives updates
Leopard is a puma-like server for managing concurrent NATS ServiceApi endpoint workers
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

Leopard NATS ServiceApi Server

Leopard is a small framework for building concurrent NATS Service API workers. It uses Concurrent::FixedThreadPool to manage multiple workers in a single process and provides a minimal DSL for defining endpoints and middleware.

Features

  • Declarative endpoint definitions with #endpoint.

  • Declarative JetStream pull consumers with #jetstream_endpoint.

  • Grouping of endpoints with #group

  • Simple concurrency via #run with a configurable number of instances.

  • JSON aware message wrapper that gracefully handles parse errors.

  • Middleware support using #use.

  • Railway Oriented Design, using Dry::Monads for success and failure handling.

  • Dry::Configurable settings container.

  • #logger defaults to SemanticLogger (adjustable as the #logger= setting)

Requirements

  • Ruby >= 3.4.0

  • A running NATS server with the Service API enabled.

Installation

Add the gem to your project:

# Gemfile
gem 'leopard'

Then install it with Bundler.

$ bundle install

Usage

Create a service class and include Rubyists::Leopard::NatsApiServer. Define one or more endpoints. Each endpoint receives a Rubyists::Leopard::MessageWrapper object for each request to the NATS Service API endpoint that service class is is subscribed to (subject:, or name:). The message handler/callback is expected to return a Dry::Monads[:result] object, typically a Success or Failure.

class EchoService
  include Rubyists::Leopard::NatsApiServer

  endpoint :echo do |msg|
    Success(msg.data)
  end
end

Run the service by providing the NATS connection details and service options:

EchoService.run(
  nats_url: 'nats://localhost:4222',
  service_opts: { name: 'echo' },
  instances: 4
)

Middleware can be inserted around endpoint dispatch:

class LoggerMiddleware
  def initialize(app)
    @app = app
  end

  def call(wrapper)
    puts "received: #{wrapper.data.inspect}"
    @app.call(wrapper)
  end
end

EchoService.use LoggerMiddleware

JetStream Pull Consumers

Leopard can also bind JetStream pull consumers through the same middleware and Dry::Monads::Result handler contract used by request/reply endpoints.

class EventConsumer
  include Rubyists::Leopard::NatsApiServer

  jetstream_endpoint(
    :events,
    stream: 'EVENTS',
    subject: 'events.created',
    durable: 'events-created-worker',
    consumer: { max_deliver: 5 },
    batch: 5,
    fetch_timeout: 1,
    nak_delay: 2,
  ) do |msg|
    Success(msg.data)
  end
end

JetStream handlers receive the same Rubyists::Leopard::MessageWrapper as service endpoints. Leopard will:

  • ack on Success

  • nak on Failure (nak_delay: is optional)

  • term on unhandled exceptions

Each Leopard instances: worker creates its own pull subscription loop, so JetStream consumers scale with the same process-local concurrency model as the rest of the framework.

Development

The project uses Minitest and RuboCop. Run tests with Rake:

$ bundle exec rake ci

This task starts NATS JetStream through ./ci/nats/start.sh, waits for broker health, runs RuboCop and the test suite, and then stops the broker.

API documentation can be generated with:

$ bundle exec rake yard

Documentation coverage is enforced with:

$ bundle exec rake yard:verify

If you want to run the broker yourself, the same script can still be used directly:

$ ./ci/nats/start.sh

Conventional Commits (semantic commit messages)

This project follows the Conventional Commits specification.

To contribute, please follow that commit message format, or your pull request may be rejected.

License

MIT