Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add instrumentation for repositories #423

Merged
merged 14 commits into from
Aug 9, 2018
Merged

Conversation

swistak35
Copy link
Contributor

Issue: #244

@mostlyobvious
Copy link
Member

Interesting, I thought that Client would be more appropriate than Repository. It might be a bit too low-level. And definitely not all methods there deserve instrumenting.

end
end

def has_event?(event_id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meh

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From one standpoint, as long as this is repository interface, then it deserves instrumenting. On the other hand what's the value of instrumenting this particular one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pawelpacana check my PR comment, I have big doubt about that one too.

@swistak35
Copy link
Contributor Author

My take on instrumentation, learning on what was in #90. I've started with Robert's idea on instrumented repository, because I wanted something easily deliverable. I believe that higher-level instrumentation still can be interesting in cases different than benchmarking (logging maybe?), but that's some start.

Also, I was not sure about instrumenting all public methods of repository, i.e. has_event?(...) - I'm not sure how useful is that instrumentation, except some hardcore benchmarking :P But I guess that in the end, instrumentation is to allow others to play with their ideas how to use it, not only some predefined usecases.

What do you guys think?

let(:test_link_events_to_stream) { false }
let(:test_binary) { false }

it_behaves_like :event_repository, InstrumentedRepository
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -8,7 +8,7 @@ def initialize(repository: RailsEventStoreActiveRecord::EventRepository.new,
dispatcher: ActiveJobDispatcher.new,
request_metadata: default_request_metadata,
page_size: PAGE_SIZE)
super(repository: repository,
super(repository: InstrumentedRepository.new(repository),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@mostlyobvious
Copy link
Member

Regarding which are worth instrumenting -- from maintainer perspective it is better to add less and reconsider adding more in the future than removing published instruments.

@swistak35
Copy link
Contributor Author

@pawelpacana Which one would you leave? I tend to all except has_event? and last_stream_event.

@mostlyobvious
Copy link
Member

@pawelpacana Which one would you leave? I tend to all except has_event? and last_stream_event.

Good call, lets drop those two for now.

These considered not necessary right now, maybe they will be added
later.

Issue: #244

let(:test_race_conditions_any) { false }
let(:test_race_conditions_auto) { false }
let(:test_expected_version_auto) { false }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why false ?

let(:test_race_conditions_any) { false }
let(:test_race_conditions_auto) { false }
let(:test_expected_version_auto) { false }
let(:test_link_events_to_stream) { false }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why false here as well ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dunno about these, I've initially skipped these values because they've required some additional methods to be defined. I will look at it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't make sense probably to test race conditions but it does make sense to verify that linking events still works correctly, don't you think?

@@ -0,0 +1,48 @@
module RailsEventStore
class InstrumentedRepository
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this decorator :)

@@ -43,5 +43,18 @@ module RailsEventStore
expect(published.first.metadata[:request_id]).to eq('dummy_id')
expect(published.first.metadata[:timestamp]).to be_a Time
end

specify 'wraps repository into instrumentation' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@paneq
Copy link
Member

paneq commented Aug 9, 2018

The tests are super verbose...

      specify "instruments" do
        some_repository = double
        allow(some_repository).to receive(:append_to_stream)
        instrumented_repository = InstrumentedRepository.new(some_repository)
        notification_calls = subscribe_to("append_to_stream.repository.rails_event_store")
        event1 = Object.new
         instrumented_repository.append_to_stream([event1], "SomeStream", "c456845d-2b86-49c1-bdef-89e57b5d86b1")
         expect(notification_calls).to eq([
          { events: [event1], stream: "SomeStream" }
        ])
      end
    end

Allow me to offer some alternative:

let(:instrumented_repository) { InstrumentedRepository.new(InMemoryRepository.new) }
let(:event) { RubyEventStore::Event.new }
let!(:notification_calls) { subscribe_to(/repository.rails_event_store/) }

      specify "instruments append_to_stream" do
         instrumented_repository.append_to_stream([event], "SomeStream", "c456845d-2b86-49c1-bdef-89e57b5d86b1")
         expect(notification_calls).to eq([
          { events: [event], stream: "SomeStream" }
        ])
      end
    end

I believe these 3 lets can be easily reused between many specs. Definitely no need to use mocks in every spec when you can just decorate InMemoryRepo and have it working with no pain.

The only downside is that we don't verify exact notification name but only that it belongs to the namespace repository.rails_event_store. But even if you don't go with that the reduced verbosity of not using mocks could be worth dozens lines of tests less.

@paneq
Copy link
Member

paneq commented Aug 9, 2018

Interesting, I thought that Client would be more appropriate than Repository. It might be a bit too low-level. And definitely not all methods there deserve instrumenting.

Good point. I felt the same. But on the other hand... It should be relatively easy to write similar decorators for Broker and Mapper and then one should be able to pin-point exact performance problems to lower-level components and maybe monitoring Client would be lower prio. It does not do that much on itself (but it does a few things).

end
end

def subscribe_to(name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we never unsubscribe, doesn't it lead to memory leaks in all RailsEventStore tests?

Wouldn't these arrays continue receiving payloads for all tests (in this file and other files) which call methods on RailsEventStore::Clients. It might be better to verify with temporary subscriptions:

callback = lambda {|*args| ... }
ActiveSupport::Notifications.subscribed(callback, /rails_event_store/) do
  ...
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let(:received) { [] }
around do |spec|
  callback = lambda {|*args| received << args }
  ActiveSupport::Notifications.subscribed(callback, /rails_event_store/) do
    spec.call
  end
end

or

let(:received) { [] }
around do |spec|
  callback = lambda {|*args| received << args }
  ActiveSupport::Notifications.subscribed(callback, "#{spec.metadata[:subscription_name]}.repository.rails_event_store") do
    spec.call
  end
end

specify "instruments append_to_stream", subscription_name: "append_to_stream" do
  # ...
end

@swistak35
Copy link
Contributor Author

@paneq mesa like use InMemoryRepository instead of mocks, but mesa no like too many lets :)

@swistak35
Copy link
Contributor Author

then one should be able to pin-point exact performance problems to lower-level components

As I've said earlier, I still think higher-level instrumentation can be valuable. As I understand it, instrumentation is not only about performance problems, but also about hooking in the right moment because of some particular usecase we can not think about. Where are you, AOP ;)

@paneq
Copy link
Member

paneq commented Aug 9, 2018

As I've said earlier, I still think higher-level instrumentation can be valuable.

That's unquestionable. It's rather a matter of priorities. Do we start with low-level instrumentation or higher-level.

As I understand it, instrumentation is not only about performance problems, but also about hooking in the right moment because of some particular usecase we can not think about. Where are you, AOP ;)

Interesting. I don't see it much that way because that's only instrumentation and not AOP :) You can't affect the code from within the subscribed code.

Now, when it doesn't directly on ActiveSupport::Notifications, it can
be in RubyEventStore. ActiveSupport::Notifications still is present in
the tests, because it serves us as API standard.

Issue: #244
@paneq
Copy link
Member

paneq commented Aug 9, 2018

@swistak35 any pland for documentation and RDoc?

@swistak35
Copy link
Contributor Author

@paneq yeah, I haven't started it because I expected that the implementation may change rapidly, I'd prefer to make documentation in separate PR after merge

@paneq
Copy link
Member

paneq commented Aug 9, 2018

good for me :)

@mostlyobvious
Copy link
Member

Let's deal with brew cask issue on master.

@mostlyobvious mostlyobvious merged commit 50676f4 into master Aug 9, 2018
@mostlyobvious mostlyobvious deleted the rl/as-instrumentation branch August 9, 2018 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants