Everyone's building services these days, but for many teams it's turning out to be harder than expected. What can we learn from object-oriented design to make this transition easier?
C E S S A R A H M E I R U B Y C O N F A U 2 0 1 5 https://www.flickr.com/photos/islespunkfan/2761157346/ I’m really excited to be here in Melbourne for RubyConfAU, to show you a brand new talk. You’re the first ones to see it, and probably the only ones to ever see it in a 20-minute format. ! 20 minutes is a fascinating time interval for a talk, and by “fascinating” I mean really really difficult. In 20 minutes, I can really only say one thing. ! It remains to be seen whether I picked the right one.
in San Francisco, where, you may have heard, the tech industry is killing the soul of the city. I’ve been there since 2001, but I’m part of the problem, because I see that and go, “FEWER! Fewer hipsters.” ! I do a lot of pair programming & helping people figure out to refactor large codebases. I raised my hand un-ironically yesterday when Keith Pitty asked if anyone loved legacy code. I actually love working with codebases that have been around for a while. You can see all the scars, all the battles fought and lost over the years. It’s like a tapestry with hidden stories woven into it.
I’m one of the directors of RubyCentral, which means I help run RubyConf and RailsConf. I’m the program director for RailsConf this year. ! In addition to Ruby Central, I’m also on the board of RailsBridge, which I founded back in 2009. We’re working to get more underrepresented folks into the Ruby community. I’m also on the board of Bridge Foundry, an organization we formed to bring our way of doing things to other communities. Now we’ve got ClojureBridge and MobileBridge going, and they’re both super cool.
you so far, this is not a talk about farming. It’s a talk about service oriented architecture! Awesome, right? We’ve had so many of those already. But this is actually also a talk about object-oriented design. ! Those two phrases are programmer jargon that can be pretty intimidating. ! And many people have very different ideas about what these things are. So I’m going give you my definition for each, and then we can explore why I want to talk about them together. Let’s start with Service-Oriented Architecture.
moment, as I’m sure you can tell from all the talks that have touched on it. ! I have a friend who’s raising VC money right now. That means she’s going around to VCs with a 5-minute pitch presentation. Most of these guys, to be perfectly honest, are assholes. She got a meeting with one of the big names down on Sandhill Road, and she was in the middle of the slide on monetization strategy when the guy interrupted her mid-sentence to ask if she planned to do SOA. ! SOA is something non-programmers have on their radar. Sort of like “the cloud.”
talk about what it actually is. Everyone starts with one Rails app in one repository. And at first that app does everything. It accesses the database and manages the tables, it routes requests, it render views, it talks to third-party services, it runs background jobs, maybe it accepts some API calls. And for the vast majority of Rails apps, this pattern is totally fine. ! But then there’s the ones that are successful. SOA is always prompted by growth, either growth in traffic that requires one component in your app to be scaled differently from the others, or growth in team size that makes it awkward for all of you to work in the same codebase, and often these happen at the same time.
Thing One Thing So SOA, as it’s most commonly practiced, involves identifying separable responsibilities in your codebase, and then isolating them in their own codebases with their own deployment setups. ! Now remember, this is all driven by growth. So when you start moving towards service oriented architecture, you’re separating responsibilities in your codebases, not because it’s some kind of moral imperative, or because your VCs tell you to, but because you’re actually feeling the pain that SOA is meant to address. ! So that’s Service-Oriented Architecture. Let’s talk about OOD.
is a much older idea than service-oriented architecture (at least among our generation of programmers). Therefore, it’s perceived by some people as rather academic, and not super useful in the everyday creation of software. ! And there are some very academic definitions of OOD floating around out there. But at its core, it’s really something very simple. Object oriented design is how you decide where to put code when you need to write it. Does it go in a class you already have? Or a new class? A method you already have? Or a new method? ! But people think about object-oriented design as academic, largely because they picture it happening like this:
end def ipsum puts “computer” end def lorem puts “program” end def lorem puts “program” end def lorem puts “program” end def lorem puts “program” end def ipsum puts “computer” end Done! You have an app you want to build, so you decide up front what the names are of all the classes that you want, then you decide how they talk to each other, and then the rest is implementation detail, in which you’re always working towards this grand plan that you have in your head, and then someday, when the code looks like your design, you get a gold star and you’re done. ! That is not what object-oriented design is. Because this never happens. But people think it should. ! One of the reasons they think that is because of SOLID.
Principle iskov Substitution Principle nterface Segration Principle ependency Inversion Principle You may have heard this acronym before. It puts together 5 principles of object-oriented design. If you thought the phrase “object-oriented design” was jargon, just wait until you see these! ! The principles are useful to know. And I recommend you look at the Wikipedia page for SOLID at some point. However, in day-to-day development, they have very limited utility. The SOLID principles describe what your code should look like when it’s done. They give you no guidance at all on how to get it there. ! And so in the absence of such guidance, many people assume that object- oriented design means coming up with a design, a priori, that fits all these principles.
puts “computer” end def lorem puts “program” end def foobar puts “baz” end But what object-oriented design actually looks like in the wild is quite different. At the beginning, you have some rough ideas of concepts you want to model, so you make a few classes, like Contract and User, and you fill in the functionality you need. At some point you notice Contract is starting to get a little large. It’s hard to work with. It’s doing too many things. It has many responsibilities.
ipsum puts “computer” end def lorem puts “program” end def foobar puts “baz” end So you take a careful look at what’s inside of it and you separate one of the responsibilities into its own class. You don’t separate it because it’s some kind of moral imperative, or because Uncle Bob told you to, but because you’re actually feeling the pain that object-oriented design is supposed to address.
decisions you make every day, from small scale to large scale, about where code goes. Design is an emergent property of those decisions, not a pre-specified framework that makes all those decisions for you. ! Design is bottom up. It is not and cannot be top down. But why not?
end def ipsum puts “computer” end def lorem puts “program” end def lorem puts “program” end def lorem puts “program” end def lorem puts “program” end def ipsum puts “computer” end Why can’t we do this? Why can’t decide what we want at the beginning and then stick to it? ! The answer is simply that we don’t know at the beginning what responsibilities our code will have.
by writing the code, putting it somewhere, noticing when it seems out of place, and then moving it. Failing to do this re-assessment once code is written is how Rails apps get into trouble. That’s how you end up with a monolith. ! I want you to think about the largest ActiveRecord model in the app you’re working on now.
point at each sequentially 1122 650 326 40 67 39 This is the Order model from Spree, an open-source e-commerce Rails app. I’m not picking on Spree - many apps have it much worse. Thinking about your model, how many lines of code does it have? ! Order itself has 650, but then we have all these modules we include, so we have to count those. Total is just over 1100. ! Most apps have at least one of equivalent size to Order. I’ve seen ActiveRecord models 5000 lines long. Usually the largest model in your codebase is also the one that changes the most often. It’s the one your application revolves around.
every single story. It’s the core of your application. If you’re an e-commerce company, perhaps it’s Order. If you’re a social network, perhaps it’s User. That’s the class that needs object-oriented design. The rest of your models, if the code fits on one screen, it doesn’t really matter. ! There’s a scatterplot in Code Climate where one axis is complexity and the other axis is churn. The files that are in the top right of that plot are the ones that change frequently and are very complex. ! Those are the classes you need object-oriented design for.
Thing One Thing ContractGroup Contract User So that’s Service-Oriented Architecture, and Object-Oriented Design. And when you’re looking at these boxes and lines diagrams, it all seem so clear, doesn’t it. You just find the boundaries of your responsibilities and make them formal. But as Rachel covered earlier, delineating the boundaries of a single responsibility is surprisingly difficult. ! And OOD is the same way. You read a book about it, and it tells you single responsibility per class, and you’re ready to do the right thing, and then you sit down in front of your Rails app and you can’t figure out what to do. ! These two problems are hard for exactly the same reason: delineating the boundaries of a single responsibility is surprisingly hard. And that’s true whether you’re trying to pull out a service or reduce the size of a class.
problem. ! Maybe this once was the right place for a phone booth, but then the landscape around it changed. We get used to it being there. We don’t notice the incongruity anymore. ! We do have some ways of helping ourselves see our phone booths. And these generally go under the name of “code smells.”
was made popular by Martin Fowler in his book on refactoring. They’re usually described as surface indicators in code that often indicate deeper problems within. ! Some people call these icebergs, because only 10% of any iceberg is above the water. ! I use code smells for a different and more specific purpose. A code smell is a sign that I’m mixing responsibilities. They are the clues that show me what objects I need to make. They point out the phone booth in the garden. Let’s look at a concrete example.
we’re most attuned to, and the most used to dealing with. ! Let’s say you have two ActiveRecord models with these two methods. These two methods are mostly doing the same thing, with minor implementation differences - the prefix is different, R vs. P, and order numbers include letters. ! Fortunately, each code smell has an associated refactoring that fixes it. When you have duplicated logic with minor differences, that’s telling you there’s another class trying to get out. The refactoring that fixes this is called “extract class.”
a call to the new class we created. ! We’ve talked a lot recently about DRY, “don’t repeat yourself,” which is an admonition to avoid repeated ideas in your code. DRY does not apply to repeated syntax. Repeated syntax can indicate a repeated idea, but often does not. It can be hard to tell the difference between the two situations. ! I have a simple rule for this, though.
class, leave it in. https://www.flickr.com/photos/aigle_dore/5952236932/ If you can’t DRY out your code by creating a new class, then leave the duplication in. ! Whether or not you can create (and name) a class that captures the idea is what tells you whether the idea is the same, or the syntax just happens to be repeated.
Smells Besides Duplicated Ideas, there are lots of other code smells that it’s worth your time to get familiar with and locate examples of in your own code. Most of them have fantastic names, too.
Smells For example, the official name of duplicated ideas is actually “shotgun surgery,” which describes the symptom of having to operate in many places at once on the codebase to make one logical change. ! Inappropriate intimacy is when one class knows things about the internal workings of another class. The fix for that is to move logic around between classes. ! Data clump is when you’re always passing two or more parameters together, and the refactoring that fixes that is to turn those two things into a class. ! Complex conditional is one of the only ones that is exactly what it sounds like. Any time you have to use else if, ask yourself if you really need it.
Conditional Service Smells Code smells are the guides we use to help us find the right boundaries. They are the missing roadmap that guides us towards SOLID code. They help us make decisions about where to put class boundaries. ! Can they also help us make decisions about where to put service boundaries? ! I think they can. But they take different forms in the SOA world.
manifest itself in a number of ways. The most obvious is multiple services accessing the same database. ! Another more subtle example of shared data is two services with separate databases that must synchronize some subset of their data regularly. ! Each piece of data must have one and only one source of truth, and further, there must be one and only one way to access it.
Conditional Shotgun Surgery Code Smells Service Smells Shared Data is the service equivalent of Inappropriate Intimacy. One service knows way too much about the interior of another service.
logic. If you’ve ever been tempted to make a gem that has your ActiveRecord models in it so they can be shared between services, your services are not independent enough.
Intimacy Shared Data Data Clump Complex Conditional Shotgun Surgery Shared Logic is the service equivalent of Shotgun Surgery. It often goes along with Shared Data, but not always.
always be deployed at the same time is a smell. Look for ways to decouple them. ! Note that Lockstep Deploy can be caused by either shared data or shared business logic, but there are many other ways to achieve it as well. Just like code smells, service smells won’t be clear-cut, won’t exist in isolation, and may be hard to identify sometimes. But just having a name to put on them does a surprising amount of good.
Intimacy Shared Data Data Clump Complex Conditional Shotgun Surgery Lockstep Deployment Lockstep Deployment is another service manifestation of Shotgun Surgery.
you have to pass in more than one or two things for it to figure out what to do, your service is probably doing too much. ! Can’t get around this one with nested routes — those are hidden query parameters. !
Intimacy Shared Data Data Clump Complex Conditional Query Parameters Shotgun Surgery Lockstep Deployment Query Parameters is the service equivalent of a complex conditional. Having to pass in parameters to specify which of several paths to take usually produces a conditional in the underlying code, which is another way to identify this one.
Intimacy Shared Data Data Clump Parameter Clump Complex Conditional Query Parameters Shotgun Surgery Lockstep Deployment Parameter Clump is of course the service equivalent of a Data Clump. ! Each of these smells indicates a problem with your service boundaries. And almost always, the problem is that individual units — whether classes, or services — are doing too much.
Intimacy Shared Data Data Clump Parameter Clump Complex Conditional Query Parameters Shotgun Surgery Lockstep Deployment More Smaller Classes More Smaller Services The answer to code smells - make more and smaller classes. The answer to service smells - make more and smaller services. ! Let me leave you with a few thoughts.
• Get good at OOD first by breaking down complex, high-churn classes • Use service smells to guide drawing service boundaries https://www.flickr.com/photos/pixx0ne/2211981698/ Service-oriented architecture is a new term for a very old skill. It’s all about identifying responsibilities. This is skill you can practice & get better at. And it’s much cheaper to learn that in your code than it is to learn it in your services. Because class boundaries are a hell of a lot easier to change than service boundaries. ! Refactoring the main codebase — your “first” service — into smaller objects is what makes service-oriented architecture even possible. You can’t identify a responsibility to extract to a service if it’s scattered across multiple big objects. You must deal with your god objects before you can do services. ! Once you get there, use the smells to guide your architecture toward single- responsibility services.