JASON CLARK: All righty. I think, by my clock,
it's about 1:40. A couple more people
are coming on in, but we'll get things started.
My name's Jason Clark. I'm an engineer at New
Relic. I work specifically on the New Relic RPM
gem, which some of you may have installed in
your applications. So that's actually gonna play in a
little bit in some of the examples that I'm
gonna use later on. But you don't need to
know too much about how that works.
The first, so, I, I've got to start off
with a, an admission to something that I've always
wanted to do when presenting in front of a
group of people, and I figured that RailsConf is
probably just about the best opportunity. So, if you'll
just bear with me for a second. Just hang on.
So at New Relic, we have a support hero
who is responsible for handling the escalations. So any
time that something gets too complicated for support, they
send it to the support hero. And my wife
heard that, and she went, if you're a hero,
you need a cape. And so she made me
this lovely hand-embroidered Ruby emblem cape. And I've always
wanted to give a presentation wearing this. So, if
that's OK. Little unprofessional, maybe, but, you know. I'm
just gonna go for it, so.
AUDIENCE: Look in the sky! It's a bird!
J.C.: All right. That feels good. Thank you. Thank
you for humoring me. All right.
So what we're here for, here to talk about
today is about something that I call evented patterns
in Ruby. So let's start off with a little
bit of a story. This is a little bit
of a fabrication, but it might be something that
some of you have experienced before.
So, imagine that you have created an application. Maybe
it's something like, something basic, for doing tracking of
your cycling statistics. And all of a sudden, your
application really takes off. It gets on the social
media. People find out about it and, you know,
you, you start getting this influx of users that
you've got to deal with. As your traffic scales,
you've got to do things like tuning your web
server, right. You, you pick your, your Unicorn or
your Puma and you get that set up. You
tune your system. And while your traffic is growing,
there's a similar sort of growth that goes on
in your code.
Your controllers start out life kind of like this.
They start out pretty simple, you know. We just
create our user when somebody signs up. But then,
as your application grows and you get more stake
holders and your company's, you know, doubling every two
months, all of a sudden, you've got other things
that you need to add in there, right. You,
maybe you want to send welcome emails, so you,
you end up, you know, getting your action mailer
going there, and cram that into, into your action
when someone signs up.
Oh, and then maybe it's really important that our
business people be able to do analytics. They want
to know about who's signing up and track data,
and there's some other place that we want to
push that data. Oh, and then the sales people,
they really want this to be integrated to their
sales system, and so they want stuff to get
pushed over to their CRM. And maybe, oh, we
want to do some sort of posting out to
the social networks. So we have a background job
that takes care of that. You know, this sort
of growth happens in some of those critical points
in your infrastructure. The spots where interesting things happen
in the domain of your application.
Now, that's kind of, can get to be a
mess, you know. This, over time, I mean this
is a short example and slightly fabricated, but I'm
sure you all have, you know, I'm sure all
of your controllers are, like, twelve lines or less
for an action, right? Nobody has these long methods
that go on and do tons and tons of
things.
You can end up with a massive snarl. You
can end up with controllers that are very difficult
to test, difficult to reason about, and they go
on and on handling all of these different things.
So what we're gonna talk about is a pattern
of using events to handle that and to take
those key parts of your infrastructure and break them
apart into smaller bits.
So, first off, we're gonna talk about what I
actually mean when I say events, the pattern that
we're talking about. We'll talk about how that can
be used in the coupling of your application, both
internally within your app and as it relates to
other libraries that you might be using.
We'll take a look at a couple of mechanisms
that you can use to institute this sort of
system. And then we'll talk about some of the
responsibilities and sort of how you should think about
this type of evented system within your app. And
keep things flexible and performant.
So, first up, the pattern. So, when I say
events, you know, there's lots of different people that
think lots of different things when I say that.
Like, some folks that this was gonna be about
event machine. It's not about event machine. So what,
what sort of things pop to mind when I
say evented programming?
AUDIENCE: Asynchronous.
J.C.: K.
AUDIENCE: ActiveSupport notifications.
J.C.: Visual Basic event handlers? Anybody?
AUDIENCE: Nice!
J.C.: I'm sorry to, no. I shouldn't do that.
No.
A lot of people probably think node. Think asynchronous
sorts of callbacks. But that's not necessarily the core
of the type of event that I'm gonna describe
here today. This, these events are not necessarily asynchronous.
They're not necessarily about IO or about distributing things.
They're just a basic pattern to decoupled pieces of
your application.
So it starts off, the main term that I'll
use. Somebody knows that something interesting happens in your
system, I'm going to refer to it as a
notifier. There's a set of subscribers that also exist.
And those are the pieces of your application that
care about that event that just happened. And then
there's some sort of eventing in the middle. Something
that dispatches those events from the notifier to all
of the subscribers.
Now, this is really pretty basic, and you might
wonder, like, why aren't these just method calls? Well,
it's because of that eventing system that we can
get the decoupling. It allows for the notifier and
the subscriber to not necessarily know directly about each
other. Those classes don't have to interact, directly, without
that intermediary between them.
What's the relationship to the, to callbacks, you might
say as well. There's, you know, at ActiveRecord, we
have before and after hooks and around hooks to
do all sorts of different things. Well, this is
similar, but it's a little different from what we're
gonna describe, because of that line, where we have
to derive from ActiveRecord::Base. That's a really tight coupling.
And what I'm gonna describe doesn't put any requirements
on your class, except that it interacts with the
event dispatcher as a collaborator.
So that's the basics of what I mean by
evented patterns. So let's take a look, now, at
how it can influence coupling. So the first example
that I'm gonna draw is about internal coupling. So
when I say that, I mean, things that are
within the scope of your application, the code that
you own and that you've written.
So, working for New Relic, the gem that I
work on, you install it in your application and
it spins up, starts monitoring performance information, and then
sends it back every minute to New Relic so
we can give you pretty graphs and alerting and
all sorts of insight into what's going on in
your app.
When this gem was originally created, we would load
things up when the app starts and we read
a little config file to say what types of
things we want to be doing. You know, there's
features that you can turn on and turn off
and configure. And we knew the story about what
this configuration was when the application had successfully gotten
run.
Well, later on, we added a feature. And that
feature was server-side configuration. So the application now connects
to our server and gets back some additional set
of configuration values. And what this means is that
the point in time at which we could assume
that the application was actually configured changed. If we
had put a bunch of logic directly into our
app start up, like right there in the initialize
where things start running, we now didn't have the
right answer for what the configuration was. We have
to wait until that connect happens in the background
before we can do that.
So let's take a look at what happened with
that connect method. So we make a call out
to the server and, you know, this is obviously
a little stripped down. There's error handling in real
life. But we get back some sort of configuration
from the server. It tells us what those settings
are supposed to be. We pass that along to
finish set up and then finish set up starts
looking like a little bit of a mess. There's
a whole bunch of different concerns here, right. We've
got something about naming rules. Something about cross-application tracing.
Javascript. Some sort of beacon, like. It's not important
what all of the inner details of all of
these pieces are. But none of them are really
related. The only thing about them is that they
care that the configuration is ready. They're wanting to
respond to that event that happened, that the config
is set.
Now, one of the outgrowths of the code being
laid out like this is that we would like
to be able to write some simple tests. Some
unit tests, like DHH tells us we shouldn't, that
look kind of like this. We run our given
set up against a chosen config and then we
can see the results of what came out of
that.
But, in practice, what we end up having to
do is we end up having to stub out
things. Like those different collaborators that were in there.
The cross application tracing. If we don't care about
it in this test, but we still need to
make sure that that code path can run without
tripping anything up or crashing because things aren't filled
out the way that it needs it to be.
Things can get even worse than this. Now that
beacon line down on the bottom, you know, we're
constructing a brand new object. Who, who knows what
that thing might be doing internally. You know? I
mean, nobody's ever written code like this, right, that
does something bad like that from a constructor for
an object. That doesn't happen. But, you know, we
end up having to do things like stubbing out
new. Make it so that objects don't do interactions
and things that we don't really want.
Well, what if we could take this whole method
and just get rid of it in the form
that it's in and instead we notify that an
event has happened. We tell our event system that
the system is now fully configured. We are ready
to go. Everything's logged. We can head on.
Now this has some really nice side-effects. One of
them is that beacon code. Now, in initialize, you
still see the code where we're making this HTTP
callout. But it's not wrapped in this subscribe block.
So it means that that call is only going
to happen when the event system lets us know
that this configuration event has occurred. That provides a
buffer. You can safely create a beacon now and
it's not going to immediately go hit the network.
The tests surround the finished set up method become
very slim and very straightforward as well. All the
finish set up is responsible for is firing that
event. And we check that that event gets dispatched
correctly and that's all that we need to unit
test at this level. And unit testing for the
individual effects that occur, moves over and gets done
in the place that responds to that event. The
thing that is gonna subscribe to the event now
takes the responsibility for testing that.
Now, this doesn't cover everything. There is a need
for an integration test. You do want something that
sees that we wire up to the right events.
But you probably have tests that are gonna exercise
those things anyways, if this isn't a critical path
of your application.
So events allow us, within our own code, to
have more focused tests around those individual pieces, more
independence between the classes. The configuration in the finished
set up no longer has to know anything about
the Javascript instrumentor, the cross-application tracer, the beacon. All
of that is pried apart.
And we found that this also starts to kind
of create a language within your application. A domain
language, if you will. Because these are the important
events that happen for us. We care, as the
New Relic gem, that we are configured. That is
a critical thing for us. And there's now a
name for that. It's not just a bunch of
stuff that's tacked on to the end of one
of our methods.
It's not all roses, though. There is an issue
if you have some sort of ordering that's required
in the things that respond to those events. And
actually we've had a couple of situations where we've
been refactoring code and trying to pull it into
event handlers and discovered that there were dependencies between
different subscribers that aren't really handled by a really
simple event system like this.
So, that's something that you have to watch out
for. If there's a lot of that sort of
ordering, you're gonna have to deal with that somewhere
in your code.
It can also make debugging and reasoning about things
a little harder. It does spread some of those
responsibilities out. Now you don't see one method that
lists all of the things that we do on
connect. You have to go search for things that
subscribe to those events. But in the right cases
I feel like it can buy you things with
the other benefits it brings along that outweigh that.
Lastly, it is a performance consideration. You know, I
would not use this sort of structure in a
tight loop that's gonna get called a million times
a second. You know. If it's something like we're
responding to a configuration event, like, that we are
now ready to go or somebody just signed up.
I mean, it'd be great if you had a
million people signing up a minute, but reality is
it's not happening as frequently as that. So, being
aware of how this will impact your performance is
really critical.
Internal coupling, like within the code that you write
yourself, though, is not the only place where you
can get bound to different things. So in, in
the New Relic gem, we have a lot of
different functionality that we instrument that work through middleware.
So we do Javascript insertion for doing end user
timings. So as your request is going back out,
we'll modify your html and put things in there.
We have error collection. So if there are unhandled
exceptions that occur during your call, at the rack
level, we will actually gather those up and see
that those happened and report them back.
And we do things with HTTP requests between the
applications if both ends of them are running New
Relic. And we need to modify headers, HTTP headers
that are on those requests. And all of these
separate things fit for us very well within middlewares.
But, that makes kind of an assumption that everything
that we want to apply that functionality to is
built on top of rack. And while that's a
pretty good assumption for a lot of things in
the Ruby space, we do support things that aren't
necessarily configured that way.
We also want to avoid people having to set
things up. There are certain cases where maybe you
have to manually add the things that we've created,
and if we have seven different middlewares that you
need to add for all this different functionality, that's
kind of problematic from a configuration view point.
So our solution to this was that we standardized
on having one middleware that we commonly will install.
And we use events to actually do the dispatch
from there. So you'll see this is just a
very basic middleware. When a request comes in and
hits call, we notify on the before and pass
along the environment that we got. We call through
to the app inside and notify after and then
return the results.
So this allows us to be able to plug
things in that maybe are not shaped like rack.
We could use some of that code, like in
the cross application tracing. If we had some web
server that was not rack-based or some configuration that
was not gonna run through middleware, we could still
have it generate a before call from the right
point and handle that with this same code, without
having to deal with trying to create middlewares or
insert something at a point that doesn't really fit.
Now, just for the middleware case, you know, this
probably could have been solved with some sort of
composition. But, for us, it was actually a really
powerful technique for keeping our code decoupled from the
library that we were building on top of. That
looser coupling depends a lot on what sort of
library and what your interactions with it are, you
know. You're maintaining, basically, kind of a compatibility layer.
And the more code that you have to put
into that layer, the more you should probably think
about whether you're approaching it correctly. Obviously you can't
do something like this with ActiveRecord, it's just not
gonna be plausible. But if you have a small
number of points where you interact with another third-party
library, applying events at those boundaries can be a
way that you can keep some separation between your
code and the library that you're dealing with.
So that's coupling. That's sort of the primary motivator
behind using an evented pattern is to decouple the
pieces of your application and allow for those to
be looser and not as away of what's on
the other end of either firing or responding to
an event.
So, let's talk about what it actually takes to
implement this. What does this look like in practice?
So I have created a small gem. This is
sort of an abstraction from what we do in
the New Relic gem. We don't use it directly
and I am not suggesting this for production use.
But it's a really good example of how simple
it is to implement something like this in Ruby.
So there's one primary class. The SimpleEvents::Notifier. When we
initialize that, it's gonna be the central thing that
keeps track of all the events. And everybody's gonna
talk to this notifier object. So we hold onto
a hash, which is where we're gonna store those
handlers from the people that subscribe to us.
Subscribing gets done. Passing in just a key for
what the event is. We typically use symbols within
our application. Some similar things that we'll see later
use strings. And then hands in a block, which
is the code for it to run at that
point that that event gets fired.
And then it's just a simple matter of keeping
track of that list of handlers. Each event may
have multiple handlers, so we make sure the array
is there and add that handler onto the list.
Now there's one other line here that we're dealing
with and that's checking for runaway subscriptions.
That has to do with the fact that for
our usage, there is a limited number of things
that should be subscribing to events. This is not
something where every single web request that comes through
subscribes and hooks itself in. And we'll talk a
little bit later on about some of the negative
side effects you can have if it's happening.
So, for us, if more than a hundred event
subscribers have subscribed to a given event, we actually
have a bug. And so we check for that
and make sure to warn that that's going on.
Your mileage may vary depending on the structure that
you choose to use.
Notifying is a little more complicated than subscribing, but
not too much more. And we'll step through it
here. So first off, we check whether the events
collection has any handlers for the particular key that
we just passed in. And this is one of
the really nice aspects of taking this simple approach.
There's no central registry. There's nothing to set up.
And event, I can create a handler for an
event that doesn't exist or I can start firing
and event that nobody handles, and it all just
gracefully falls out. It doesn't cause any problems if
there aren't handlers wired up for things.
If we do find that there are event handlers,
we simply iterate across those and call passing along
the arguments that we received on the event. For
our particular use in New Relic, we trap errors
and will log and swallow any exceptions that happen
as a result of our direct event handlers. Now,
this is kind of an artifact of the sort
of gem that we are. We're there in the
background and these events are things for the agent
and for us sending our data back.
And the worst possible thing that we could do
is crash an application. So we trap everything that
goes through. If you were doing this in an
application setting, you may want to log. You may
also want to raise those errors and fail quickly
in the case that one of your notifiers has
something go wrong. But your mileage, you know, you
can choose what the right policy is for how
you're using events and what setting you're putting those
in.
So that's it. Like, that's an entire eventing system
is about fifty lines of Ruby, which allows you
to decouple those different pieces of your application. But
the fact of the matter is that you might
not have to even write that system yourself if
you're using Rails. ActiveSupport::Notifications have been baked in from
I think about the 3.0 version of Rails. And
it's a system that's very much like what we've
described here.
I'll abbreviate in the slides a little because ActiveSupport::Notifications
gets really, really long in the code, so, we'll
scrunch that down a little bit. But all sorts
of things flow through this evented system any time
a Rails request is occurring. So we get things
about the dispatch at the top level. We get
things about the controller and what it's doing. We
get notifications about SQL calls. We get notifications about
view and template rendering. Like, tons and tons of
events are streaming through this ActiveSupport::Notification system all of
the time.
Some common things that you might be familiar with,
like the log, the debug log that you get
of your queries, that's running through ActiveSupport::Notifications that are
fired by ActiveRecord. So what's this look like?
It looks very similar to what we've seen before.
So subscribing to an event, you give it the
name, you give it a block, and then you
can see the form of what one of these
events was that came through. It'll hand it to
us. It's got a name. It's got an identifier.
And then it's got this hash that's sort of
a payload of data that comes along with it.
ActiveSupport also provides a simple wrapper for handling that
payload and handling those events when they're in a
very common form. So most of the things that
Rails sends out, they're gonna have a name. They're
gonna have a duration and a payload in a
standard format. So this class kind of wraps up
that access and makes it a little neater.
On the notifying side, the method that you call
is instrument. So if you want to fire an
event and have that be sent out to the
system, you just give it the string for the
name, give it whatever payload for the data, and
you're off and running.
There's also a nice form of it that it
has that will wrap your code in a block,
so you can do something in there, and what
that'll do is that'll tack a duration on. So
it'll time what's going on. And this is one
of the key things that ActiveSupport::Notifications are used for
within Rails is timing how long those different things
take. Those timings that you see in your debug
output are all coming from things being wrapped by
this sort of instrument call.
As with all things Rails, there's a lot more
richness as well under the surface. You can do
subscriptions by regular expressions. If events are nested, the
ActiveSupport::Notifications system keeps track of that and will take
care of letting you know so you could look
and see whether an event was occurring within another
event, and you can go crazy if that's happening
too much, but.
There are temporary subscriptions and unsubscribe, which is useful
in certain scenarios where, maybe you do want to
have something hook into the ActiveSupport::Notifications for the course
of a request. But you don't want to have
that handler hanging around and responding for the rest
of the lifetime of your application.
And there's some really great examples that you can
find in Rails itself. So the log subscriber class,
which is the thing that spits out a lot
of your development log, is a great example of
how you can hook into that underlying infrastructure.
So that's the mechanism. That's how you can build
events in or use a library that you're probably
already using to get a lot of these things
done.
Let's talk a little bit more in detail about
the different parties that are involved here, and what
they're responsible for and considerations there. So notifiers have
a lot of responsibility. They're, they're kind of the
key points in your application. You should be, in
this scheme, you would be adding events for the
things that matter the most.
And on that, I think that naming is a
really critical thing around this to get the benefit
that you're looking for. So in the example that
I gave up front, you'll, you probably didn't notice,
but I named the event configured, to say, hey,
we are in a state where we are fully
configured.
I didn't name it connect to server or finish
with the set up or, you know, binding it
to the mechanism of what it was. I couched
it in the terms of what mattered to my
application. I think that that's something that's really powerful
if you do this right. If you, it allows
for changes in the location, like in our case,
that change of when we were configured. It moved
from just plain application start up to a point
in time in the future when we may hear
back from a server. And who knows when that
configured point might change in the future?
We're not bound by the event name to some
particular method or point in time. We can change
that and still have it make sense within the
domain of what we've created.
It's also important to consider what your payload is.
What data you're sending along from the notifier. So
Rails follows the convention with ActiveSupport::Notifications that I would
definitely encourage, of sending along primarily a hash of
different values that you can pipe through. I learned
this the hard way the second time that I
needed to add a bit of data onto one
of my events. And the mechanism, as we saw,
just sort of percolates the arguments along that we've
given it.
Well, if you add another argument, all those other
handlers downstream need to get updated. So do it
from the start to make these things flexible. Pass
your payloads along as a hash that you can
then add additional data that old event handlers don't
need to care about and that new things can
pick up along the way.
On that topic of the payload as well, if
you're writing some sort of internal application, you know,
you can put whatever you want into these payloads.
But as a third party, you may want to
be cautious if you're having other people, other gems,
and other codes subscribe to these events to try
to keep your payloads as primitive as you can.
Maybe even all the way down to just base
types.
The advantage here is that you are, if you
put one of your internal classes as something that
you're packing around in this event, and then broadcast
it out to the world for everybody to use,
you now don't know who might be getting access
to those objects and using them. And depending on
what sort of usage you've got with your, your
app, it can be difficult to make changes to
things that maybe you consider to be internal details.
If you turn things into primitives, you know, it,
they're just strings and hashes and numbers, those are
pretty safe, right. You can maintain that mapping from
your internal objects to those primitives and not have
to worry about who might be changed when you
refactor.
So the other end of things is the subscribers.
I think it's really important to consider, carefully, who
the subscribers are for a given event. This pattern
doesn't fit everywhere. This is not something where every
single thing that happens in your application should be
an event. And if you're writing an event, and
there's only one thing that's gonna handle it, you
might be misapplying this pattern. You might not get
the benefit that you need, there.
A lot of times, you'll find that there is
some natural home, some place within your app that
is already there that is the right place to
be subscribing to that event and responding to what's
happening. But if you don't, it's an, there's some
interesting ideas around how you can create what that
location is. So there's this gem that I've run
into, it's called mutations. It's sort of building command
objects that represent different actions that happen in your
system.
Now, you know, you might recognize something very like
this from earlier in the morning, but it can
be an interesting idea if you have key pieces
of your system where a workflow or something that
occurs is really important. You can write something, a
command, that encapsulates what the event is, what that
action is that needs to be taken. And this
can be something that helps in those cases that
I described, where there may be implicit ordering. There
may be ordering between different components. You could create
a command that wraps those up in the right
ordering to be applied and then have that be
what responds to the events.
Probably one of the biggest difficulties that you can
run into with an evented pattern is, if there's
too much nesting, if this gets overused, and events
trigger events and trigger events on all sorts of
different things, it can become very difficult. And the
main advice that I have there is that this,
this technique works best, I think, at the critical
lynch points of your application. The things that you
really care about the most to be providing, that
sort of visibility and language around.
And another thing to be aware of with the
implementation that we've done here. I've had a number
of poeple come up afterwards and point out, they're
like, it's all still synchronous. It's all still just
running. I mean, this is just splitting up where
those things are. And that's totally true. This is
not as described something that will make your application
parallel or asynchronous in how that event handling happens.
But it might give you good points in time
when you could decide to push things off. Things
could go to background jobs or things could get
dispatched to all sorts of other places if you
wanted.
It's also important with subscribers to watch out for
leaks. There is, when you create a block, so
like, if this subscriber was created for every web
request, the fact that we have that block potentially
holds onto a reference to this object and keeps
it alive past a point in time when you
might have thought that it was gonna go away.
I've seen this not just in Ruby but in
a number of different languages with these sorts of
evented patterns with handlers that get subscribed, where they
can hold references to objects that you might not
be expecting them too. And so you gotta watch
out for that to keep things from leaking.
So those are some ideas around the responsibilities that
your subscribers and your notifiers should have. And I
hope that maybe this has opened your eyes a
little bit to some ideas about how you might
be able to partition your app differently. What are
those responsibilities and things that happen during your app
that would be worth having an event or worth
giving that name to.
And as you can see that it's a pretty
simple thing to implement that can give you a
lot of benefit down the road. Thank you.