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_updated
processes messageobject.metadata.updated
- method
#on_object_deleted
processes 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