JOSH SZMAJDA: All right. You guys ready to talk about SOA? Woo!
I'm Josh. Hi. How you doing? I'm the CTO
at Optoro. We make software for retailers. Help them
deal with their stuff at scale. I'm working on
a project called RACK-AMQP. RACK-AMQP is a collection of
different projects, including an RC, some client libraries, and
most notably, Jackalope, which is a new Ruby web
application server, like Unicorn, Passenger, Thin, or, or anything
like that. Except that it's special-purpose. It's for service-oriented
architectures. And it doesn't actually speak HTTP at all.
So. Why am I doing this crazy thing? Well,
my app drove me to it. My app is
huge. It's really complicated. This is kind of a
services diagram of my app. Or where we'd like
to go with it.
There's over three-hundred and fifty models in one Rails
application. The test suite takes over eight hours. It's
kind of a headache. You could put it. So
we decided the way to move forward with it
is to break it up into smaller applications that
work together. And, of course, that means SOA.
Now, I mean, SOA is really wrapped up in
a lot of pretty awful baggage, when you come
down to it. SOA, you think about enterprise service
buses. You think about strict interface definitions. Versions. I
mean it's a lot of complexity that you have
to worry about. Extra load balancers. More configuration. More
distributed tracing and monitoring. I mean, it's a, it's
a real pain.
So I, I hate SOA, right. It makes me
feel like this. So, but when you get down
to it, SOA is actually not all of that.
SOA is really just independent actors communicating. They're working
together to get something useful done. So, really, SOA
can mitigate complexity by enforcing simplification. Each service only
has to think about its own little bubble. It
only has to worry about its little component. And
it doesn't have to think about anything else that
goes on in its entire system.
You can let your system architect worry about what's
going on across the whole thing. All you have
to do is pay attention to your one little
service and you can be pretty happy.
So, when you get down to it again, SOA
is really just independent actors communicating. Now that communicating
bit is important. How they communicate actually determines how
your application behaves. And the kinds of communication you
can use impacts the kind of application you can
write.
So, the typical communication pattern that we're most familiar
with as programmers is sort of direct messaging. I
am an object. I can send a message to
my friend. My friend is gonna give me back
a response. It's very normal. It's what, in fact,
we use all the time with HTTP. But when
you think about it, what we actually want to
do is be able to send different kinds of
messaging structures. We want to publish that something happened
in my system and have people subscribe to that
event and take some interesting action.
It's all about decoupling. I don't have to know
about my neighbor anymore. I just need to know
that I did something, and then somebody else might
care that, oh, you did something. That's great. Let
me do something about that.
But again, like I said, let me. We're Rails
developers. We're used to writing web applications. We speak
HTTP all the time. HTTP allows us to do
direct messaging, so that's what we tend to do.
I'd say most SOAs out there, when they get
off the ground, they start building web applications that
speak with each other through HTTP. It's what we're
familiar with.
But what would we actually like our transport mechanism
to be? We want it to be, you know,
data structure independent. HTTP specifies that there be a
body, but it doesn't specify the format of the
body. We want it to be fast. We want
it to be open, transparent, understandable, clear. You know,
debuggable. Observable. And flexible.
So, is HTTP all of those things. We also
need, not only direct request response type of messaging,
like RPC, but we also need pub, subscribe, and
broadcast. Additionally, we don't need more load balancers. More
SSL certificates. Distributed logging. Trying to figure out what's
going on in this service over here versus that
one over there. It's a nightmare.
And, you know, looking up the various services in
your system, it's a pain. So, again, I mean,
HTTP, you know, it has, it is blind. It's
fast. It's open, clear, it's well-understood. It's what we
know. It's really great for RPC. It's a better
alternative.
And that's AMQP. AMQP is blind, also. It doesn't
care what data you're transmitting across it. It's extremely
fast. It's an open system. It's great for all
messaging pattern. Not just direct messaging, but pub/sub, and
queue-based messaging. It's centralized, which in terms of a
service-oriented architecture is very beneficial, in that you have
one point to manage, one point to pay attention
to everything at. And the only issue is that
it's not what we're familiar with. It's not what
we know. You know, again, we're web developers. We're
not AMQP developers. At least not the most of
us.
So it's a challenge.
So the RACK-AMQP project that I'm working on is
really designed to make AMQP simple. We are trying
to introduce as few opinions as absolutely necessary in
order to make this possible. And to give you
a stepping stone into the world of AMQP where
you can start to integrate more advanced concepts into
the system, without having to relearn your entire world.
So it also lets us continue to build what
we know. Because it's built on Rack, you know,
we're web-developers. We can continue to use the web
concepts that we're familiar with. We can developer locally
on our web machines using the systems that we're
comfortable with. And then when we deploy, we simply
deploy it to Jackalope, instead of to Unicorn or
Passenger or whatever, and it's magically in this world
of AMQP.
So. How does that work? Well, let's look at
AMQP a little bit more in depth, first. So
AMQP is the advanced messaging queue, message queueing protocol.
It is an open standard. It's developed by Oasis,
which is an open standards body. It, again, like
I said, uses a centralized distribution pattern. That centralized
distribution pattern allows you to do the various kinds
of messaging that are really handy for us. The
pub/sub and the other interesting thing. This implies that
we have a broker in the center that all
of our services connect to and transmit messages through
that broker. And that broker can make intelligent decisions
about how to route those messages.
AMQP messages themselves, they have headers and bodies, just
like HTTP. There are well-known fields, like content type
and priority, in this case. And we can actually
leverage the conventions that we understand from HTTP to
pull AMQP into the world. So AMQP, like I
mentioned, has these headers and bodies. It has well-known
fields. And HTTP essentially has headers and bodies and
also well-known fields. So we can essentially emulate HTTP
over AMQP without too much trouble. And, again, the
AMQP supports the RPC messaging pattern, which is what
HTTP is.
So, one of the parts of the project is
the AMQP HTTP RC, which, it simply does as
little as it has to to define the structure.
It's just a documentation on how we're doing the
mapping. One of the other goals is that we
want to make inner-operability with other languages and other
platforms really easy. So having an RC will allow
it, allow us to have just a central document
to refer back to in constructing other servers.
So let's look at HTTP a little bit more
in-depth. How does HTTP work? Well, you know, when
we send a request, we send a specific request
to a server, to a, you know, specific IP
address and port. And we get back a response.
And, of course, the request has things like the
HTTP verb, the path, headers, the bodies. And the
response comes back with a response code.
It's all stuff we're familiar with. So all we
had to do is map that into AMQP terms,
which is essentially what we've done here. And this
is just a reference for just a few of
the things that we're doing. Like content type. There's
already a content type header in AMQP, so we
just reuse that. Some things didn't make sense. Like
the protocol for example. In HTTP you've got HTP
or HTPS or HTTP plus dev or whatever. But
with AMQP, your protocol's already negotiated when you connect
to the broker. So it doesn't really make sense
for us to worry about that in the messages
we're passing around the system.
The host name becomes, essentially, the routing key. The
queue target that we're sending to in AMQP. And
it makes it really simple. So, this looks like
this, essentially, in code. This is an example of
how you could write a client that speaks AMQP,
HTTP. You create a call-back queue. We'll get back
to that in one sec. You publish a message
that looks like an HTTP message to the target
queue. And then you wait for a response.
Now, the callback queue is actually a really important
thing here. AMQP is a hundred percent queue based.
It's all asynchronous under the hood. But, you know,
we want to write a synchronous system. HTTP is
synchronous. I send a request. I wait for the
response, or receive the response and move on. So,
to do that in AMQP, we create a respond
queue that we are going to listen to in
the broker. It's an anonymous queue. There's a great
convention. We're doing this in AMQP. It's very easy.
So we create a response queue, we get the
name of that queue, and then as part of
the message we send to our target, we say,
here's the response queue to reply back at. And
then we simply wait for the response on that
call-back queue to come back. So that lets us
get the synchrony that we're familiar with, that we
need, with HTTP style programming, while still having an
asynchronous system under the hood.
RACK, of course, we're all using RACK today. Even
if you're not familiar with it. RACK, of course,
is what runs all Ruby web application servers. Turns
out that RACK actually doesn't care about HTTP. All
that RACK really defines is that you had defined
a call method that receives an environment, which is
basically a hash, and it responds back with a
three-element array. The HTTP response code, headers, and the
body.
That hash doesn't actually have to be anything but
a hash. It just has to look like an
HTTP environment. So emulation was actually pretty simple. So
this is basically how it looks. We subscribe to
a queue on the server, and then for every
message we receive back for that queue, we unpack
some things into variables. We create what looks like
the HTTP environment and we have the request method
and query string. If you ever wrote CGI way
back in the day, this might look familiar to
you.
And then we essentially pass that environment onto our
RACK application, receive the response from the RACK app
and then publish that back to the response queue.
So this is what Jackalope is doing for us.
Jackalope is emulating HTTP for our RACK applications. And
it just works. I'll show you a demo at
the end. You don't have to modify your code
at all. You can just simply deploy to it,
and it's speaking AMQP instead of HTTP. So that's
it. We're done. We can go on vacation, right?
Well. I didn't talk about how to actually put
this in production, other than just use Jackalope. So
you have to choose a broker in AMQP. So
what we're using is RabbitMQ. RabbitMQ is a well-known
AMQP broker. If you're doing any AMQP you're probably
using RabbitMQ, most likely. It's used by giants out
there. I mean, Google, VMWare, NASA. All these people
are using Rabbit pretty heavily.
It's extremely scalable. It's fault-tolerant. It's distributable. And it's
secure. It does everything you'd ever want. And it
also gives you a really great management console. We
can go in and see what's going on in
your system. Like I mentioned, the distributed logging we
saw in the, in the last talk, if you
were here for that, trying to get an idea
of what's going on across your system can be
challenging. Rabbit doesn't tell you everything you'd want to
know, but it at least gives you an idea
of what queues are being used. Their depths at
the point. The kind of information that can help
you get, at least get a handle on it.
Additionally, you're gonna have to think about how you
talk to the real world. I mean, AMQP is
all well and good behind the scenes, but how
do I continue to interact and serve my clients
that I actually care about, to the point of
getting paid, right.
So this is the architecture we typically use at
Optoro. We have our Rabbit server in the middle,
and all the various services sitting on Jackalope that
talk to Rabbit. And then additionally we have one
or more API services that are continuing to be
deployed on Unicorn, in our case. They talk to
the outside world and internally translate the, the needs
through AMQP and the rest of the system.
One of the things we actually published for each
of our services is a client gem that kind
of isolates the worry, the worry about exactly which
transport we're using away from the consumer, the API
here. So it actually is, is really simple.
And so the way that we do that is
we typically have been using HTTParty for our HTTP
communication needs. So we wrote, as one of the
other projects part of RACK-AMQP, is AMQParty, which is
just a API-compliant version of HTTParty. So we can
actually just drop in the AMQParty constant in place
of the HTTParty constant, and you can see down
below, that transport variable, we typically configure that at
boot time to be either HTTParty or AMQParty. And
everything, again, just works.
We change the, the url a little bit, too.
But we do that all part of the, part
of this setup for each server. For each client.
Of course, if you don't want to use HTTParty,
some people like the various other options that are
out there, we also are publishing RACK-AMQP client, which
is what H, AMQParty is built on. It, it's
a very simple API. You get a client, you
tell it which Rabbit to connect to. You send
a request to your target queue with your uri
as part of it, with any, you know, your
HTTP method and all that. You get back your
response. It's synchronous. It's simple. It's actually built on
Bunny, which is a really great AMQP gem out
there, and that also is very easy to use.
Additionally, we're publishing a sample SOA using Rails. It's
a work-in-progress at the moment. BUt the userland and
userland_client are mostly built as, you can see where
things are at the mo- at the current state.
It kind of gives you an idea about how
we think about SOA at Optoro, and how you
might be able to use Jackalope and the RACK-AMQP
project in your own projects.
So userland is a, is Rails service that essentially
publishes a user's concept out into the world. And
then userland_client is a gem that consumes the userland
service and interface uses the userland gem to talk
to the userland service.
And so then, you know, how fast is this?
Well, there's this bench mark I've got. I'll show
you it in just a sec. The only weird
thing we have to do here is tell AMQParty
which Rabbit to talk to, and you kind of
saw that in the, in the client gem a
little bit, so that's a little bit of setup
we do. Again, like I said, when we make
the decision about which, whether HTTParty or AMQParty. So
we tell it which Rabbit. And then, inside, we,
five hundred times, request this JSON using AMQParty and
then five hundred times request the same thing using
HTTParty.
So here, I'll show you that in, in use.
Let's see. OK.
Let's make this big. All right. So here I'm
gonna boot the app using Unicorn. So Unicorn is
listing on port 8080. Wow. That's really big. Trust
me, it's 8080 there. I'm gonna boot the same
map using Jackalope. The only difference I have to
do to boot the app is I have to
say with the right queue name, so it's going
to listen to the test dot simple queue.
So there it is running. And then down here
I'm gonna starty the bench mark. And like I
said, it's gonna five hundred times hit each service.
What it actually does is hit each service three
times, just to make sure that it's warm, and
then it can go through and hit it all
the way through. And there we go.
So you can see, it's two point nine seconds
total to do five hundred AMQP requests, and three
point six seconds total to do five hundred HTTP
requests. I was actually surprised when I got this
result. Jackalope is beta. I mean, it's, we're using
it at Optoro, but it's not really been battle-tested.
But it's faster, at the moment. So why is
it faster?
Well, maybe I haven't written all the code yet.
But more likely it, more likely it's because of
this concept in TCP called the slow-start phenomonon. And
also, your whole TCP negotiation. So each one of
these five hundred HTTP requests, close the connection, open
the connection, send the message, receive the response. When
you open the TCP connection you go through a
whole sinaq phase, which I didn't put slides in
for and maybe should have. But it's.
I highly recommend checking out Eliot Gregorich's talks, by
the way, about how HTTP works, how TCP works.
But basically, you go through protocol negotiation for any
TCP connection, and then there's a feature of TCP
called the slow-start feature, which essentially allows you to
negotiate your connection speed. And that's really great when
you're talking over the internet, and you're not sure
of the latency or the availability of routers to
be decent along the way. But when you're doing
internal applications, it just gets in your way. It
just slows you down.
So AMQP, we hold a persistent connection to the
AMQP broker. We do one sinaq cycle, and then
we get our maximum connection speed and we stay
there. And then we can multiplex information over that
single connection and it works really well. Now, you
could write that with HTTP if you did a
keep-alive connection, held over that connection and talk to
the same thing back and forth.
But, the bottom line is that most of us
don't do that when we write HTTP. We're using
something like HTTParty or, I always forget the tiforce
or tif- whatever it is. Sorry.
Most of the time, you know, we drop the
connection, you know, in between requests. We don't hold
it open. It's just not something we think about.
So using AMQParty or any of the libraries that
we're published, it holds that open and it just
makes it more efficient overall.
So I hope that's the primary explanation of why
it's so much faster. So I just wanted to
mention a few references, a few things that were
really inspriring when we kind of went down this
road. You know, this is really a departure from
what we're used to in Rails. And the Architecture:
The Lost Years talk and Matt Wynne's original Hexegonal
Rails talks were all about departing from what we're
used to, I think. So those are really interesting.
If you haven't checked those out, definitely do.
Additionally, Martin Fowler posted a recent article that was
awesome, called Micro Services. Definitely, definitely read this if
you are looking into service-oriented architectures. It lets you
not think about that heavy-weight bloat that we think
about with SOA.
It's really a revisitation of the idea of, just,
you have independent services that are collaborating to get
something done in a very simple way. And that's
really, again, also influenced the direction we're taking here.
We want to introduce as few specific choices as
possible to just kind of give us a simple
transport to, to build on top of. Additionally, the
Ruby AMQP gem has some awesome docs about how
AMQP works, how to use it with Ruby. And
RabbitMQ also publishes some really great documentation on what
AMQP is, how it works. How to use it
in the organization in your applications.
And there's also this really great article I found
about how HTTP works, in general. It's, I think
a piece of school material from a university in
Singapore. Definitely check it out. It's got really great
diagrams.
Thanks. Again, I'm Josh. I'm at jszmajda on the
Twitters. I am the CTO at Optoro. We have
blinq dot com. We have retailers with their returned
goods and excess goods, help them figure out what
it is, what the current value is and move
it quickly.
Additionally, I am the host of the Ruby hangout.
Just a quick blurb for the Ruby hangout, it's
a online meetup. It's really handy when you can
go face-to-face and talk to people if you have
a local meet up. If you don't have a
local meet up or if you're too busy or
if you can't make it for whatever reason, the
Ruby hangout is an online meet up to, essentially,
give you that local meet up feel, as much
as we can online. So check that out. It's
a lot of fun. I'm one of the co-organizers
of the DC Ruby Users group. And I want
to give a quick shoutout to Jonathan. Thanks Jon.
He's on my team, helped me get Jackalope over
the, over the finish line. So super helpful on
that.
All the code's on GitHub. Github dot com slash
rack dash amqp slash rack dash amqp is where
the RC lives and has bookmarks to the other
parts of the project. And I would love to
take your questions, because that talk was way short.
And I've got, actually, a whole bunch of stuffed
Jackalopes up here for good questions. So please feel
free to come grab one.