Purpose
In Ruby, monkey patching is often frowned upon. Modifying code by simply overwriting components of a class or a module is risky business - but there are times when it is justified. Debugging or testing scenarios, in particular, can benefit; debug-only or test-only code can be patched in at source code parse-time, leaving no run-time overhead for production releases. It’s a way to simulate the compile-time debugging you might use with languages like ‘C’ while retaining the rapid development advantages of an interpreted language.
Given this, Hoodoo 1.8.0 introduced Hoodoo::Monkey
, a first class citizen that provides a formal, reasonably clean, reversible monkey patching mechanism. It leans on Ruby 2 features which allow replacement method implementations to call the original via super
- it “feels like” writing a subclass rather than a patch.
In detail
The Hoodoo::Monkey
RDoc includes a lot of background information and a series of examples which cover almost everything you need. There’s not a lot left to cover in a Hoodoo Guide given the relative simplicity of the monkey patching system.
Within services
If you’re using the service shell as the basis for a service, then in revisions from 2016-04-26 onwards you’ll find a folder service/monkeys
which is auto-included after all the other service folders. Put your monkey patches here. Otherwise, use whatever inclusion scheme you feel is appropriate.
Example
A monkey patch doesn’t necessarily have to replace a method; it can add one which isn’t already present. For example, a service resource implementation class might not already define a before
or after
filter method, but a monkey patch might add one; it should still call super
, of course, just in case the implementation is subsequently updated.
module AnalyticsMonkey
module InstanceExtensions
def after( context )
if some_conditions_related_to_context?
data = { :context_related_data => 'context-related stuff' }
Hoodoo::Services::Middleware.logger.report(
:info,
interaction.target_interface.resource || '(unknown)',
:analytics,
data
)
end
super( context )
end
end
end
Hoodoo::Monkey.register( extension_module: AnalyticsMonkey, target_unit: SomeImplementation )
Hoodoo::Monkey.enable( extension_module: AnalyticsMonkey )
Within Hoodoo
Hoodoo uses a naming convention to mark parts of its normally-internal implementation which are exposed as public interfaces. Such methods start with the name monkey_
; use the RDoc search field with text monkey
to get a list of these. Methods thus named must not be called by external code. They’re considered very much an implementation detail. They may refer to input data or instance data which is not ordinarily exposed to external callers and may change without notice.
The potential fragility of a patch based upon what amounts to an internal implementation method is alleviated if you can simply act, as per RDoc recommendation, as a filter; modify input data or output data while deferring to the original implementation through super
. If you require a more invasive change, then you’ll either have to be much more careful to maintain your patch when Hoodoo updates occur, or consider sending in a Hoodoo pull request that adds the required features to the core code.
Example
Hoodoo’s middleware uses a Hoodoo monkey for optional NewRelic cross-application tracing around inter-resource calls for on-queue architectures. Due to the use of a monkey, this only ever introduces any kind of overhead if it is opted into. Otherwise, the code is literally not present - no conditional branches, no subclasses, nothing. See the source code for details.
Further reading
Remember, RDoc has deeper technical information about all of the classes and methods involved: