Library / SDK
krisleech/wisper avatar
krisleech/wisper

Wisper: A Micro Pub-Sub Library for Ruby That Decouples Without a Framework

GitHub describes it as A micro library providing Ruby objects with Publish-Subscribe capabilities. The repository metadata lists Ruby as its primary language. This article stays within the project description and details documented in the GitHub repository README.

3,331 stars154 forksRubyLicense varies

At a glance

What is it?
Wisper is a small Ruby gem that adds publish-subscribe to plain objects. It targets hexagonal architectures and Rails apps that want to move away from ActiveRecord callbacks.
Who is it for?
Adopt Wisper if you build Ruby services or Rails apps that need a lightweight, framework-agnostic way to decouple business logic from side effects like email, logging, or statistics. Avoid it if you need a full event bus with persistence, retries, or complex routing, or if you prefer a formal event-sourcing setup.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Probably not. The repository last received commits 26 months ago, on August 15, 2024.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • krisleech/wisper3,331 ★ · +1
Star history
Repository2026-08-132026-10-22
krisleech/wisper3.3k3.3k

1 Stars in the last 70 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

What Wisper Solves and Who It Is For

Wisper is a Ruby gem that gives any object publish-subscribe capabilities. It is aimed at developers who want to separate core business logic from external concerns without pulling in a full event system. The README explicitly mentions hexagonal architecture and Rails apps that want an alternative to ActiveRecord callbacks and Observers. The intended user is someone who finds callbacks too coupled, but who does not want to migrate to a message broker. Wisper is not a framework. It is a single module you include. It was extracted from a Rails codebase, but it does not depend on Rails. That makes it usable in plain Ruby services, background jobs, or any object that needs to announce state changes.

The Mechanism: Publisher, Listener, and Runtime Subscription

The core is the Wisper::Publisher module. A class includes it and calls broadcast with an event name and any number of arguments. Any object can be a listener, as long as it implements a method matching the event name. Subscriptions happen at runtime, not at class definition time. You instantiate a publisher, call subscribe on it, and pass a listener object. The listener's methods are called synchronously by default. This is a key design choice: events are just method calls. There is no event object, no payload hash, no routing. The broadcast method is aliased as publish, so you can use either name. The publisher does not know who is listening. That is the decoupling. It also means the listener must implement the exact method name, and the publisher must spell the event name consistently. A typo in either side silently drops the event.

Getting It Running: Commands and Basic Subscription Patterns

Installation is a one-line Gemfile entry: gem 'wisper', '~> 3.0'. Then you include Wisper::Publisher in your class. The README shows a CancelOrder class that broadcasts cancel_order_successful or cancel_order_failed. To subscribe, you call subscribe on the publisher instance and pass a listener object. For blocks, you use on with the event name, and you can chain on calls. You can also subscribe to multiple events in one on call by passing extra event names. The block receives the broadcast arguments. There is a global subscription API: Wisper.subscribe(MyListener.new) registers a listener that receives every event it can respond to. You can scope global subscriptions to a publisher class with the scope option, passing a symbol, string, class, or array. Temporary global listeners work inside a block: Wisper.subscribe(listener) do ... end. These are threadsafe, but temporary ones only receive events on the same thread.

Asynchronous Handling and the Adapter Ecosystem

Wisper itself is synchronous. For async, you pass async: true to subscribe. That alone does nothing. You need an adapter. The README lists wisper-celluloid, wisper-sidekiq, wisper-activejob, wisper-que, and wisper-resque. Each adapter changes how the listener is invoked. The README warns that some adapters require the listener to be a class, not an instance, and that the event methods must be class methods. This is a real shift: the synchronous API encourages instance listeners, but the async world often wants class-level methods. That inconsistency can trip you up. Also, the adapter list is from the README, and some of those gems may be unmaintained. You have to check each one. The core gem does not include any async implementation, so you are dependent on a separate project for a common requirement.

Global Listeners and Temporary Scope: The Cross-Cutting Tool

Global listeners are a distinctive feature. Wisper.subscribe(MyListener.new) registers a listener that receives all broadcast events it can respond to. The README suggests this for statistics, indexing, caching, and logging. You can scope by publisher class, which is more precise. The scope option accepts a symbol, string, class, or array. You can also call subscribe directly on a publisher class, which is equivalent to a scoped global subscription. Temporary global listeners are scoped to a block and capture events from publishers you do not have direct access to. This is useful in controllers or service objects where you create a publisher inside a library you do not control. The global listener mechanism is threadsafe, but the temporary one is only same-thread. That is a meaningful limitation if you broadcast inside background jobs or threads.

Limitations and Failure Modes

The most explicit failure mode is the block return problem. The README warns: do not return from inside a subscribed block, because Ruby blocks behave like procs, and a return will prevent subsequent listeners from receiving their events. This is a subtle and dangerous bug. If you have three listeners and the first block returns, the other two get nothing. Another limitation is the lack of an event broker or persistence. If a listener is down, the event is lost. There is no replay, no dead-letter queue. For a micro library that is fine, but it means Wisper is not for critical messaging. Also, the README does not specify a license. The repository metadata says license unknown. That is a real adoption risk. You cannot legally use a library without knowing its license. Before using Wisper in a commercial product, you must find the actual license file or contact the maintainer.

Alternatives: What Else Does This Space Offer?

The main alternative is Ruby's built-in Observable module from the standard library. Observable uses add_observer and changed/notify_observers. It is synchronous and has no async adapters, no global listeners, and no scoping. Wisper is more ergonomic and more flexible. Another alternative is a full event bus like Rails' ActiveSupport::Notifications. That gives you a global namespace, subscriptions with patterns, and instrumentation. It is built into Rails, so there is no extra gem. But it is tied to ActiveSupport, and it does not give you per-publisher subscription as naturally. Wisper's approach is more object-oriented: the publisher is the subject, not a global channel. If you need async with retries and durability, you should look at a background job framework with its own eventing, like Sidekiq's built-in middleware or a dedicated event store. Wisper is not a replacement for those.

Maintenance, Upgrade Cost, and License Considerations

The repository shows no recent releases and no last push date, which raises questions about active maintenance. The README points to WisperNext and Ma as alternatives for greenfield applications. That is a strong signal that the original project is in maintenance mode. Upgrading from Wisper 2.x to 3.0 may involve changes, but the README does not list them. You have to read the changelog. The async adapter gems are separate projects, and their maintenance varies. The license is unknown, which is a blocking issue for many organizations. You should not assume it is MIT just because it is a Ruby gem. Check the actual repository files before using it. The core is small, so the upgrade cost is likely low, but the unknown license and unclear maintenance status are real costs.

Editorial conclusion

Adopt Wisper if you build Ruby services or Rails apps that need a lightweight, framework-agnostic way to decouple business logic from side effects like email, logging, or statistics. Avoid it if you need a full event bus with persistence, retries, or complex routing, or if you prefer a formal event-sourcing setup. Before adopting, verify that the async adapter you need (sidekiq, activejob, celluloid, etc.) is maintained and matches your background job infrastructure. Also check the block subscription caveat: returning from a block breaks delivery to subsequent listeners. Wisper is a pragmatic, small tool that does one thing well, but it leaves orchestration and reliability to you.

Frequently asked questions

What is krisleech/wisper?

It is a Ruby micro library that gives objects publish-subscribe capabilities. Any class including its publisher module can broadcast events, and listeners subscribe at runtime, with events delivered synchronously or asynchronously.

How do I install wisper and what version does it pin?

By adding the line gem 'wisper', '~> 3.0' to the application's Gemfile. The pessimistic constraint allows later 3.x releases while holding the major version, and the library states it was extracted from a Rails codebase but does not depend on Rails.

Does wisper handle asynchronous events itself?

No. Subscribing with async: true requires a separate adapter gem, and five are named: ones for Celluloid, Sidekiq, Active Job, Que and Resque, most of them maintained by other contributors. Depending on the adapter, a listener may need to be a class with class methods rather than an object with instance methods.

What licence is wisper released under?

The repository index records the licence as unknown and no licence file appears at the root of the tree, so the terms are not stated in the repository itself. The gem is published with a signing certificate, but that establishes provenance rather than terms.

Can wisper listeners be limited to certain publisher classes?

Yes. A scope can be given as a symbol, the class itself, a string, or an array of those, and the listener then receives events from all instances of the specified classes and their subclasses.

Official sources

  1. Official README
  2. Project repository
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/krisleech-wisper.svg)](https://hysenlabs.com/projects/krisleech-wisper)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/krisleech-wisper.svg)](https://hysenlabs.com/projects/krisleech-wisper)