Skip to content

Commit

Permalink
Merge pull request #327 from RailsEventStore/with-metadata
Browse files Browse the repository at this point in the history
Allow to dynamically enrich events metadata by using `with_metadata`
  • Loading branch information
mostlyobvious authored May 3, 2018
2 parents d961104 + 30866ab commit 06f6f34
Show file tree
Hide file tree
Showing 16 changed files with 359 additions and 184 deletions.
26 changes: 22 additions & 4 deletions rails_event_store/lib/rails_event_store/client.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
module RailsEventStore
class Client < RubyEventStore::Client
attr_reader :request_metadata

def initialize(repository: RailsEventStoreActiveRecord::EventRepository.new,
mapper: RubyEventStore::Mappers::Default.new,
event_broker: EventBroker.new(dispatcher: ActiveJobDispatcher.new),
request_metadata: default_request_metadata,
page_size: PAGE_SIZE)
capture_metadata = ->{ Thread.current[:rails_event_store] }
super(repository: repository,
mapper: mapper,
event_broker: event_broker,
page_size: page_size,
metadata_proc: capture_metadata)
page_size: page_size)
@request_metadata = request_metadata
end

def with_request_metadata(env, &block)
with_metadata(request_metadata.call(env)) do
block.call
end
end

private
def default_request_metadata
->(env) do
request = ActionDispatch::Request.new(env)
{
remote_ip: request.remote_ip,
request_id: request.uuid
}
end
end
end
end
end
20 changes: 14 additions & 6 deletions rails_event_store/lib/rails_event_store/middleware.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
module RailsEventStore
class Middleware
def initialize(app, request_metadata_proc)
def initialize(app)
@app = app
@request_metadata_proc = request_metadata_proc
end

def call(env)
Thread.current[:rails_event_store] = @request_metadata_proc.(env)
@app.call(env)
ensure
Thread.current[:rails_event_store] = nil
if config.respond_to?(:event_store)
config.event_store.with_request_metadata(env) do
@app.call(env)
end
else
@app.call(env)
end
end

private

def config
Rails.application.config
end
end
end
23 changes: 1 addition & 22 deletions rails_event_store/lib/rails_event_store/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,7 @@
module RailsEventStore
class Railtie < ::Rails::Railtie
initializer 'rails_event_store.middleware' do |rails|
rails.middleware.use(::RailsEventStore::Middleware, RailsConfig.new(rails.config).request_metadata)
end

class RailsConfig
def initialize(config)
@config = config
end

def request_metadata
return default_request_metadata unless @config.respond_to?(:rails_event_store)
@config.rails_event_store.fetch(:request_metadata, default_request_metadata)
end

private
def default_request_metadata
->(env) do
request = ActionDispatch::Request.new(env)
{ remote_ip: request.remote_ip,
request_id: request.uuid,
}
end
end
rails.middleware.use(::RailsEventStore::Middleware)
end
end
end
Expand Down
47 changes: 47 additions & 0 deletions rails_event_store/spec/client_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
require 'spec_helper'

module RailsEventStore
RSpec.describe Client do
TestEvent = Class.new(RailsEventStore::Event)

specify 'has default request metadata proc if no custom one provided' do
client = Client.new
expect(client.request_metadata.call({
'action_dispatch.request_id' => 'dummy_id',
'action_dispatch.remote_ip' => 'dummy_ip'
})).to eq({
remote_ip: 'dummy_ip',
request_id: 'dummy_id'
})
end

specify 'allows to set custom request metadata proc' do
client = Client.new(
request_metadata: -> env { {server_name: env['SERVER_NAME']} }
)
expect(client.request_metadata.call({
'SERVER_NAME' => 'example.org'
})).to eq({
server_name: 'example.org'
})
end

specify 'published event metadata will be enriched by metadata provided in request metadata when executed inside a with_request_metadata block' do
client = Client.new(
repository: InMemoryRepository.new,
)
event = TestEvent.new
client.with_request_metadata(
'action_dispatch.request_id' => 'dummy_id',
'action_dispatch.remote_ip' => 'dummy_ip'
) do
client.publish_event(event)
end
published = client.read_all_streams_forward
expect(published.size).to eq(1)
expect(published.first.metadata[:remote_ip]).to eq('dummy_ip')
expect(published.first.metadata[:request_id]).to eq('dummy_id')
expect(published.first.metadata[:timestamp]).to be_a Time
end
end
end
30 changes: 25 additions & 5 deletions rails_event_store/spec/middleware_integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,41 @@
require 'rails_event_store/middleware'
require 'rack/test'
require 'rack/lint'
require 'support/test_application'

module RailsEventStore
RSpec.describe Middleware do
DummyEvent = Class.new(RailsEventStore::Event)

specify do
specify 'works without event store instance' do
event_store = Client.new
request = ::Rack::MockRequest.new(Middleware.new(app))
request.get('/')

event_store.read_all_streams_forward.map(&:metadata).each do |metadata|
expect(metadata.keys).to eq([:timestamp])
expect(metadata[:timestamp]).to be_a(Time)
end
end

request = ::Rack::MockRequest.new(Middleware.new(
->(env) { event_store.publish_event(DummyEvent.new); [200, {}, ["Hello World from #{env["SERVER_NAME"]}"]] },
->(env) { { server_name: env['SERVER_NAME'] }}))
specify 'sets domain events metadata for events published with global event store instance' do
event_store = Client.new(
request_metadata: -> env { {server_name: env['SERVER_NAME']} }
)
app.config.event_store = event_store

request = ::Rack::MockRequest.new(Middleware.new(app))
request.get('/')

event_store.read_all_streams_forward.map(&:metadata).each do |metadata|
expect(metadata[:server_name]).to eq('example.org')
expect(metadata[:server_name]).to eq('example.org')
expect(metadata[:timestamp]).to be_a(Time)
end
end

def app
TestApplication.tap do |app|
app.routes.draw { root(to: ->(env) {event_store.publish_event(DummyEvent.new); [200, {}, ['']]}) }
end
end
end
Expand Down
51 changes: 24 additions & 27 deletions rails_event_store/spec/middleware_spec.rb
Original file line number Diff line number Diff line change
@@ -1,42 +1,39 @@
require 'spec_helper'
require 'rails_event_store/middleware'
require 'rack/lint'
require 'support/test_application'

module RailsEventStore
RSpec.describe Middleware do
specify 'lint' do
request = ::Rack::MockRequest.new(::Rack::Lint.new(Middleware.new(
->(env) { [200, {}, ['Hello World']] },
->(env) { { kaka: 'dudu' } } )))

expect { request.get('/') }.to_not raise_error
before do
allow(Rails.application).to receive(:config).and_return(configuration)
end

specify do
request = ::Rack::MockRequest.new(Middleware.new(
->(env) { [200, {}, ['Hello World']] },
->(env) { { kaka: 'dudu' } } ))
request.get('/')

expect(Thread.current[:rails_event_store]).to be_nil
specify 'calls app within with_request_metadata block when app has configured the event store instance' do
Rails.application.config.event_store = event_store = Client.new
expect(event_store).to receive(:with_request_metadata).with(dummy_env).and_call_original
expect(app).to receive(:call).with(dummy_env).and_call_original
middleware = Middleware.new(app)
expect(middleware.call(dummy_env)).to eq([204, {}, ['']])
end

specify do
request = ::Rack::MockRequest.new(Middleware.new(
->(env) { raise },
->(env) { { kaka: 'dudu' } } ))
specify 'just calls the app when app has not configured the event store instance' do
expect(app).to receive(:call).with(dummy_env).and_call_original
middleware = Middleware.new(app)
expect(middleware.call(dummy_env)).to eq([204, {}, ['']])
end

expect { request.get('/') }.to raise_error(RuntimeError)
expect(Thread.current[:rails_event_store]).to be_nil
def dummy_env
{
'action_dispatch.request_id' => 'dummy_id',
'action_dispatch.remote_ip' => 'dummy_ip'
}
end

specify do
request = ::Rack::MockRequest.new(Middleware.new(
->(env) { [200, {}, ['Hello World']] },
->(env) { raise } ))
def configuration
@configuration ||= FakeConfiguration.new
end

expect { request.get('/') }.to raise_error(RuntimeError)
expect(Thread.current[:rails_event_store]).to be_nil
def app
@app ||= -> _ { [204, {}, ['']] }
end
end
end
Expand Down
50 changes: 0 additions & 50 deletions rails_event_store/spec/railtie_spec.rb

This file was deleted.

21 changes: 0 additions & 21 deletions rails_event_store/spec/request_metadata_spec.rb

This file was deleted.

3 changes: 1 addition & 2 deletions rails_event_store/spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
require 'support/mutant_timeout'
require 'support/fake_configuration'


MigrationCode = File.read( File.expand_path('../../../rails_event_store_active_record/lib/rails_event_store_active_record/generators/templates/migration_template.rb', __FILE__) )
migration_version = Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new("5.0.0") ? "" : "[4.2]"
MigrationCode.gsub!("<%= migration_version %>", migration_version)
Expand All @@ -22,4 +21,4 @@
end

$verbose = ENV.has_key?('VERBOSE') ? true : false
ActiveJob::Base.logger = nil unless $verbose
ActiveJob::Base.logger = nil unless $verbose
10 changes: 10 additions & 0 deletions rails_event_store/spec/support/test_application.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require 'action_controller/railtie'
require 'rails_event_store/railtie'
require 'securerandom'

class TestApplication < Rails::Application
config.eager_load = false
config.secret_key_base = SecureRandom.hex(16)
initialize!
routes.default_url_options = { host: 'example.org' }
end
33 changes: 0 additions & 33 deletions rails_event_store/spec/support/test_rails.rb

This file was deleted.

Loading

0 comments on commit 06f6f34

Please sign in to comment.