adomokos / light-service

Series of Actions with an emphasis on simplicity.
MIT License
837 stars 67 forks source link

Adds around_each to WithReducers for organizers #79

Closed bwvoss closed 8 years ago

bwvoss commented 8 years ago

The ambition of this commit is to unobtrusively gather arbitrary metrics from your application.

An around_each receiver follows the duck type of:

def call(action, context)

The rest is up to you!

Here is an example that could be used to check persisted counts in a data pipeline (there is another example in the README for logging duration or profiling actions):

class DataCounterCheck
  def self.call(action, context)
    before_count = context.data.count
    result = yield
    after_count = context.persisted_data.count

    LightService::Configuration.logger.info({
      :action       => action,
      :before_count => before_count,
      :after_count  => after_count,
      :difference   => before_count - after_count,
    })

    result
  end
end

Design Consideration

I tried to focus most on the readability of the API, and I think it reads nicely method-chained. I chose to simply yield the block passed by light-service so the around each objects do not have to change even if the way light-service chooses to execute actions does. It's fairly open-ended, but still simple enough, I hope.

Complexity

The around_each method call provides enough to do the use cases I can foresee without overcomplicating the software. It does add some assignments and conditionals, so it is close to a new abstraction, but probably not quite yet. It changes the flog by such:

flog lib/light-service/organizer -g

Before:

123.2: flog total
     7.7: flog/method average

    55.5: LightService::Organizer::WithReducerLogDecorator total
    40.2: LightService::Organizer::WithReducerLogDecorator#reduce
          lib/light-service/organizer/with_reducer_log_decorator.rb:27
    15.2: LightService::Organizer::WithReducerLogDecorator#with
          lib/light-service/organizer/with_reducer_log_decorator.rb:13

    19.0: LightService::Organizer::WithReducer total
    11.6: LightService::Organizer::WithReducer#reduce
          lib/light-service/organizer/with_reducer.rb:10
     7.4: LightService::Organizer::WithReducer#reduce_rollback
          lib/light-service/organizer/with_reducer.rb:28

After:

128.4: flog total
    7.6: flog/method average

    55.5: LightService::Organizer::WithReducerLogDecorator total
    40.2: LightService::Organizer::WithReducerLogDecorator#reduce
          lib/light-service/organizer/with_reducer_log_decorator.rb:27
    15.2: LightService::Organizer::WithReducerLogDecorator#with
          lib/light-service/organizer/with_reducer_log_decorator.rb:13

    23.1: LightService::Organizer::WithReducer total
    15.7: LightService::Organizer::WithReducer#reduce
          lib/light-service/organizer/with_reducer.rb:15
     7.4: LightService::Organizer::WithReducer#reduce_rollback
          lib/light-service/organizer/with_reducer.rb:37

Benchmarks

It has a negligible impact on the organizer's reduction performance -- it is about 1.01x slower with around_each calls.

From this script https://gist.github.com/bwvoss/8483ecc4a0315e724fed, here are some before and after IPS benchmarks:

Calculating -------------------------------------

         default     884.000  i/100ms
         around_each 855.000  i/100ms

-------------------------------------------------

         default      8.912k (± 2.4%) i/s - 45.084k
         around_each  8.786k (± 3.6%) i/s - 44.460k

Comparison:

         default:     8911.9 i/s
         around_each: 8786.1 i/s - 1.01x slower
adomokos commented 8 years ago

Thank you, @bwvoss!

rewinfrey commented 8 years ago

:thumbsup: @bwvoss, I really like the analysis too!

bwvoss commented 8 years ago

Thanks guys, it was a fun PR!