{"id":18030578,"url":"https://github.com/edisonywh/rocketman","last_synced_at":"2025-04-10T01:12:09.918Z","repository":{"id":56892510,"uuid":"201648806","full_name":"edisonywh/rocketman","owner":"edisonywh","description":"🚀 Rocketman help build event-based/pub-sub code in Ruby","archived":false,"fork":false,"pushed_at":"2019-09-28T00:53:39.000Z","size":212,"stargazers_count":141,"open_issues_count":2,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-10T01:12:05.555Z","etag":null,"topics":["events","pubsub","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edisonywh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-08-10T15:32:26.000Z","updated_at":"2024-03-18T09:29:14.000Z","dependencies_parsed_at":"2022-08-21T01:20:55.687Z","dependency_job_id":null,"html_url":"https://github.com/edisonywh/rocketman","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edisonywh%2Frocketman","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edisonywh%2Frocketman/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edisonywh%2Frocketman/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edisonywh%2Frocketman/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edisonywh","download_url":"https://codeload.github.com/edisonywh/rocketman/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248137888,"owners_count":21053775,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["events","pubsub","ruby"],"created_at":"2024-10-30T09:14:38.738Z","updated_at":"2025-04-10T01:12:09.904Z","avatar_url":"https://github.com/edisonywh.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Rocketman\n![rocketman](./rocketman.jpg)\n\u003csub\u003e*yes, I know it says Starman on the image*\u003c/sub\u003e\n\u003e *🎶 And I think it's gonna be a long long time 'Till touch down brings me round again to find 🎶*\n\nRocketman is a gem that introduces Pub-Sub mechanism within your Ruby code.\n\nThe main goal of Rocketman is not to replace proper message buses like Redis PubSub/Kafka, but rather be a stepping stone. You can read more about the [rationale behind the project](https://github.com/edisonywh/rocketman#why-use-rocketman-rather-than-a-proper-message-bus-eg-redis-pubsubkafka) down below.\n\nAs with all Pub-Sub mechanism, this greatly decouples your upstream producer and downstream consumer, allowing for scalability, and easier refactor when you decide to move Pub-Sub to a separate service.\n\nRocketman also works without Rails.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'rocketman'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install rocketman\n\n## Usage\n\nRocketman exposes two module, `Rocketman::Producer` and `Rocketman::Consumer`. They do exactly as what their name implies. All you need to do is `include Rocketman::Producer` and `extend Rocketman::Consumer` into your code.\n\n### Producer\nProducer exposes one **instance** method to you: `:emit`. `:emit` takes in the event name and an optional payload and publishes it to the consumers. There's nothing more you need to do. The producer do not have to know who its consumers are.\n\n```ruby\nclass Producer\n  include Rocketman::Producer\n\n  def hello_world\n    emit :hello, payload: {\"one\" =\u003e 1, \"two\" =\u003e 2}\n  end\nend\n```\n\nNote that Producer emit events with threads that run in a thread pool. The default number of worker is 5, and the workers default to checking the job with a 3 seconds interval. You can tweak these to your liking, refer to the [`Configuration` section](https://github.com/edisonywh/rocketman#configuration) below for more informations.\n\n### Consumer\nConsumer exposes a **class** method, `:on_event`. `:on_event` takes in the event name, and also an additional block, which gets executed whenever a message is received. If an additional `payload` is emitted along with the event, you can get access to it in the form of block argument.\n\n```ruby\nclass Consumer\n  extend Rocketman::Consumer\n\n  on_event :hello do |payload|\n    puts \"I've received #{payload} here!\"\n    # =\u003e I've received {:payload=\u003e{\"one\"=\u003e1, \"two\"=\u003e2}} here!\n  end\nend\n```\n\nSimple isn't it?\n\n#### Consume events from external services (Relays)\n\nIf you want to also consume events from external services, you can do that too.\n\nRocketman has the concept of a `Relay`. The cool thing is, `Relay`s are just `Producer`s that understand how to relay messages from external services (like `Redis`) into Rocketman events.\n\nRocketman ships with a `Redis` relay, which you can use it like so (assuming you have Redis installed):\n\n```ruby\nrequire 'rocketman/relay/redis' # This is not required by default, so you need to explicitly require it.\nRocketman::Relay::Redis.new.start(Redis.new)\n```\n\n\u003e **NOTE**: You should always pass in a **new, dedicated** connection to `Redis` to the Redis relay. This is because `redis.psubscribe` will hog the whole Redis connection (not just Ruby process), so `Relay` expects a dedicated connection for itself.\n\nThat's it, the `Redis` relay service will now listen for events from external services on behalf of you, and then it'll push those events onto the internal `Registry`.\n\nIt'll translate the following:\n\n```\nredis-cli\u003e PUBLISH hello payload\n```\n\nto something understandable by your consumer, so a consumer only has to do:\n\n```ruby\non_event :hello do |payload|\n  puts payload\nend\n```\n\nNotice how it behaves exactly the same as if the events did not come from Redis :)\n\n**This pattern is powerful because this means your consumers do not have to know where the events are coming from, as long as they're registed onto `Registry`.**\n\nRight now, only `Redis` is supported, but it is extremely easy to add a `Relay` yourself since it's just a `Producer`. Checkout the implementation of `rocketman/relay/redis` for reference, upstream contributions for services are very welcomed too.\n\n## Persisting emitted events\n\nBy default, the events emitted from your app will be stored in an in-memory `Queue`, which will get processed by Rocketman threaded workers.\n\nHowever this also means that if your app dies with events still in your job queue, your emitted events which are stored in-memory will be lost.\n\nThat is obviously not desirable, so that's why **Rocketman ships with an option to use `Redis` as your backing storage mechanism.**\n\nAll you need to do is pass in a `Redis` connection to Rocketman. Refer to the [`Configuration` section below](https://github.com/edisonywh/rocketman#configuration) for more information.\n\n## Configuration\n\nHere are the available options to tweak for Rocketman.\n\n```ruby\n# config/initializers/rocketman.rb\n\nRocketman.configure do |config|\n  config.worker_count = 10 # defaults to 5\n  config.latency      = 1  # defaults to 3, unit is :seconds\n  config.storage      = Redis.new # defaults to `nil`\n  config.debug        = true # defaults to `false`\nend\n```\n\nCurrently `storage` only supports `Redis`, suggestions for alternative backing mechanisms are welcomed.\n\n`debug` mode enables some debugging `puts` statements, and also tweak the `Thread` workers to `abort_on_exception = true`. So if you have failing jobs, this is how you can figure out what's happening inside your workers.\n\n## Why use `Rocketman`, rather than a proper message bus (e.g Redis PubSub/Kafka)?\n\nIt is worth noting that `Rocketman` is not meant to be a replacement for the aforementioned projects -- both Redis PubSub and Kafka are battle-tested and I highly encourage to use them if you can.\n\n**But**, `Rocketman` recognizes that it's not an easy task to spin up an external message bus to support event-driven architecture, and that's what it's trying to do - to be a stepping stone for eventual greatness.\n\nMoving onto a event-driven architecture is not an easy task - your team has to agree on a message bus, the DevOps team needs the capacity to manage the message bus, and then what about clustering? failovers?\n\nSo what Rocketman offers you is that you can start writing your dream-state event-driven code **today**, and when the time comes and your team has the capacity to move to a different message bus, then it should be a minimal change.\n\n## Architecture\n\nHere's a very crude drawing of the architecture of Rocketman\n\n![erd](./erd.png)\n\n## Roadmap\n\nRight now events are using a `fire-and-forget` mechanism, which is designed to not cause issue to producers. However, this also means that if a consumer fail to consume an event, it'll be lost forever. **Next thing on the roadmap is look into a retry strategy + persistence mechanism.**\n\n~~Emitted events are also stored in memory in `Rocketman::Pool`, which means that there's a chance that you'll lose all emitted jobs. Something to think about is to perhaps move the emitted events/job queue onto a persistent storage, like Redis for example.~~ **Redis support is now available!**\n\nThe interface could also probably be better defined, as one of the goal of Rocketman is to be the stepping stone before migrating off to a real, proper message queue/pub-sub mechanism like Kafka. **I want to revisit and think about how can we make that transition more seamless.**\n\n## Development\n\nAfter 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.\n\nTo 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).\n\n## Contributing\n\nBug reports and pull requests are **very welcomed** on GitHub at https://github.com/edisonywh/rocketman, but before a pull request is submitted, **please first open up an issue** for discussion.\n\nThis 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.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Rocketman project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/rocketman/blob/master/CODE_OF_CONDUCT.md).\n\n## Why is it called Rocketman?\n\nUh.. well it's named after the song by Elton John, but really, it has nothing to do with an actual Rocketman.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedisonywh%2Frocketman","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedisonywh%2Frocketman","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedisonywh%2Frocketman/lists"}