Updates README.md

This commit is contained in:
Brusk Awat 2023-03-01 00:44:54 +03:00
parent 1eca27ff53
commit d34b3140a8
Signed by: broosk1993
GPG Key ID: 5D20F7E02649F74E

202
README.md
View File

@ -1,24 +1,212 @@
# Outboxable
TODO: Delete this and the text below, and describe your gem
The Outboxable Gem is tailored for Rails applications to implement the transactional outbox pattern. It currently only supports ActiveRecord.
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/outboxable`. To experiment with that code, run `bin/console` for an interactive prompt.
Please take into consideration that this Gem is **opinionated**, meaning it expects you to follow a certain pattern and specific setting. If you don't like it, you can always fork it and change it.
### Restrictions
1. When using RabbitMQ, it only publishes events to a ***topic*** exchange.
1. It assumes that you are using routing keys to publish to the topic exchange.
1. It publishes events in a background job using [Sidekiq](https://github.com/sidekiq/sidekiq). Therefore, you application must use Sidekiq.
1. It implements the [polling publisher pattern](https://microservices.io/patterns/data/polling-publisher.html). For that, it uses [](https://github.com/sidekiq-cron/sidekiq-cron) to check the unpublished outboxes every 5 seconds after the initialization of the application.
## Installation
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
Install the gem and add to the application's Gemfile by executing:
$ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
```ruby
$ bundle add outboxable
```
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
```ruby
$ gem install outboxable
```
Then run:
```shell
$ rails g outboxable:install
```
The command above will add a migration file and the Outbox model. You will need then to run the migrations:
```shell
$ rails db:migrate
```
## Usage
TODO: Write usage instructions here
The installation command above will also add a configuration file to your initializer:
```ruby
# This monkey patch allows you to customize the message format that you publish to your broker.
# By default, Outboxable publishes a CloudEvent message to your broker.
module Outboxable
module RabbitMq
class Publisher
# Override this method to customize the message format that you publish to your broker
# DO NOT CHANGE THE METHOD SIGNATURE
def to_envelope(resource:)
{
id: resource.id,
source: 'http://localhost:3000',
specversion: '1.0',
type: resource.routing_key,
datacontenttype: 'application/json',
data: @resource.payload
}.to_json
end
end
end
end
Outboxable.configure do |config|
# Specify the ORM you are using. For now, only ActiveRecord is supported.
config.orm = :activerecord
# Specify the message broker you are using. For now, only RabbitMQ is supported.
config.message_broker = :rabbitmq
# RabbitMQ configurations
config.rabbitmq_host = ENV.fetch('RABBITMQ__HOST')
config.rabbitmq_port = ENV.fetch('RABBITMQ__PORT', 5672)
config.rabbitmq_user = ENV.fetch('RABBITMQ__USER')
config.rabbitmq_password = ENV.fetch('RABBITMQ__PASSWORD')
config.rabbitmq_vhost = ENV.fetch('RABBITMQ__VHOST')
config.rabbitmq_event_bus_exchange = ENV.fetch('EVENTBUS__EXCHANGE_NAME')
end
```
The monkey patch in the code above is crucial in giving you a way to customize the format of the message that you will publish to the message broker. Be default, it follows the specs of the [Cloud Native Events Specifications v1.0.2](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md).
### General use-cases
The Outboxable Gem provides a general-purpose polymorphic model for creating outboxes. In order to maintain a transactional outbox, the changes that occur to a resource and the associated outbox must happen within the same transaction:
```ruby
ActiveRecord::Base.transaction do
book = Book.find(1)
book.update(title: 'Patterns of Enterprise Application Architecture', author: 'Martin Fowler')
Outbox.create(
routing_key: 'books.updated',
exchange: 'YOUR RABBITMQ EXCHANGE',
payload: book.as_json,
outboxable: book
)
end
```
If you are intending to create an outbox that is not necessarily associated with a resource, you can use the following configuration that sets the ``size`` attribute to ``:batch``:
```ruby
ActiveRecord::Base.transaction do
book = Book.find(1)
book.update!(status: :published)
Outbox.create!(
routing_key: 'notifications.publish',
exchange: 'YOUR EXCHANGE',
payload: {
channels: ['sms', 'email'],
message: "Hello, world!"
},
size: :batch
)
end
```
### The Outboxable Concern
The Outboxable Gem provides an ActiveRecord Model Concern that you can reuse in your models, which in turn will take care of transactionally creating events in case of create or update of the resource. All you have to do is to ``include Outboxable`` in your model and implement a method by the name of ``outbox_configurations``:
```ruby
class Book < ApplicationRecord
include Outboxable
# Enums
enum status: { draft: 0, published: 1 }
def outbox_configurations
@outbox_configurations ||= {
base: {
routing_key: 'books'
},
run_on: {
create: {},
update: {
condition: proc { |book| book.published? },
routing_key: 'books.published'
}
}
}
end
end
```
The ``outbox_configurations`` method will be called and used by the Outboxable Gem to transactionally create an outbox and publish. In the code above, it will create an outbox when the book is created. For that purpose it will use the routing key ``books.created`` as a convention. It will also publish an event if the book is updated, using the routing key: ``books.published`` since it was specified in the hash.
Here's the schema of what could be passed to the ``outbox_configurations`` in JSON Schema format:
```json
{
"type": "object",
"properties": {
"base": {
"type": "object",
"properties": {
"routing_key": { "type": "string" }
}
},
"run_on": {
"type": "object",
"properties": {
"create": {
"type": "object",
"properties": {
"condition": { "type": "Ruby Proc" },
"routing_key": { "type": "string" }
}
},
"update": {
"type": "object",
"properties": {
"condition": { "type": "Ruby Proc" },
"routing_key": { "type": "string" }
}
}
}
}
}
}
```
The ``run_on`` key represents another hash that can have the keys ``create`` and ``update``. If one of these keys are not supplied, the outbox will not be created for the unspecified operation; in other words, if you do not specify the configuration for ``update``, for example, an outbox will NOT be created when the book is updated.
Each operation key such as ``create`` and ``update`` can also take a ``condition`` key, which represents a Ruby proc that must return a boolean expression. It can also take a ``routing_key`` option, which specifies that routing key with which the outbox will publish the event to the message broker. If you don't specify the ``routing_key``, it will use the base`s routing key dotted by``created`` for create operation and ``updated`` for update operation.
Last but not least, run sidekiq so that the Outboxable Gem can publish the events to the broker:
```shell
$ bundle exec sidekiq
```
## Development