Skip to content

Commit

Permalink
Merge branch 'master' into feature/add_web_specs
Browse files Browse the repository at this point in the history
  • Loading branch information
bedrock-adam committed Jan 1, 2025
2 parents 49d71bc + b71a542 commit bee29b2
Show file tree
Hide file tree
Showing 39 changed files with 469 additions and 325 deletions.
16 changes: 12 additions & 4 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ jobs:
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis
ports:
- 6379:6379
steps:
- uses: actions/checkout@v3
- name: set up ruby
Expand All @@ -54,9 +58,9 @@ jobs:
export PGPASSWORD=outboxer_password
psql -h localhost -U outboxer_developer -d outboxer_test -c 'SELECT version();'
- name: run database migrations
run: OUTBOXER_ENV=test bin/rake outboxer:db:migrate
run: RAILS_ENV=test bin/rake outboxer:db:migrate
- name: run rspec
run: OUTBOXER_ENV=test bin/rspec spec
run: RAILS_ENV=test bin/rspec spec

mysql:
runs-on: ubuntu-latest
Expand All @@ -82,6 +86,10 @@ jobs:
--health-interval=10s
--health-timeout=5s
--health-retries=5
redis:
image: redis
ports:
- 6379:6379
steps:
- uses: actions/checkout@v3
- name: set up ruby
Expand All @@ -96,15 +104,15 @@ jobs:
mysql -h 127.0.0.1 -P 3306 -u outboxer_developer -poutboxer_password outboxer_test -e 'SELECT VERSION();'
- name: run database migrations
env:
OUTBOXER_ENV: test
RAILS_ENV: test
DATABASE_ADAPTER: mysql2
DATABASE_HOST: 127.0.0.1
DATABASE_PORT: 3306
run: |
bin/rake outboxer:db:migrate
- name: run rspec
env:
OUTBOXER_ENV: test
RAILS_ENV: test
DATABASE_ADAPTER: mysql2
DATABASE_PORT: 3306
DATABASE_HOST: 127.0.0.1
Expand Down
84 changes: 29 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## Background

Outboxer is an ActiveRecord implementation of the transactional outbox pattern for PostgreSQL and MySQL databases.
Outboxer is a Rails 7 implementation of the transactional outbox pattern.

## Setup

Expand All @@ -21,10 +21,10 @@ gem 'outboxer'
bundle install
```

### 3. generate schema
### 3. generate schema and publisher

```bash
bin/rails g outboxer:schema
bin/rails g outboxer:install
```

### 4. migrate schema
Expand All @@ -33,83 +33,57 @@ bin/rails g outboxer:schema
bin/rake db:migrate
```

### 5. seed database

```bash
bin/rake outboxer:db:seed
```

### 6. queue message after event creation

#### new event

```bash
bin/rails g outboxer:event
```

#### existing event
### 5. Publish message

```ruby
class Event < ActiveRecord::Base
after_create do |event|
Outboxer::Message.queue(messageable: event)
end
Outboxer::Publisher.publish do |message|
# TODO: publish message to your broker here
end
```

### 7. generate publisher
See: [publisher best practices](https://github.com/fast-programmer/outboxer/wiki/Publisher-best-practices) for common integration examples including with sidekiq

#### sidekiq
### 6. run publisher

```bash
bin/rails g outboxer:sidekiq_publisher
bin/outboxer_publisher
```

#### custom
### 7. open rails console

```bash
bin/rails g outboxer:publisher
bin/rails c
```

### 8. publish message out of band

#### Sidekiq
### 8. create test event

```ruby
Outboxer::Publisher.publish do |message|
OutboxerIntegration::Message::PublishJob.perform_async(message)
end
TestEvent.create!
```

#### Custom

```ruby
Outboxer::Publisher.publish do |message|
# publish message to custom broker here
end
```

### 9. run publisher

```bash
bin/outboxer_publisher
TRANSACTION (0.2ms) BEGIN
TestEvent Create (1.2ms) INSERT INTO "events" ....
Outboxer::Models::Message Create (1.8ms) INSERT INTO "outboxer_messages" ...
TRANSACTION (0.4ms) COMMIT
=>
#<TestEvent:0x000000010a329eb0
id: 1,
user_id: nil,
tenant_id: nil,
type: "TestEvent",
body: nil,
created_at: Sat, 21 Dec 2024 09:11:56.459083000 UTC +00:00>
```

### 10. open rails console
### 9. Observe published message

```bash
bin/rails c
```

### 11. create event
Confirm the message has been published out of band e.g.

```ruby
Event.create!
```

### 12. Observe published message

Confirm the message has been published out of band
2024-12-21T09:12:01.303Z pid=13171 tid=publisher-1 INFO: Outboxer published message 1 to sidekiq for TestEvent::1
```

## Management

Expand Down
11 changes: 0 additions & 11 deletions app/jobs/outboxer_integration/message/publish_job.rb

This file was deleted.

17 changes: 17 additions & 0 deletions app/jobs/outboxer_integration/publish_message_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module OutboxerIntegration
class PublishMessageJob
include Sidekiq::Job

def perform(args)
job_class_name = to_job_class_name(messageable_type: args['messageable_type'])
job_class_name&.safe_constantize&.perform_async('event_id' => args['messageable_id'])
end

def to_job_class_name(messageable_type:)
captures = messageable_type.match(/\A(?:(\w+)::)?(\w+)Event\z/)&.captures
return if captures.nil?

captures[0] ? "#{captures[0]}::#{captures[1]}Job" : "#{captures[1]}Job"
end
end
end
9 changes: 9 additions & 0 deletions app/jobs/outboxer_integration/test_started_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module OutboxerIntegration
class TestStartedJob
include Sidekiq::Job

def perform(args)
Test.complete(event_id: args['event_id'])
end
end
end
3 changes: 3 additions & 0 deletions app/models/application_record.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
17 changes: 5 additions & 12 deletions app/models/event.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
class Event < ActiveRecord::Base
class Event < ApplicationRecord
self.table_name = 'events'

# validations

# validates :user_id, presence: true
# validates :tenant_id, presence: true
# validates :created_at, presence: true

# validates :type, presence: true
# associations

# validates :eventable_id, presence: true
# validates :eventable_type, presence: true
belongs_to :eventable, polymorphic: true

# associations
# validations

# belongs_to :eventable, polymorphic: true
validates :type, presence: true, length: { maximum: 255 }

# callbacks

Expand Down
41 changes: 41 additions & 0 deletions app/models/outboxer_integration/test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module OutboxerIntegration
class Test < ApplicationRecord
self.table_name = 'outboxer_integration_tests'

def self.start(user_id: nil, tenant_id: nil)
transaction do
test = create!(tenant_id: tenant_id)

event = TestStartedEvent.create!(
user_id: user_id,
tenant_id: tenant_id,
eventable: test,
body: {
'test' => {
'id' => test.id } })

{ id: test.id, events: [{ id: event.id, type: event.type }] }
end
end

def self.complete(event_id:)
transaction do
started_event = TestStartedEvent.find(event_id)
test = started_event.eventable
test.touch

event = TestCompletedEvent.create!(
user_id: started_event.user_id,
tenant_id: started_event.tenant_id,
eventable: test,
body: {
'test' => {
'id' => test.id } })

{ id: test.id, events: [{ id: event.id, type: event.type }] }
end
end

has_many :events, as: :eventable
end
end
4 changes: 4 additions & 0 deletions app/models/outboxer_integration/test_completed_event.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module OutboxerIntegration
class TestCompletedEvent < Event
end
end
4 changes: 4 additions & 0 deletions app/models/outboxer_integration/test_started_event.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module OutboxerIntegration
class TestStartedEvent < Event
end
end
8 changes: 5 additions & 3 deletions bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ require 'outboxer'
require 'sidekiq'
require 'irb'

require_relative "../app/models/event"
Dir.glob(File.join(__dir__, '../app/models/**/*.rb')).each do |file|
require_relative file
end

env = ENV['OUTBOXER_ENV'] || 'development'
config = Outboxer::Database.config(env: env, pool: 1)
environment = ENV['RAILS_ENV'] || 'development'
config = Outboxer::Database.config(environment: environment, pool: 1)
Outboxer::Database.connect(config: config)

Sidekiq.configure_client do |config|
Expand Down
4 changes: 2 additions & 2 deletions bin/outboxer_load
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ if max_messages_count <= 0
exit(1)
end

db_config = Outboxer::Database.config(
env: ENV.fetch('OUTBOXER_ENV', 'development'), pool: 1)
environment = ENV.fetch('RAILS_ENV', 'development')
db_config = Outboxer::Database.config(environment: environment, pool: 1)
Outboxer::Database.connect(config: db_config)

Signal.trap('INT') do
Expand Down
35 changes: 8 additions & 27 deletions bin/outboxer_publisher
Original file line number Diff line number Diff line change
@@ -1,34 +1,15 @@
#!/usr/bin/env ruby

require 'bundler/setup'
require 'outboxer'

options = {
env: ENV.fetch('OUTBOXER_ENV', 'development'),
buffer: ENV.fetch('OUTBOXER_BUFFER', 1000).to_i,
concurrency: ENV.fetch('OUTBOXER_CONCURRENCY', 2).to_i,
poll: ENV.fetch('OUTBOXER_POLL', 5.0).to_f,
tick: ENV.fetch('OUTBOXER_TICK', 0.1).to_f,
heartbeat: ENV.fetch('OUTBOXER_HEARTBEAT', 5.0).to_f,
log_level: ENV.fetch('OUTBOXER_LOG_LEVEL', 'INFO'),
}

logger = Outboxer::Logger.new($stdout, level: options[:log_level])
require 'sidekiq'
require 'outboxer'

Outboxer::Publisher.publish(
env: options[:env],
buffer: options[:buffer],
concurrency: options[:concurrency],
poll: options[:poll],
tick: options[:tick],
heartbeat: options[:heartbeat],
logger: logger
) do |message|
case message[:messageable_type]
when 'Event'
# TODO: publish message here
require_relative '../app/jobs/outboxer_integration/publish_message_job'

logger.info "Outboxer published message #{message[:id]} for "\
"#{message[:messageable_type]}::#{message[:messageable_id]}"
end
Outboxer::Publisher.publish do |message|
OutboxerIntegration::PublishMessageJob.perform_async({
'message_id' => message[:id],
'messageable_id' => message[:messageable_id],
'messageable_type' => message[:messageable_type] })
end
Loading

0 comments on commit bee29b2

Please sign in to comment.