Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/jedld/resugan

simple, powerful and unobstrusive event driven architecture framework for ruby
https://github.com/jedld/resugan

event-driven eventbus

Last synced: about 2 months ago
JSON representation

simple, powerful and unobstrusive event driven architecture framework for ruby

Awesome Lists containing this project

README

        

[![Gem Version](https://badge.fury.io/rb/resugan.svg)](https://badge.fury.io/rb/resugan) [![CircleCI](https://circleci.com/gh/jedld/resugan.svg?style=svg)](https://circleci.com/gh/jedld/resugan)

# Resugan

Simple, powerful and unobstrusive event driven architecture framework for ruby. This gem provides
a base framework in order to build a more powerful event based system on top of it. Events cuts across multiple objects and allows you to cleanly separate business logic to other cross cutting concerns like analytics and logging. Multiple events are consolidated allowing you to efficiently batch related operations together.

Also allows for a customizable backend which enables the use of various evented queuing mechanisms
like redis queue, amazon SQS with minimal changes to your code that generates the events.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'resugan'
```

And then execute:

$ bundle

Or install it yourself as:

$ gem install resugan

## Basic Usage

Register listeners, using :

```ruby
_listener :event1 do |array_of_params|
puts "hello! event 2 has been called!"
end

_listener :hay do |array_of_params|
puts "hello! someone said hay!"
end
```

Listeners are basically code that listens to an event, in this case :event1 and :hay.
an array of params equal to the number of times that specific event was captured
will be passed. So if :event1 was called twice, array_of_params will contain 2 elements.

Generate events using _fire and wrap them in a resugan block:

```ruby
resugan {
puts "I am now going to generate an event"

_fire :event2

_fire :hay

_fire :bam, { some_param: 'param' } # you can pass hashes to add meta to the event
}
```

The _fire method is available inside all objects, however the events won't be
collected unless within the context of a resugan block. The idea is that you
can prepackage a library that fires those events but won't actually
get used until someone specifically listens for it.

Note that events don't have to be fired at the top level of the block, even objects
used inside the block can invoke fire to generate an event.

The two events should fire and should print:

```ruby
hello! event 2 has been called!
hello! someone said hay!
```

Note that your listener will be executed once even if an event has been fired
multiple times. However params will contain the payload of both events. This allows you to batch together requests and efficiently dispatch them as a group.

# Object helpers

Helpers are available to make listening firing events a little bit cleaner:

```ruby
class TestObject
include Resugan::ObjectHelpers
end
```

This basically allows for the attach_hook to be available

```ruby
class TestObject
include Resugan::ObjectHelpers

def method2
_fire :event1
end

def method3
_fire :event2, param1: "hello"
end

attach_hook :method2
attach_hook :method3, namespace: "namespace1"
end
```

What this does is it essentially wraps the specified methods inside a resugan block.

Please see spec/resugan_spec.rb for more examples and details.

## namespaces

Resugan supports namespaces, allowing you to group listeners and trigger them separately

```ruby
_listener :event1, namespace: "group1" do |array_of_params|
puts "hello! event 2 has been called!"
end

_listener :event1, namespace: "group2" do |array_of_params|
puts "hello! someone said hay!"
end

_listener :log, namespace: %w(group1 group2) do |array_of_params|
array_of_params.each {
puts "listener that belongs to 2 namespaces"
}
end

resugan "group1" do
_fire :event1
_fire :log
end

resugan "group2" do
_fire :event1
_fire :log
end
```

Behavior is as expected. Events under group1 will only be handled by listeners under group1 and so on.

The above should print:

```
hello! event 2 has been called!
listener that belongs to 2 namespaces
hello! someone said hay!
listener that belongs to 2 namespaces
```

### Behavior of nested resugan blocks

Resugan will by default only resolve events at the outermost resugan context of
a namespace.

```ruby
_listener :event1 do |array_of_params|
puts "hello! event 1"
end

_listener :event2, namespace: "group2" do |array_of_params|
puts "hello! event 1"
end

resugan {
_fire :event1

resugan {
_fire :event2
}
}
```

If there are nested resugan blocks note that event dispatch will occur at the
outermost context. Output will be:

```
hello! event 1
hello! event 2
```

To force the innermost block to immediately resolve, use resugan! instead (or set Kernel.config.reuse_top_level_context = false globally):

```ruby
resugan {
_fire :event1

resugan! {
_fire :event2
}
}
```

Since the inner block will be resolved first, Output will be:

```
hello! event 2
hello! event 1
```

## Unique Listeners

the _listener always creates a new listener for an event, so if it so happens that
the code that creates those listeners gets executed again it will create another one.
if you want to make sure that listener only gets executed once you can pass an id
option:

```ruby
_listener :event1, id: 'no_other_listener_like_this' do |array|
# some code that gets executed
end
```

Or you can use the _listener! form which make sure a certain block is limited to
only a single instance.

```ruby
2.times do |i|
_listener! :event1 do |array|
# There will be only one instance of this listener no matter how many times it is defined
end
end
```

## Customizing the Event dispatcher

The way events are consumed is entirely customizable. You can register your own event dispatcher:

```ruby
class MyCustomerDispatcher
def dispatch(namespace, events)
events.each do |k,v|
Resugan::Kernel.invoke(namespace, k, v)
end
end
end
```

You need to implement your own dispatch method, captured events are passed as
parameters.

You can then set it as the default dispatcher:

```ruby
Resugan::Kernel.set_default_dispatcher(MyCustomerDispatcher)
```

Or assign it to a specific namespace:

```ruby
Resugan::Kernel.register_dispatcher(MyCustomerDispatcher, 'CustomGroup')
```

This allows you to use various queue backends per namespace, like resugan-worker for example.

### Debugging

Sometimes you need to track where events are fired. You can do so by enabling line tracing:

```ruby
Resugan::Kernel.config do |c|
c.line_trace_enabled = true
end
```

Line source should now be passed as params everytime you fire an event. You can also
view it by dumping a resugan context.

```ruby
puts(resugan {
_fire :event1
}.dump)
```

```ruby
{:event1=>[{:params=>{:_source=>"/Users/jedld/workspace/resugan/spec/resugan_spec.rb:144:in `block (5 levels) in '"}}]}
```

### Using Resugan::Engine::MarshalledInlineDispatcher

By default, resugan uses the Resugan::Engine::InlineDispatcher as the default dispatcher for
all namespaces. For performance reasons, params passed to the _fire method are passed as is, but there are
times when you want to simulate params that are passed using JSON.parse as is the case
when using a custom dispatcher that uses redis (see resugan-worker). In this case you may set MarshalledInlineDispatcher
as the default dispatcher for test and development environment instead (e.g. rails):

```ruby
Resugan::Kernel.set_default_dispatcher(Resugan::Engine::MarshalledInlineDispatcher) if Rails.env.development? || Rails.env.test?
```

Related Projects
=================

Below are projects that extend resugan.

### Resugan Worker

A project that wraps resugan listeners to be consumed using an external worker. Think of this as a redis queue backend.
Can also be used as a sample on how to extend resugan.

https://github.com/jedld/resugan-worker

### Testing

RSpec helpers are available:

https://github.com/jedld/resugan-rspec

## Similar Projects

wisper (https://github.com/krisleech/wisper) - An excellent gem that focuses on a coupled pub-sub model. Though its global listeners somehow have the same effect though in a syntactically different way.

event_bus (https://github.com/kevinrutherford/event_bus) - Loosely coupled pub-sub similar to resugan

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/resugan. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.

## License

The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).