Publish Subscribe Development Pattern - samvera/hyrax GitHub Wiki
< Back to Patterns
NOTE: At this writing, all links are to code in Release 3.0.1.
Publish-Subscribe
General Overview
This is often referred to as Pub-Sub.  Using the Pub-Sub approach, code will publish a message (e.g. metadata_updated).  There is a corresponding listener that subscribes to listen for that message.  The listener has a method that will execute some statements in response to the message.
See also Hyrax's Event Bus (Hyrax::Publisher).
Terminology
| Term | Description | 
|---|---|
| Publish (aka pub) | The act of publishing a message. | 
| Publisher | The infrastructure code that processes the pub-sub registration, listening for messages, and forwarding to the code implementing the response to events in listeners. | 
| Subscribe (aka sub) | Registering a listener to listen for a message in a registered Listener class. | 
| Message | A string message that is registered as an event in a Listener class. In our implementation, all Listener methods beginning with on_(e.g.on_object_metadata_updated) are registered as possible event messages (e.g.object.metadata.updated). | 
| Listener | A class registered to listen for specific messages.  It has methods that process each message.  The method follows naming conventions to match a message (e.g. object.metadata.updated) to the event method (e.g.on_object_metadata_updated) that processes it. | 
Hyrax Specifics
- Publisher (information only - you won't need to make any changes here)
- Which publisher to use is defined in Hyrax::Configuration.
- At this time, we are using Hyrax::Publisher as the publisher.
 
- Listener
- There are many listeners defined.
- The classes are defined in app/services/hyrax/listeners
- Listener classes are autoloaded in app/services/hyrax/listeners.rb
- Listeners are registered in config/initializers/listeners.rb with the publisher subscribing to the Listener.
- Listener defines a method with a name (e.g. on_object_metadata_updated) following the pattern for message names (e.g.object.metadata.updated) for each message it processes.
 
- Messages
- A message can be published from anywhere with Hyrax.publisher.publish('message.name', param: value)
 
- A message can be published from anywhere with 
An Example
- Listener MetadataIndexListener- registered in config/initializers/listeners.rb
- defined in app/services/hyrax/listeners/metadata_index_listener.rb
- processes 2 messages
- method #on_object_metadata_updatedprocesses messageobject.metadata.updated
- method #on_object_deletedprocesses messageobject.deleted
 
- method 
- both execute Hyrax.index_adapter.save(resource: event[:object])
 
- Publish a message
- Hyrax.publisher.publish('object.metadata.updated', object: saved, user: user)
 
General Observations
Multiple events can be published to process the changes that have happened. For example, when a work is created, two messages will be published...
  Hyrax.publisher.publish('object.deposited', object: curation_concern, user: user)
  Hyrax.publisher.publish('object.metadata.updated', object: curation_concern, user: user)
Transactions will publish events once a step or series of steps have successfully completed. For example, the above messages are only published if the save was successful. See lib/hyrax/transactions/steps/save.rb
Testing
The following is an example of testing that a published method was successfully called. You need to following:
- require 'hyrax/specs/spy_listener'
- define a listener
- subscribe to the listener before the test
- unsubscribe from the listener after the test
- test that the message was called
These requirements are seen in the following example...
require 'hyrax/specs/spy_listener'
describe "MyClass" do
  describe "#my_method that publishes a message" do
    let(:listener) { Hyrax::Specs::SpyListener.new }
    before { Hyrax.publisher.subscribe(listener) }
    after  { Hyrax.publisher.unsubscribe(listener) }
    it 'publishes a message' do
      # using object_metadata_updated message as an example; change to the actual published message
      expect { described_class.new.my_method }
        .to change { listener.object_metadata_updated&.payload }
    end
  end
end