00:00:00.299
So let's give him a round of applause and get this thing back on track.
00:00:08.880
That's right, we're going to get everything back on track. Quite the opposite, I'm going to be talking mostly about how we've taken things way off track in the past.
00:00:14.880
Basically, yeah, this is just a presentation of a bunch of mistakes and dumb things we did. That's really my only claim to fame.
00:00:21.779
I'm not being humble; the reputation I have made is being a guy that reacts well to his own mistakes.
00:00:26.820
So yeah, I'm pretty good at it.
00:00:31.980
Let's talk about me for a little bit just to get some context around this. I work at a company called Braintree Payments, where we do online credit card processing.
00:00:38.579
It's a big Rails app; most of what we do is on a Rails app called the Gateway. I'll discuss that a bit more here in a bit, but it's been around since 2008 and it's quite large.
00:00:50.700
Before that, I spent several years at Groupon, a similar story with a big Rails application that grew quickly and had a big team.
00:00:57.480
So it also grew to a sizeable extent, which introduced some challenges and pain points.
00:01:08.280
Prior to those dark ages, I'm not going to talk about it.
00:01:13.439
Fun fact about me: I have never been paid to write new Rails code. I only seem to come into pre-existing applications that already have some age on them.
00:01:20.280
In that, I increase their bloat and age.
00:01:27.659
I believe in not wasting time, so I'm going to go ahead and tell you all the big insights up front.
00:01:32.700
And then if you want to leave, you're not going to hurt my feelings.
00:01:39.960
First, don't write hacks. No matter what the temptation is, try not to do it.
00:01:45.659
Beware of building frameworks, whether you're creating one from scratch or extracting your code. There's a lot of hidden complexity that sneaks up on you and can be detrimental.
00:01:52.680
Deliberately cultivate patterns in your application; this is really important to do early on.
00:01:59.460
And then do what I call 'commit-based documentation.' We'll talk about that here in a bit.
00:02:06.659
So let's sort of recap what I'm talking about. First, I want to be super clear: everything I'm discussing are actual things we've done in production. I'm showing actual code or representations thereof when I need to protect privacy.
00:02:13.319
I want to be clear that everyone involved, myself included, had really good intentions. Everyone is trying to do better. We had no design in this application at all, and it's easy to go straight from that point.
00:02:27.480
I also want to clarify that the things I'm going to talk about are totally blameless—some of the people who have done these things include me but also some of the best software developers I've ever worked with.
00:02:33.239
People that I would work with again in a heartbeat. So I want to make it clear that as I discuss these examples and poke fun at some of these things, I do it out of a desire to help others avoid repeating the mistakes I’ve made or that I’ve observed.
00:02:56.459
So, enough prefaces. Let’s start actually talking about what I want to discuss.
00:03:01.500
This is a picture of a monolith. When I say monolith, it's easy to think of that sleek obsidian thing from 2001, which nobody quite understands, but that’s not actually right.
00:03:13.440
This is a monolith in the real world; it refers to something carved from a single piece of stone. When we talk about big, gigantic Rails applications, that’s how I want you to think about it.
00:03:19.860
The key differences are that the original artist, creator, or engineer of this design had an idea in their mind, but this also represents a series of compromises regarding reality.
00:03:25.319
Design is compromised in the real world, and many things get lost in that translation.
00:03:31.440
So a lot of what we're going to be discussing is akin to time travel.
00:03:37.319
To put it in some concrete numbers, this is roughly the lines of code in the primary Braintree code base that I'm going to talk about today. Year over year, we started in 2008 with around 20,000 lines of code and we’re now north of 300,000.
00:04:05.879
In 2015, we thought, 'Wow, this is a lot of code; we really have to start decomposing this.' We figured we had to do better the next year. Spoiler alert: we didn’t.
00:04:13.260
But it turns out that decomposing a giant application is hard and challenging. It's a work in progress.
00:04:23.600
Once I have some good advice or at least a pattern of mistakes we made in this, I’ll give another talk sometime.
00:04:30.479
Currently, about three percent of the lines of code in our code base are from 2009 or earlier. My initial gut reaction to seeing that is: awesome! Great! That means most of the code is new and I don’t have to worry about it because we’re replacing old code at a pretty good rate.
00:04:52.199
However, it's not really true if you consider the total amount of code we wrote in 2009; one-third of it is still running in production. That’s actually much higher than I was expecting and this came as a surprise.
00:05:09.840
It indicates that the code we write for applications that are successful and don’t go away is going to stick around. It’s going to haunt us.
00:05:21.000
In our current code base, about 43 percent of the lines of code are older than a year. A year is an interesting metric—first because it's a nice number, and second, it represents the termination of the half-life of a line of code.
00:05:31.259
What I mean by this is that anybody who worked on that code has definitely forgotten any reason why they wrote it. Now, the only way you can find out more is from the code itself.
00:05:42.240
With an asterisk that we’ll talk about later.
00:05:54.479
Finally, before making a point, I want to provide one other definition up front: when I talk about maintainability, I'm referring to the vague, hand-wavy term that I'm not going to be able to improve in the situation.
00:06:06.600
What I mean throughout this discussion is minimizing the cost of making changes in the future. I’m not referring to error resiliency or anything like that; I’m simply talking about making it easy for me six months or twelve months from now to come in and make changes and revisit the decisions we made.
00:06:19.800
There have been some attempts to formalize this, but this is a gnarly formula—it’s the Halstead volume times 5.2 times the natural logarithm of that times cyclomatic complexity, lines of code, blah, blah, blah.
00:06:38.100
This doesn't actually work that well because I don't think the number of comments in a codebase significantly impacts maintainability; this has also been somewhat debunked.
00:06:51.419
So, the tl;dr of that is: this is hard to measure and I apologize for being vague and imprecise.
00:07:02.940
All right, piece of advice one: don’t write hacks.
00:07:08.639
I’m going to switch gears a little bit; I’m going to talk about a core Ruby function—definitely a real thing that exists that everybody uses all the time.
00:07:14.039
I'm going to talk about the method key_grep on Hash.
00:07:19.259
Now, key_grep—you might be asking if you’re not super familiar with this function—what it does is it takes a hash, such as {a: 5, Apple: 2, B: 7}, and takes a regex.
00:07:27.240
Optionally, you can pass in a block, which is what I'm doing here, and it, of course, returns the matching keys.
00:07:33.599
If you're not super familiar with it, I'll forgive you; you're probably more familiar with the base form without the block.
00:07:39.600
That just takes a regular expression and, of course, returns a two-dimensional array of matching results.
00:07:44.460
All right, so I've been misleading you a little bit—of course, like key_grep is a bizarre function.
00:07:49.500
It does not exist in Ruby; this is something we wrote. This has been in the Braintree codebase for quite some time.
00:07:55.860
This is hard to explain, but you basically pass in a pattern and can optionally get back the key, the value, and the regex match.
00:08:02.639
Oh dear, I touched the cable; don't touch the cable, Scott. I'm so sorry; man, I've broken everything.
00:08:08.819
See, I told you I'm good at creating problems. Right, truth in advertising.
00:08:13.880
I need a young priest and an old priest. Okay, all right; I will not touch anything with the exception of the right arrow key again.
00:08:21.840
So yeah, I'm not going to dive into exactly how this works. Again, that's probably a conversation for a different time.
00:08:26.879
The key question is: why did we do this? Because I really have no good explanation for why this was a method we felt was so important that we built it into Hash.
00:08:32.219
There’s nothing here that couldn’t have been like this could have been key_grep as a module that could have been tied to hash, but we didn’t do that.
00:08:37.680
Why is that a problem? It doesn’t really solve a problem for us—certainly not a problem that’s likely to come up time and time again.
00:08:42.719
When we bake things into core modules like that, when we monkey patch something, it becomes widely available.
00:08:48.779
It has high fan out combined with a high likelihood for change. This is sort of the genesis of a maintenance nightmare.
00:08:56.100
What I mean by this is, let's take this class A—it doesn't really matter what it is, class, or module—whatever it provides some contract to the world.
00:09:01.740
When we monkey patch something, like Hash, Hash is everywhere; it's very easy to use that throughout our applications.
00:09:07.320
But by monkey patching Hash, we’re writing code that is almost certainly going to change. The probability that it survives from 2009 to today is pretty low.
00:09:13.680
In fact, we cut this out at one point, but it was there longer than I would like to admit.
00:09:19.500
This creates really challenging refactoring and a difficult problem for us to solve— we shouldn’t make challenging problems.
00:09:25.200
We have enough challenging problems as it is without creating new ones.
00:09:31.920
Okay, you're probably saying, 'Scott, okay, that’s just a ridiculous contrived example; surely there are better things to talk about?'
00:09:42.940
Okay, let’s talk about something a little more concrete. This is actual Ruby code; I’m no longer going to mislead and lie to you, I don’t think.
00:09:49.140
Here we have an array; we're calling map to string on it—great! This came out in Ruby 1.8.7.
00:09:55.500
At Groupon, we liked this—this is open source, so I am showing it. Normally, I would not show code from other employers, but you can actually find this out on GitHub if you really look for it.
00:10:01.680
We liked it, but for some methods it’d really be better to pass in an argument rather than just saying 'I’m going to call to string' I could say 'I’m going to call to string with something else'.
00:10:10.560
Here's a concrete example: map plus, and then we can call it with something like the square brackets convention we came up with, so it returns 2, 3, 4.
00:10:16.920
Okay, well this is already a more sane example and I can see why this is useful; this is a problem I run into in Ruby.
00:10:22.079
However, this wasn't enough for us. No, we needed more power.
00:10:28.440
Not every method has every function defined. In a perfect world, it would, but the world isn't perfect.
00:10:36.960
In this case, we have an array of mixed types; we’ve got five and we've got Steve. We want to call 'days ago' on the things that know how to respond to days and ago.
00:10:51.660
So, we introduced this convention of using a minus sign, which signifies that if it can do it, we can call on it.
00:10:58.200
Of course, anything you can’t add on to that gets treated under those same rules and conventions.
00:11:04.800
So if we call '5 Steve map days ago,' you get a date and a nil.
00:11:10.560
Again, this can be useful in certain circumstances but it's not a great idea.
00:11:16.920
And why did we do this? It saves me time right now. Okay, this is already a way better argument than the last one.
00:11:22.140
It saves me time right now, and so this isn't a bad argument against writing software.
00:11:27.720
But it is a bad reason to follow this hackish approach.
00:11:34.200
Looking back at Ruby 1.8.6, this is the definition of symbol. This is a super blurry definition; sorry about that.
00:11:40.200
You don't really need to read it, but it didn't define simple to proc.
00:11:46.920
When Ruby 1.8.7 came along, we defined simple to proc, and now we have this new extension.
00:11:51.660
Time passes; we're using this a lot, but now Ruby 1.9.1 comes along and adds a few new methods on symbols.
00:11:58.680
We're continuing to add more implications, but now they've defined open brackets on the symbol—why would they do this?
00:12:08.040
The root cause is, while I’ve never actually used that in production, I’ve relied on this totally different definition of what this method accomplishes.
00:12:17.100
This turned out to be an upgrade nightmare. We reached a point where I thought, 'Well, I don’t want to be the person upgrading this code base; this is going to be bad.'
00:12:23.640
I did this—this is a travesty I perpetrated. I disagree with Ruby on principle; I don't like how Ruby works, and I tried to change it from inside Ruby.
00:12:32.100
This is an okay version; however, this is not the problematic code I’ll talk about that later. This was a result of an earlier problem I created.
00:12:42.600
I created some more problems while attempting to solve it, but this was a pretty convoluted situation.
00:12:49.699
Importing data from some CSV file: we had a recurring issue with data imports from untrusted sources.
00:12:59.760
We came up with a DSL to say we were going to clean the field names—with something that sanitizes names.
00:13:06.300
I mentioned my dark ages when I started as an enterprise consultant and worked in statically typed languages.
00:13:13.620
I mostly don’t miss that, but one thing I miss is you could refer to functions without invoking them; it drives me crazy.
00:13:19.800
In Ruby, I couldn't easily invoke a method without invoking it; there weren't great mechanisms to do that.
00:13:25.740
You can wrap things in a proc or block, but it’s not the same because everything has the same contract; it’s just both and it's repetition.
00:13:31.140
So I changed how Ruby worked in this one place.
00:13:37.200
The old version I commented out here. The new version involves a bare reference to this method, and we’ll figure out how to invoke it later.
00:13:43.620
This was a gross workaround that never worked smoothly, and it was a bad idea.
00:13:50.460
This also falls down in interesting ways when you want to pass in something with currying.
00:13:57.060
This required a ton of hacks to make it work, and it was a bad idea.
00:14:03.600
Similar reason: I disagree with Rails on principle because we’re talking about a framework, not a language.
00:14:08.940
Let’s talk about Active Record. When we call User.find(1), it gives back some instance of a model called Scott, okay, fair enough, straightforward.
00:14:18.960
However, calling 'id' results in a runtime error; don’t call 'id' on Active Record models, use 'pk' instead.
00:14:27.000
There are a couple of reasons for this, one being principle and one being pragmatic. The principle was 'id' is an overloaded term; 'pk' is really how this should be tackled in terms of classical database design.
00:14:39.000
The other reason relates to an old quirk that has since been fixed, but in older versions of Ruby, every object had a method definition on an 'id' which could be called on anything.
00:14:47.280
Active Record overrode that so that when you call 'id' on Active Record, you get its canonical database representation—its primary key.
00:14:55.039
As a result, you could run into edge cases where, if you ended up with nil instead of an Active Record option and call 'id' on it, you’d get variable outputs.
00:15:04.680
This led to bugs that really bugged us, so we wanted to create something called 'pk'.
00:15:09.000
However, this required a phenomenal amount of hacks. We still have this running in our database. You can change code, but databases are forever.
00:15:23.280
So, once you've got something running in production, that’s it.
00:15:27.240
And what's troubling about this is that anything we bring in makes that same assumption about Rails, because that’s how Rails works.
00:15:32.760
This approach also adds pressure to other dependencies in our code, so something that relies on 'id' needing to function correctly.
00:15:43.080
So these reasons often weren't good enough. I may disagree on principle, but it saves me time right now.
00:15:50.700
What I want to emphasize is: there are a couple of edge cases where I’m contradicting myself.
00:15:57.360
Time box changes to fundamental code—I'm not providing an example here, but I will talk through it.
00:16:04.920
When we were upgrading this same Gateway application to Rails 3.2, we ran into an issue.
00:16:11.280
Because of some things we do in Rails, the 'flash' hash wasn’t going to run nicely for both versions of this code to run simultaneously.
00:16:18.480
So, we realized this would be a problem existing in production for about an hour.
00:16:25.920
We could either change all invocations accessing flash, or we could temporarily monkey patch this with what we called the BT flash hash.
00:16:32.640
We chose the latter, and it turned out fine.
00:16:39.240
Backports, if you have critical functionality that hasn’t been released yet, you can backport it. If you need something for Ruby 1.9 and you're on Ruby 1.8.
00:16:47.760
Hopefully, nobody is on 1.8 at this point.
00:16:53.960
If you’re recording this stuff, it might be a good reason to consider.
00:17:01.680
That said, unless you’re dealing with something where the cost of forking is pretty high—like Ruby or Rails—you should consider a fork rather than monkey patching.
00:17:09.240
Even when you do this, it still carries some risk.
00:17:15.539
You have to test the heck out of it—test the happy paths, the unhappy paths, the 'why in God's name would anybody do this?' paths.
00:17:21.040
Make sure you test every conceivable scenario so that when some of your assumptions change, you've already identified it.
00:17:28.720
You should also explicitly call out these hacks as hacks; one pattern I really like is called 'app/hacks.'
00:17:35.100
That way, people know there be dragons—and by dragons, I mean zombie dragons, which you shouldn't touch.
00:17:41.280
Additionally, you should comment thoroughly. Self-documenting code is not enough to handle these kinds of situations.
00:17:47.760
While it tells you how it works, I can tell you where it was used, but I can’t tell you why somebody did this.
00:17:53.880
If nothing else, find a way to create a time bomb spec—that is, something that will break when your assumptions change.
00:18:00.840
For example, we wrote a spec that, if a week passes and this code still exists, it will fail.
00:18:07.560
Because we told ourselves that the only way we’re keeping this in production is if it’s time to take it out.
00:18:12.600
Piece of advice number two: beware of building frameworks.
00:18:14.940
We’ve all seen this quote a million times about regular expressions and how it creates new problems. I feel the same way about frameworks.
00:18:22.140
When extracting frameworks, it doesn’t mean they’re the wrong solution, but it introduces an extra level of complexity that can be easy to overlook.
00:18:35.220
My spiritual guru, Sandy Metz, said, 'Dependencies are killing you.' When we create frameworks, we introduce new dependencies and connections in our code base.
00:18:43.020
So we must be super careful when doing this. Hampton mentioned this morning that determining what even is a framework is a challenging task.
00:18:55.680
I’m going to give it my best shot: it's a subsystem providing generic functionality that can be customized by clients.
00:19:03.600
The reason I like this is that it serves my purposes: the last phrase there about customization by clients—that’s what gets you, or at least that’s what's gotten me.
00:19:09.900
So let's talk about a related concept—Martin Fowler has an idea of design stamina.
00:19:19.100
Essentially, there's this hypothetical good design line where you don’t lose productivity over time because you have the correct design.
00:19:26.640
On one side, there’s this line with no design, which is easy to get started with but will lead to many problems over time.
00:19:32.880
When we talk about building frameworks, we move that line closer to the good design line; we're talking about a long-term investment to facilitate future development.
00:19:43.680
We must be very cognizant of how and when we're paying those costs.
00:19:49.200
This is a concrete example: this is based on something I made up; I can’t really show you the original source.
00:19:53.760
Here’s a painful story we learned at Groupon: here's a movie controller, which is a bog-standard Rails 3 controller—not very interesting.
00:20:04.420
As applications grow, you're probably writing this controller or variations of it a zillion times.
00:20:10.920
So naturally, we think: hey, that's repetition! We don’t repeat ourselves; we’re good software developers.
00:20:17.040
Thus, we’re going to build a framework called deep_rest, and now we just say what resource we're working with.
00:20:23.880
It also has this special edge case where we need an additional instance variable set for new actions so that the views have it.
00:20:29.220
Everything looks great so far. However, with version two, we realize that not all users should be able to create and modify these movies.
00:20:34.380
Let’s say we’re building something like IMDb; we only want admin users to be able to do that.
00:20:44.040
So we establish that index and show don’t require login, while everything else will require it.
00:20:50.280
However, we have a Robin Hood problem: there are five million movies called Robin Hood, and our support users keep adding them.
00:21:00.280
It becomes tricky to untangle duplicates, so we need to check for duplicates occasionally and take action based on one of these values.
00:21:07.440
Now we've got this before filter that operates on an action which hasn't actually been defined, and it calls us to check for duplicates.
00:21:13.080
Check for duplicates renders a new template, and it’s not clear how 'new' plays into any of this at all because it’s no longer explicit in this controller; it all feels implicit.
00:21:20.520
To complicate matters, now we don’t just have admin users; we trust most of our usual internal users to create new movies or make edits.
00:21:28.560
This leads us to introduce 'roles'—a special non-role key called public, which defines a lack of role.
00:21:35.460
We then specify actions available for those: support and admin can access a set, while admin can only delete.
00:21:41.220
This is a pretty painful place to be now for many reasons, particularly as it complicates the normal happy path.
00:21:48.720
Most controllers will still follow that original example we began with, but users of deep_rest need to think about access and all sorts of additional things.
00:21:55.560
A couple of mistakes were made here: firstly, we created a framework for a straightforward problem.
00:22:03.960
Making that decision to create a framework is a trade-off discussion—where we can accept complexity and where we can't.
00:22:10.680
In this specific case, we identified a simple repetition of similar controllers but didn’t fully understand the problems they would address.
00:22:16.920
As we progressed, we just kind of added features without carefully considering refactoring.
00:22:22.620
While it’s easy to talk about needing constant refactoring, it’s critical when creating a framework.
00:22:28.920
Your design choices can greatly affect downstream clients, so you must consider their impact.
00:22:35.180
One way to tackle this complexity is to let people opt-out of the framework.
00:22:39.600
Either specify they’re in or they’re out—there’s no middle ground.
00:22:46.720
This isn’t code we wrote, but it’s something you can find as open source. I really admire the design of this restful controller.
00:22:52.700
You can say, 'Here’s the solution for my movie controller,' and if you want to modify an action, you just override the method.
00:22:58.920
This allows you to solve the simple problem while letting others opt out without being obtrusive.
00:23:05.760
Okay, let’s discuss that importer again; this thing was a bane in my existence. I made so many mistakes and learned a ton.
00:23:12.480
But this is the initial version of the interface I’m talking about: making it accepting 45 optional arguments.
00:23:19.020
In a true Rubyist style, I’m speaking to you: my method doesn’t take 45 optional arguments; it takes one.
00:23:23.280
Nope, it’s not that great; here's the problem: we define an import, and the interface takes a delimiter.
00:23:31.680
You can see it’s either a CSV or TSV; it’s a format but not even in delimiter—that's a misnomer.
00:23:38.100
We also added a location or stream option—there must be one of the two, but neither can be optional.
00:23:45.480
Next, we have fields: we've got to have something named; so, we could pass an array of names.
00:23:52.620
Alternatively, we can pass a symbol, which infers it from the first row of the CSV or point to a path on disk for external mapping.
00:23:59.460
Finally, we have validations—things will say whether to import this row; if field headers are defined, pass in a hash referencing field names.
00:24:06.900
Otherwise, you want to pass a bare array; this is a very problematic interface.
00:24:13.020
Here’s a diagram showing some interface A and several classes relying on that interface.
00:24:18.120
Let’s say we add a new caller, like the smile emoticon, which adds new requirements to the previous base case.
00:24:23.520
Well, when you change the original interface, you must consider everything relying on that same interface.
00:24:30.900
By introducing the smile emoticon, oh no, this complexity has crept into the whole system.
00:24:36.780
This makes it challenging to change, and a significant change often happens when you’ve lost all original context.
00:24:44.640
For maintaining good practices, let's turn to the SOLID principles.
00:24:51.419
We have single responsibility, open-closed, list of substitutions. The one Rubyists don't talk about is dependency inversion.
00:24:58.620
The open-closed principle should guide us: when you write code, it should be open for extension but closed for modifications.
00:25:07.580
This is difficult to achieve; I’m lucky if I get it right a couple of times a year.
00:25:15.000
Even more so, the interface segregation principle states that no client should depend on an interface they don’t use.
00:25:21.600
Originally, many of us thought this doesn't apply to us, as Uncle Bob emphasizes recompilation's impact on C++.
00:25:27.000
While recompilation can be challenging, there's also the maintenance headache that we need to be aware of.
00:25:34.560
For our interfaces, we realize that clients are not using most of them as they are designed to be mutually exclusive in specific ways.
00:25:40.620
What the interface segregation principle does is promote the creation of adapters, so clients only interact with the functionalities they require.
00:25:47.760
Next, let’s explore an example where this works: Active Record. I believe Active Record handles this quite well.
00:25:53.640
Imagine I have this fictional order class that belongs to a customer. As the consumer, you don’t need to know the underlying mechanics.
00:26:00.180
It’s a straightforward solution, as Active Record knows how to translate that to maintain its current set of reflections.
00:26:06.120
You just care about the logic directly relating to the belongs-to behavior.
00:26:12.120
This is especially important when you look at the 'has' and 'belongs_to_many' relationships that can be much more complex.
00:26:19.140
This is a great example of good software design.
00:26:26.760
One last sad tale: pray for him. We built something at Braintree that we don’t recommend.
00:26:34.560
Like most of these things, it started with good intentions and clever insights.
00:26:41.400
We have these forms for users that consist of an email and a password. This is a hypothetical scenario; these forms are not real.
00:26:49.080
Still, there's a model for User with similar attributes, like email and password, that should ideally be consistent.
00:26:55.620
But no—the short answer is no. Let’s fast forward to the end of this story since I don't want to take forever retelling tales of woe.
00:27:01.620
We attempted several implementations to get this to work. This is our under review form.
00:27:08.160
Among several issues to point out, we incorporated the idea of a theme mixed with all these name fields and text fields.
00:27:15.120
We realized some forms display differently and that had to be integrated somehow.
00:27:21.420
Instead of having a direct relationship with a model, we came up with a population model method that knows how to convert this form into a model.
00:27:29.280
There are weird edge cases we account for because it doesn't apply cleanly.
00:27:36.180
This became extremely challenging. Not all repetitions are equal.
00:27:43.080
I think of these in terms of three categories: the most benign is test repetition, which I don't worry too much about.
00:27:51.420
Test repetition is fine because it makes other changes to production code easier.
00:27:57.720
Then there's incidental repetition—the first type of concern, like described with the movie controller where they demonstrate structural similarities but lack conceptual overlap.
00:28:04.320
This can be solved by building a framework, so it's a good idea to establish a happy path and invite people to adopt it.
00:28:11.880
However, this can lead to actual conceptual repetition, where two entities represent the same idea.
00:28:20.040
This is an area where frameworks can address major issues, but it also introduces complexity.
00:28:27.060
Frameworks bring failure modes because they are often orthogonal to the problem domain.
00:28:35.940
Certainly, I’m building something like IMDb while working on a framework for controller overlap.
00:28:42.600
Most frameworks aren’t addressing business problems instead of technical ones.
00:28:49.320
Proceed cautiously, and if you're working in a pure monolith, avoid doing things without a clear purpose.
00:28:55.440
You can do this well, however. An example of doing it well, I’ll bring it up but won’t dive deep into details.
00:29:00.720
One thing that we’ve done well at Braintree is creating a framework for serializing API error codes.
00:29:07.740
Our product is software-as-a-service that we provide to our customers, and as we’ve been splitting the monolith into separate services, more services impact what error codes we send.
00:29:14.520
We built a shared framework between projects to ensure all services handle this systematically.
00:29:21.600
We understood our clients at the time so when building it, we knew what it needed to look like.
00:29:29.280
Thus, don’t be too quick to jump to create frameworks—frameworks should not be approached casually.
00:29:35.760
Deliberately cultivate patterns. As a recap: we see exponential code growth.
00:29:41.700
Facts about organizations experiencing this: headcount rising tends to follow similar trends.
00:29:47.580
No matter how diligent you are, this growth outpaces organic knowledge sharing.
00:29:54.840
Even at Braintree, we pair; no code goes to production without pairing, and yet it’s not enough.
00:30:00.600
New team members will use existing code as a sample of best practices.
00:30:07.560
The implication is: our chosen patterns have a long-lasting impact; they echo throughout time.
00:30:13.560
Even if our choice is to have no patterns, that too can create confusion.
00:30:20.520
Therefore, we must choose patterns intentionally and make them first-class concerns in application design.
00:30:27.120
We should make it clear what the chosen patterns are and ensure they’re the easiest option available.
00:30:34.080
When considering frameworks, think about patterns that come from enterprise or application architecture.
00:30:40.920
These higher-level design patterns can enhance understanding of Rails and help improve service encapsulation.
00:30:49.200
Be obvious and make it easy; it’s easy to think Rails is all about controllers, models, and views.
00:30:55.440
However, we should include a variety of services, ensuring that the people consuming them are also utilizing those patterns.
00:31:02.700
You also need to build other services while accounting for these actions that utilize those same patterns.
00:31:11.160
Lastly, let’s touch on commit-based documentation: we have code acting as documentation.
00:31:16.500
Self-documenting code is great, and we want to minimize comments but recognize their necessity.
00:31:23.160
There’s something missing in the balance, so let’s recapture the earlier point about the monolith.
00:31:30.780
While we understand how it works, we don’t always understand why it was initiated to begin with.
00:31:38.880
For example, here’s a commit from our codebase: my colleague Tony was correcting a typo and linking it back to a specific Trello story.
00:31:48.960
Similar example from years ago when we had a different work tracker; let's reference something connected.
00:31:56.040
These stories are part of your documentation—your commit messages are vital.
00:32:08.700
We should not delete or archive these messages as they provide context for us, giving insight into past decisions.
00:32:16.680
Regardless of how close you are with coworkers, ensure those stories contain a comprehensive context.
00:32:23.280
Recounting the importance—we want changes to be done in one atomic commit so it remains manageable in the future.
00:32:29.760
So to summarize: don't write hacks, beware of building frameworks, deliberately cultivate patterns, and utilize commit-based documentation.
00:32:35.640
I work for a company called Braintree; we do online payment processing. If you're a merchant, I would love to discuss our services.