-
Not Synced
Thanks Mike.
-
Not Synced
Thanks for the invitation.
-
Not Synced
I want to start with a picture from this
building, which I have to say
-
Not Synced
I love this venue,
and especially this building.
-
Not Synced
And especially when it's not on fire.
-
Not Synced
And I think we were really dumb yesterday.
-
Not Synced
So if it starts beeping,
I will be leaving this time.
-
Not Synced
If you have not done it,
I suggest you walk up this round arc—
-
Not Synced
you can walk all the way up to the top—
-
Not Synced
and on that walk you will see that stairs
somewhere that says "The Beginning".
-
Not Synced
I want the beginning of my talk to be the
end of my last talk on a very similar topic.
-
Not Synced
And that was an idea that I was proposing
-
Not Synced
and that was the realization first of all
that every gem that you start,
-
Not Synced
the gem starts with a namespace.
-
Not Synced
When you develop a Rails app,
you never start with a namespace.
-
Not Synced
And so if you compare the two,
a gem is like a box.
-
Not Synced
You put a little label on it.
-
Not Synced
And a Rails app is a
gorgeous infinite nothingness
-
Not Synced
with all the bells and whistles of
Active everything.
-
Not Synced
To get a gauge of how relevant that is,
who writes Rails apps on a daily basis?
-
Not Synced
That's about two-thirds, maybe three-quarters.
-
Not Synced
And who writes Ruby,
but never touches Rails?
-
Not Synced
That's way less. Okay.
But still a few.
-
Not Synced
So I have a feeling that for the second
group, what I'm going to say is going to
-
Not Synced
apply a little bit less.
-
Not Synced
Maybe because of Rails not being around.
-
Not Synced
So the idea from my last talk was:
the next time you start an app,
-
Not Synced
just put everything in a namespace.
-
Not Synced
And I end it with what I found to be an
awesome finishing slide,
-
Not Synced
give yourself a box so you can start
thinking outside of it.
-
Not Synced
With the idea that if you have a gem,
and you give something a name,
-
Not Synced
and you put things inside of it,
you can open that box,
-
Not Synced
and you can go WTF when you find something
that obviously doesn't fit in the box.
-
Not Synced
If you try to do that with a Rails app,
-
Not Synced
you don't have a box—
you don't have a name—
-
Not Synced
so everything is going to fit.
-
Not Synced
The best game you can play is
what doesn't belong with the others.
-
Not Synced
Red. Green. Loud.
What doesn't belong with the others?
-
Not Synced
And you're going to know that it's Loud.
-
Not Synced
But if you look at your typical Rails app,
what are you going to have?
-
Not Synced
You're going to have a User, a Company,
Roles.
-
Not Synced
And then, the Tulip pricing engine that
you wrote.
-
Not Synced
And then the marketing part for that
and a store.
-
Not Synced
You're going to tell me that you can still
play that game and find out what may or
-
Not Synced
may not belong to this app?
-
Not Synced
It's very hard, because it kind of pushes
us in the direction of just putting all of
-
Not Synced
app in there.
-
Not Synced
So what I want to talk about today is
Component-based Ruby
-
Not Synced
and Rails Architectures.
-
Not Synced
I promise you I put this slide in before
that happened.
-
Not Synced
(Laughter)
-
Not Synced
I may want this button more desperately
than you. Trust me.
-
Not Synced
Stop me if I'm going too fast.
-
Not Synced
Because I want to talk about
large applications.
-
Not Synced
And one thing I heard about them is
you just never build them.
-
Not Synced
So I work for Pivotal Labs, and we
start a new project every few weeks,
-
Not Synced
and many of them are greenfield,
so many of them are tiny when we start.
-
Not Synced
But you heard from Matt yesterday,
we have pretty big apps as well.
-
Not Synced
And for every app, since our definition
of success is that a client can stay
-
Not Synced
successful after we leave,
what we have to strive for is some kind of
-
Not Synced
architecture where you can develop an
application well over time.
-
Not Synced
So we have to think about the app being
larger than it is, while we develop it.
-
Not Synced
Despite TDD and all.
-
Not Synced
Sandi Metz wrote her awesome book last
year and gave a few talks.
-
Not Synced
And at the one that I was at—
I don't know if she said it in other talks as well—
-
Not Synced
but she said, "Your app is out to kill you".
-
Not Synced
The growing complexity of your app is
going to chase you
-
Not Synced
and if the app were a bull,
you might feel like that guy.
-
Not Synced
And that is very exciting.
-
Not Synced
For the next second that is exciting.
-
Not Synced
For the next hours or days that is painful.
-
Not Synced
Okay, maybe this is a bit drastic,
but who has felt like their app was
-
Not Synced
behaving like that bull to them?
-
Not Synced
I have felt like that as well.
-
Not Synced
They are not typically that fast.
-
Not Synced
Quite the opposite.
-
Not Synced
But they behave like something
that is too big to handle.
-
Not Synced
So what I want to get to, is for us to
be more like this guy.
-
Not Synced
Calmer. In control.
With sunglasses and a hat.
-
Not Synced
(Laughter)
-
Not Synced
But I don't really like bull fighting,
so I actually want us to be this guy.
-
Not Synced
(Laughter)
-
Not Synced
Just hanging out.
-
Not Synced
I want to go home at five on a Friday night.
-
Not Synced
And I think this guy is probably the one
who is going to do that the most successfully
-
Not Synced
because he is just fine
probably all week long.
-
Not Synced
This "never build large apps" is part of a
larger quote from Justin Meyer:
-
Not Synced
"The secret to building large apps",
he says, "is never build large apps."
-
Not Synced
"Break your applications into small
pieces. Then assemble those testable,
-
Not Synced
bite-sized pieces into your big
application."
-
Not Synced
But how?
-
Not Synced
He was—I think—talking about JavaScript.
-
Not Synced
I'm talking about Ruby on Rails.
-
Not Synced
You can tell from the font that you're
supposed to read this slide.
-
Not Synced
(Laughter)
-
Not Synced
I'm going to use a tiny sample
application throughout this talk.
-
Not Synced
And this is where you can find it.
-
Not Synced
If you can't read this after all, just
search for github and the next big thing
-
Not Synced
and I think it comes up first.
-
Not Synced
I promise this thing is tiny
-
Not Synced
and it does only one thing.
-
Not Synced
It's an announcement page.
-
Not Synced
You can announce whatever you have to
announce.
-
Not Synced
And people can sign up for updates.
-
Not Synced
I spell it with two Ns but I don't know
if my Twitter handle is only one.
-
Not Synced
That's how often I tweet.
-
Not Synced
So the service thanks you for signing up
and I just press return.
-
Not Synced
And when I made this sample app
I had to come up with
-
Not Synced
a little bit more than just that
to actually have something to move around.
-
Not Synced
So I realized pressing return makes it
really easy for me to do that
-
Not Synced
many, many times.
-
Not Synced
But of course I only want my email address
to be registered once.
-
Not Synced
So I thought I'll give the server the
ability to kind of feedback the level
-
Not Synced
of annoyance that it feels while you're
just continuing to press return
-
Not Synced
and annoying it.
-
Not Synced
At some point it'll just freak out.
-
Not Synced
The freak out picture didn't come.
-
Not Synced
Let's try again.
-
Not Synced
It's an awesome picture of a guy with
a lot of hair.
-
Not Synced
I need your help though.
-
Not Synced
I need you to imagine something
really big.
-
Not Synced
Because that app is tiny
and I'm going to make examples
-
Not Synced
of refactorings or rearchitecturings that
don't make sense in that app.
-
Not Synced
So bear with me and always imagine
something very big.
-
Not Synced
I'm going to go through eight ways of
architecting this application.
-
Not Synced
And I have roughly two minutes for
every one.
-
Not Synced
Let's start with number one.
-
Not Synced
If you checkout that GitHub project,
you will find those eight steps as tags
-
Not Synced
and you can roughly follow the steps that
I'm taking.
-
Not Synced
The first one is a normal Rails application.
-
Not Synced
It's all-in-one.
-
Not Synced
Let's look at what's happening.
-
Not Synced
There is a TeaseController here.
-
Not Synced
It has two actions: new and create.
-
Not Synced
Let's briefly see what happens.
-
Not Synced
No surprises. It's not a well written
method. John would refactor this.
-
Not Synced
We're trying to find the entry that you
gave this form.
-
Not Synced
If we find it, we update tries on that
entry.
-
Not Synced
When we put it into this annoyance meter
it'll tell us how annoyed we are
-
Not Synced
at this point, and we'll feed that back
to the client.
-
Not Synced
If we don't find the entry,
we'll just say, "Thanks for signing up."
-
Not Synced
And then there's a bit of catching
unexpected cases.
-
Not Synced
That's pretty much what's going on in
this controller.
-
Not Synced
There is a model back in this.
-
Not Synced
It's Entry. It has the email address
and a number of tries.
-
Not Synced
And there's this AnnoyanceMeter,
which is not really important.
-
Not Synced
It just derives new strings
out of these counts.
-
Not Synced
I suspect that everyone has seen this.
-
Not Synced
And I will—for this talk—assure you
that it every one of these stages
-
Not Synced
you can run the tests, and—if I didn't
pick the wrong commit—they will pass.
-
Not Synced
So I have tests, but I won't show them.
-
Not Synced
I will just be moving them around for
every stage
-
Not Synced
and adding new ones where there is a new
test to be written.
-
Not Synced
Now remember, I said at the beginning of
every Rails app it's kind of this infinite void.
-
Not Synced
A good picture for that is probably
a big dump site.
-
Not Synced
I've already asked you if you ever felt
like the guy in front of the bull,
-
Not Synced
and maybe you also felt like the driver
in that bulldozer or whatever that is.
-
Not Synced
The reason I'm saying that about this tiny
app is that there's two things in there
-
Not Synced
that are now indistinguishable—if you look
at it from 30,000 feet—as to what they do,
-
Not Synced
how they're connected,
if they have any structure.
-
Not Synced
It pretty much doesn't exist.
-
Not Synced
So for all intents and purposes—
if you just multiply, extrapolate from these two
-
Not Synced
classes to, say fifty or a hundred—
you have no clue
-
Not Synced
who's interacting with whom,
what's going on, and why.
-
Not Synced
Unfortunately this doesn't
look very chaotic.
-
Not Synced
But if I were to write a hundred class
names here, it would be chaotic
-
Not Synced
and you would not be able to assess any
sort of structure
-
Not Synced
just by looking at those classes.
-
Not Synced
A first way of getting structure
into this application is modules.
-
Not Synced
The plus we get from that is
a higher level structure.
-
Not Synced
And you've all probably seen this one too.
-
Not Synced
Not much changes.
-
Not Synced
The controller doesn't change at all,
except it now references to Entry and
-
Not Synced
the AnnoyanceMeter within their
namespaces.
-
Not Synced
In the AnnoyanceMeter, Annoyance left the
class name; it's now in a module.
-
Not Synced
I moved some other stuff around
that was mainly for testing.
-
Not Synced
We now have this structure.
-
Not Synced
And if we come back to this analogy
then maybe we have moved
-
Not Synced
from a dump site to a recycling yard.
-
Not Synced
Recycling yards are awesome.
-
Not Synced
I should know this because I remodeled my
house, and you visit those places very
-
Not Synced
often when you do.
-
Not Synced
You can point to a corner of the recycling yard and say, "That's where the scrap metal is."
-
Not Synced
However if you've ever gone to such a place, you also know that you stick the hard-to-recycle stuff under your car because you're not a nice citizen
-
Not Synced
and then you try to pawn it off to the recycling yard because you just want to get rid of it and it's so hard to get rid of.
-
Not Synced
People do that all the time.
-
Not Synced
Just stand in front of the clean wood and then suddenly, not-so-clean wood gets thrown on top.
-
Not Synced
What does that mean for code?
-
Not Synced
It means there is structure.
-
Not Synced
I have now an EmailSignup module and an Annoyance module.
-
Not Synced
And in that is more classes.
-
Not Synced
But there is no guarantee that these classes are a) independent, or b) actually doing what they say.
-
Not Synced
I can't prove it.
-
Not Synced
That's why I put the rectangles over each other.
-
Not Synced
But at least I didn't have to write Entry anymore because I now have a higher-level concept, that being this EmailSignup module.
-
Not Synced
If I want to improve on this, I would like to go to the next step, which is prove that these two pieces are independent.
-
Not Synced
I call it the gem component app.
-
Not Synced
I may need to explain a little bit more about this step.
-
Not Synced
First off, you're not seeing that engines folder right there.
-
Not Synced
We're not looking at that.
-
Not Synced
We're looking at the annoyance gem.
-
Not Synced
I made a folder gems and I put an annoyance folder in there.
-
Not Synced
Actually this is a complete folder structure of a gem.
-
Not Synced
We have a gemspec; it defines our annoyance gem—and I should probably fill in this data—but there is a Gemfile which tells us that we are running tests with RSpec and magically—this may be the only time we need to look at tests—I have tests now in this gem.
-
Not Synced
If I go into that subfolder, I can now independently run.
-
Not Synced
Because of the way RSpec will load these files, I can prove to you that these tests for the levels and the meter of the annoyance will pass without knowing about EmailSignup.
-
Not Synced
Again, it doesn't matter here; it matters a whole lot if you're dealing with tens of hundreds of files.
-
Not Synced
I can prove that this thing is independent.
-
Not Synced
How many have seen shoving a gem like this into the subfolder of an app?
-
Not Synced
Everyone else should start doing that.
-
Not Synced
You could ask me two good questions: one is why am I not using git submodules? "I'm not smart enough" is the answer to that.
-
Not Synced
Why am I not using a different repository?
-
Not Synced
"I'm too lazy" is the answer to that.
-
Not Synced
You can just do this.
-
Not Synced
So how do I use it in the application?
-
Not Synced
The Gemfile of the main application is now just pointing to an annoyance gem and it's just referring to it by its path.
-
Not Synced
That's all there is to it.
-
Not Synced
This will be loaded.
-
Not Synced
I can access it like before.
-
Not Synced
And if I look into the TeaseController, we are still down here just loading that AnnoyanceMeter.
-
Not Synced
Nothing else special has happened.
-
Not Synced
The only thing that happened is we now have provably independent tests; we have provably independent code within that one component that we just extracted.
-
Not Synced
In the words of Eric Evans, "Choose modules that tell the story of the system and contain a cohesive set of concepts."
-
Not Synced
We are now able to prove that a set of concepts—namely within that gem—is independent of the others.
-
Not Synced
I think that is of great value.
-
Not Synced
I was in Iceland last weekend and I found something funny so I wanted to put these slides in.
-
Not Synced
It's good for push and pull to be so explicit, but if you look at the doors, it's actually not that explicit as to what they do.
-
Not Synced
They denote how to operate this door, but now look at Icelandic signs.
-
Not Synced
You know how to open these doors.
-
Not Synced
They scream at you, "This is how you open me."
-
Not Synced
You might also rip them apart, but you know.
-
Not Synced
(Laughter)
-
Not Synced
For the structure of the app, now there is a second component.
-
Not Synced
So we've got a little bit more structure.
-
Not Synced
Is that cool so far?
-
Not Synced
Gems make up a portion of apps, but if I have dependencies towards anything Rails, I have to do a lot of homework myself.
-
Not Synced
In the next step—what I want to call the Rails component app—I'm still going to do that for a Rails component.
-
Not Synced
So I want provable structure for Rails.
-
Not Synced
The way to do this easily is with Rails engines.
-
Not Synced
If a Rails engine were actually a train engine, then a Rails application would actually be a train.
-
Not Synced
So you might want to reconsider to call the whole thing Ruby in Trains.
-
Not Synced
But I still have to clarify a thing that engines have still this wrong perception of being for pagination and generic administration and authentication.
-
Not Synced
They are not.
-
Not Synced
If you look at the docs it says Rails engines allow you to wrap a specific Rails application or a subset of functionality and share it with other applications of within a larger packaged application.
-
Not Synced
I should know because I pushed that change last time I gave a talk like this.
-
Not Synced
It's very good that it says that now.
-
Not Synced
Let's look at what that means.
-
Not Synced
Now next to this gems folder we also have an engines folder.
-
Not Synced
Now you're allowed to notice that the app folder went away.
-
Not Synced
Since we only have one controller, there is no longer any necessity for this app to contain any code itself.
-
Not Synced
It only contains gems.
-
Not Synced
Let's start with the more self-contained one: EmailSignup.
-
Not Synced
If we go in here, there is still that Entry and it's now within a module and within a gem, which is also incidentally an engine.
-
Not Synced
If you don't know a lot about them check out this source code or find out about them.
-
Not Synced
I can't go into more detail but I'm happy to do that in Questions.
-
Not Synced
So essentially this means I have my database migrations in here; I have tests for this; it runs independently.
-
Not Synced
And the main app, as before, just includes those gems and references them.
-
Not Synced
Let's quickly look at the Teaser because the Teaser is now what contains the controller.
-
Not Synced
Actually I also want to point out that the Teaser contains assets, controllers and views in my example here.
-
Not Synced
It does not contain models because it doesn't have any data itself.
-
Not Synced
EmailSignup only has the model. It only has this entry thing.
-
Not Synced
The Teaser thus must be getting that data from somewhere else.
-
Not Synced
And indeed it does.
-
Not Synced
It too requires EmailSignup to be present.
-
Not Synced
And it references that dependency of course in the gemspec so that it's actually a valid gem definition.