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
#runwith 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.
-
#loggerdefaults 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:
-
ackonSuccess -
nakonFailure(nak_delay:is optional) -
termon 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