What Comes After MVC
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Part Number | 68 | |
Number of Parts | 94 | |
Author | ||
License | CC Attribution - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/30717 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
00:00
QuicksortValidity (statistics)DatabaseClient (computing)Endliche ModelltheorieMereologyCASE <Informatik>Model theorySoftware repositorySoftware testingCodeSource codeFunctional (mathematics)Different (Kate Ryan album)Information retrievalOnline helpCondition numberNetwork topologyThread (computing)Electronic mailing listResultantCybersexDiscrete groupMessage passingProcess (computing)Rule of inferenceMUDGame controllerMultiplication signLibrary (computing)Inheritance (object-oriented programming)EmailWeb pageFactory (trading post)WebsiteProjective planeQuery languageMilitary baseRow (database)Software developerGoodness of fitHelmholtz decompositionLine (geometry)Physical systemObject (grammar)Hydraulic jumpRight angleGame theorySoftware design patternState of matterDescriptive statisticsPattern languageZoom lensHierarchyNumberTable (information)MathematicsShape (magazine)Field (computer science)Social classPlastikkarteArithmetic meanIsing-ModellReal numberDesign of experimentsAlgebraic closureLink (knot theory)Level (video gaming)SpacetimeLaptopDomain nameTrailSampling (statistics)Self-organizationSound effectForm (programming)Mobile appComputer-assisted translationOrder (biology)LogicError messageNumbering schemeWritingData mining1 (number)Data structureHuman migrationAddressing modeInformation technology consultingDivisorParameter (computer programming)Slide ruleTouchscreenDigital photographyGodCountingInsertion lossSoftware bugVariable (mathematics)Suite (music)Code refactoringComputer animation
07:53
Message passingObject (grammar)Constructor (object-oriented programming)Hash functionSound effectFactory (trading post)IntegerString (computer science)CodeRule of inferenceRow (database)DatabaseModel theoryParameter (computer programming)Variable (mathematics)NumberSystem callConfiguration spaceRight angleState of matterAddress spaceException handlingSocial classSoftware testingAgreeablenessSoftware frameworkSubstitute goodEndliche ModelltheorieElectronic mailing listQuicksortAttribute grammarFreezingCode refactoringLibrary (computing)Standard deviationExpected valueCASE <Informatik>Water vaporEmailProjective planeMathematicsAddressing modeGroup actionModule (mathematics)Portable communications deviceMetropolitan area networkMiniDiscComputer fileGenderPoint (geometry)Multiplication signEqualiser (mathematics)ResultantLengthRevision controlValidity (statistics)Table (information)Sign (mathematics)Interface (computing)Key (cryptography)Structural loadInstance (computer science)PlanningMetadataConstraint (mathematics)SummierbarkeitType theoryArithmetic meanComputer-assisted translationDifferent (Kate Ryan album)Regulärer Ausdruck <Textverarbeitung>Level (video gaming)Free variables and bound variablesDefault (computer science)Fiber bundleComputer animation
15:47
WaveMultiplication signSelectivity (electronic)Moving averageRight angleEndliche ModelltheorieObject (grammar)Different (Kate Ryan album)Row (database)Rule of inferenceMessage passingSocial classQuery languageEmailDatabaseInstance (computer science)Model theoryMobile appCodeAdaptive behaviorSlide ruleSound effectVariable (mathematics)System callSpacetimeService (economics)NumberWeb 2.0Standard deviationElectronic mailing listMereologyPattern languageIdentity managementCycle (graph theory)HexagonEqualiser (mathematics)Attribute grammarField (computer science)ResultantThread (computing)Natural numberUniqueness quantificationSoftware testingProcess (computing)Data storage deviceIntegerLogical constantPhysical lawDecision theoryHierarchyForm (programming)Point (geometry)String (computer science)Similarity (geometry)Address spaceSpeech synthesisExtreme programmingHeegaard splittingReal numberCuboidFile archiverFigurate numberFactory (trading post)Gastropod shellStrategy gameBitFree variables and bound variablesVideo gameWordMathematicsTable (information)Flow separationAssociative propertyInformation overloadTwitterReading (process)Database transactionMusical ensembleDivisorWrapper (data mining)Software design patternOnline helpComputer animation
24:21
CuboidComputer-assisted translationRoutingObject (grammar)Total S.A.Multiplication signGastropod shellExecution unitComputer programmingShape (magazine)BitMessage passingThread (computing)Group actionCountingGame controllerWeb 2.0Uniform resource locatorImperative programmingSoftware testingElectronic mailing listData storage deviceProcess (computing)Software repositorySlide ruleLinear regressionRight angleSoftware bugDecision theoryControl flowEmailSocial classCycle (graph theory)SimulationVideo gameDesign by contractDatabaseCodeCollaborationismRule of inferenceMobile appSoftware frameworkConfidence intervalResultant1 (number)Algebraic closureFunctional (mathematics)Touch typingRow (database)Sound effectType theoryMereologyWritingMacro (computer science)AuthorizationDebuggerWeb pageComputer fontTable (information)Adaptive behaviorLocal ringTouchscreenStructural loadExterior algebraMUDSoftware developerGradientClient (computing)Line (geometry)Front and back endsoutputReal numberDisk read-and-write headRouter (computing)Variable (mathematics)Serial portModel theoryRepository (publishing)InformationComputer fileComputer animation
Transcript: English(auto-generated)
00:12
Hey, everybody. Welcome. I want to say I'm really happy to be starting off the crafting code track of the conference. So let's just dive right into some.
00:24
Like, here's an example of how a Rails app starts. It's a typical example of an example you see in a design talk. And I'm not interested in this example. I want to turn this into, like, the real world schema that I saw at a client. And it's a little more complicated. It's not a mistake or a joke. Like, this is a mature domain model for a complex real world problem.
00:43
And this isn't even close to the full thing. You don't even see a user model in there. This is what those pretty little examples look like after a year or two or five of exposure to reality. And I think Rails has succeeded by promoting design conventions, like just put your code in these folders, connect your models with active record, use MVC. But as our apps have gotten bigger and bigger
01:02
and older and older, the structure hasn't been enough to support them. Microservices and engines and gems, they break up our code, and I think they hurt most apps more than they help. I think monoliths are great, and I want to make good ones. Today I want to talk to you about what happens next. What happens after you've used Rails generate
01:20
a few dozen times and after you have a migration that only reverses a previous migration because a feature changed and then changed back? What happens after your test suite creeps above one minute, then 10, then 20, and there's no end in sight? What happens after you've got a long argument at lunch about factories and mocks and, like, you're holding grudges afterwards and nothing is settled? What happens when a small feature just doesn't
01:41
seem possible or, like, making a small refactoring turns half your test suite red? What happens when MVC isn't enough to organize your code? So welcome. I'm Peter Harkins. I'm a senior consultant at DevMind. We're a small, happy consultancy in Chicago. And I've touched dozens of Rails apps in the last couple of years. I've previously worked in Django and PHP at one place with 100 small sites and at other
02:02
places with huge Rails apps that are not aging so well. And as I mentioned on the title slide, I'm camera shy, so please don't post pictures of me online. It's an uninteresting personal situation. Just do me a little favor. Thanks. And you don't even need to take photos of the screen. I'm going to put all of the slides with verbatim speaker notes in case I get stage fright up after so the last slide will have the link to that.
02:24
And you might even want to close your laptop because if you think I'm speaking fast now, I'm going to keep this pace up and we're going to move through a lot of code and unfamiliar concepts. And I don't think you want to miss out because there's a cat GIF in Slack. So, like, here's the plan. We're going to talk about the code we have now. I'm going to give two rules for breaking it down in different ways.
02:45
And we're going to explore what those rules implicate for our code. And then I'm going to end with some resources, some tools that you can use, some links to more places you can see good code. The techniques we're going to talk about apply to all of our code, but almost all of the examples are going to be from the model layer because that's where I see most of the problems in code quality.
03:03
We have skinny controllers, which are great, but then models just grow and grow. And that big ball of mud that we grow in our models has some tests, but it's unreliable anyways, and it frustrates our efforts to add features or write good code. I think ActiveRecord is a really well-written gem with a lot of benefits for making apps.
03:21
Like, we have an ORM, a query builder, a factory girl. And we can easily get it any model, anywhere, any time, whenever we want to retrieve from or persist to the database. And validations are right there in our models, so they're easy to find and change. And models are just an obvious home to put the related code. But all of these early benefits is
03:42
mirrored by a long-term drawback. Those amazing tools let us put off painful things until we've painted ourselves deep into a corner. And without hierarchy, we can have cyclic dependencies where we have to create objects in an invalid state before we can create the other objects they need to work. There was one in that real-world example slide that you didn't even see because these things are really hard to spot.
04:04
Always accessible means any part of our code can create, retrieve, and destroy models, so functionality drifts off into hooks and triggers. And it's fine when we have a whole young code base in our head, but this is the source of that fear you feel when you realize you don't know what other code might edit some model of yours or when you don't know what's going to be behind that method you call on a model.
04:23
Finally, all that easily accessible database is sort of the ultimate global variable. It's not only shared between processes, it's shared over time and maybe even with different apps entirely if you're integrating with a legacy system. With our validations up in our code, it's really easy to insert invalid data or have models confused with tons of conditional validations or to edit
04:43
validations and then later discover that records you just retrieved from the database were retroactively made invalid and you can't save them back. That's a real fun bug. And that home for code keeps getting new wings attached to it. Active record models already have business logic and validations and trigger SQL queries and serialized
05:01
adjacent, so why don't we have them render some HTML, some views, maybe their own form or send emails more and more. They just become a junk drawer for any kind of vaguely related code. And I'm emphatically not saying that active record sucks. I think it's great code, but all of the good features of the active record design pattern make it easy for us to write a lot of bad
05:21
code, and then we get stuck with no vocabulary to fix it. We're doing this pattern, and so why is everything painful after a year or two? And can we solve it without some complicated scheme that leaves our code looking weird and over designed? I want to talk about how to get out of this trap. We're going to start with some really bad real world code. It's mine. Decompose it according to two explicit rules
05:41
and talk about how to test the pieces. The improvements we're going to make are incremental, can be used partially and can be reversed if you think they're a bad idea. And if a new developer joins and starts writing code without knowing the rules, it's not like your code base is going to instantly burst into flames. So let's get into that example project. I want to show you what it is. Shybrary is a Web site that archives mailing
06:03
lists. The name is a portmanteau of Chicago and library because I'm from the one and I love the other. So here's the overview for a mailing list about game design and development. You don't need to be able to read it. I know it's tiny, but you can just see the shape of it that up top is a table full of numbers. It's each month's count of how many threads and messages there are, and then below is a big text description of the list. I really like reading mailing list discussions
06:23
because there's a lot of smart stuff out there, and I started Shybrary to help with that and also as an excuse to play with different databases and Ruby itself. And I'll admit when I started it, I had no idea what I was doing with object-oriented design. It was some of the first OO code I wrote. So if you look back in the Git repo after the talk, it will be really obvious that
06:40
that's the case. On the other hand, it doesn't look a lot worse than what I see at clients. And I used Shybrary as a proving ground to experiment with OO design for a year, and I'll be sharing the successful results with you today. So clicking into one of those individual months, here's a list of all of the discussion threads. You can see two of them expanded and all of the tree of people replying down.
07:02
And then in an individual thread, there's that tree repeated at the top and then just all of the messages. A big feature for me is reading one discretion thread per page because I don't want to look through a keyhole at one message and then click for one message and then click for one message. I want to see everything. It meant a lot of fun code, though, sorting and parenting the messages back together or dealing with bad data and trying to do that.
07:22
So let's jump into the code for messages. Yeah. I know you can't read it. It's the God object of the system. And it's I'm not too embarrassed, though, because it's only 300 lines, and I've seen models that are four or five times that. So let's zoom in at the top.
07:40
This is the start. It doesn't inherit from ActiveRecord base, but because ActiveRecord was the only pattern I knew for database access, it's a fine example of what ActiveRecord classes turn into it after a while and how to decompose them. It starts off with a bunch of accessors. There's all the sort of fields you'd expect. And then there's a big regex and a method for normalizing subjects by removing all
08:01
of the, like, re, forward, forward, re, gunk that builds up when your uncle forwards you that e-mail about Obama fluoridating our water. And then there's a factory method for instantiating messages from a hash. And there's a lot going on so far, so let's just keep scrolling down where there's the constructor, which is really complicated and confusing. A message can get fetched by the database by passing its database key or passed in as
08:23
a big string or even from another message object. It's a real mess, and it should be at least three factory methods like that deserialize, but we could do even better than that. So let me hide that big distraction. The call number variable is the unique ID for each message, so it's really important. But it's an optional variable all the way at the end of the arguments.
08:42
And I papered over that by having an exception down below to keep the message from living in an unusable invalid state for too long. There's a nice idea working here that objects shouldn't be seen by the outside world in an invalid state, and we'll find a better expression of that later. But there at the end it extracts some metadata like subject to instance variables. So let's look at how it does that.
09:02
It calls this method, extract metadata, calls this load subject, and then it falls back to a placeholder if it can't find one. And then it knows if the subject looks like a reply using that bid rex, and it has another method that's instance level that calls that class method. Message kind of goes on and on like this, but we've already seen enough code that I have examples for the rest of the talk.
09:23
And we're going to extract those clumps of related code that work on the same variables to objects called values. I promised you two rules, so let's talk about them and apply them to this code. The first one is that values are immutable. When we call a method, we know it will give us the same answer it did last time.
09:40
Well, unlike that message object where maybe we could ask it its subject and then yield us some other code and then we look at the message again and it could have a new subject. This is really closely related to referential transparency if you know that, but I'm not going to try and split that hair. For example, in the code you write now, integers are immutable. When you have three, you add one, you get a new int, four. You don't update three.
10:01
We also use dates immutably, adding and subtracting them to get new dates. We might have a date variable like posted at that changes, but the date April 23 doesn't turn into some other date. You can't turn Tuesday into Wednesday. The variable and the value in it are separate things. Code that's immutable, like values, cannot call code that's mutable because then it
10:22
can't guarantee it will give the same answer. If my expiration date were to ask the user what plan they're on, it might end up giving us two different answers because the user changed account types or something. The second rule is that values don't have side effects. They don't do anything another piece of code might see besides return. They don't read from an API, they don't save to the database, they don't look at
10:43
files on disk, and they don't update an attribute on another object. They just return. Code that doesn't have side effects cannot call code that has side effects. To use a mathy term, like mutability and side effects are transitive. If a method uses code that mutates or has side effects, then that method is mutable or has side effects.
11:00
Like if you've taken a vow of nonviolence and you hire someone else to beat up someone you don't like, that is totally cheating on your vow of nonviolence. When we ask a subject for its normalized version, it calls a method that goes and talks to the database. Well, then that would be the same to us as if the subject directly was talking to the database. So we're going to take those two rules and we're going to make them real by extracting
11:22
some values from that message code. So here's the call number. Now it's a proper class. Since it's the unique key for each message in Chaiberry, it really matters that these things are valid and correct. And it enforces that validity with an exception. A value is never allowed to be in an immutable
11:41
state because if it's immutable, it can't ever change to a valid one. And there's another reason, too. If an object can be invalid, every method that uses that object has to wonder which it's getting, real or fake, because they'll act differently. It's sort of a violation of the Liskov substitution principle. Finally, call number uses a great little
12:01
gem called adamantium. After the initialized method, the whole object is frozen so that an exception would be raised if anything tries to mutate a call number. If you've used freeze in Ruby with objects, there's a couple of weird little edge cases and adamantium does what you'd expect. Adamantium guarantees our immutability. So I want to look at the second value we're going to take out of message.
12:23
It's the here's the front half of it. Subject starts out really simple. It just has a simple initializer that wraps up a string. And like call number, it uses adamantium to enforce its immutability. It also uses the standard library's forwardable module to delegate some methods. So if you ask a subject for its length or try to sort it, it just makes the string
12:42
that it wraps up do all the work. Subject also uses equalizer, another great little gem that takes a list of attributes and generates double equals and other equality methods, as well as hash if we need to use it as the key in a hash. And it's kind of weird to me now how few of our classes in default Rails code implement basic Ruby interfaces like equality and hashing. I think maybe that's a sign that we're too
13:02
framework dependent, but I'm not really sure. Here's the back half of subject, which has those methods that we saw, including that regex, and all the code related to subject lives in the subject object. So you can just ask a subject if it's normalized. If you have a string floating around, you don't have to go call a class method on the message. So those are values.
13:22
We started breaking down our active rocker models into less complicated pieces by finding methods that use the same attributes and variables and pulling them out to their own classes like subject. Or when the model knows an awful lot about an attribute's behavior, like how message knew what it meant for a call number to be valid or not, we pull that out, too. This is the extract class refactoring, and there's nothing special yet. To make that class into a value, we'll
13:44
make it immutable and remove its side effects. That doesn't mean a method can't have a local variable that changes, because that's usually an easy way to write methods. But from the outside, you can't even see that it's there. We always get the same answer for the same arguments. When we extract those values, they'll want the common Ruby methods I talked about on
14:01
subject and maybe some less common ones, like if the value can be used in place of a string or integer, like subject can be used in place of a string, then it will want to stir or to int. And finally, really early on, you'll be tempted to have values call or return active record objects, because so much of our code lives there now. Don't do it.
14:20
A value can't guarantee it's immutable and has no side effects if it's calling methods on another object that might do exactly those things. The simplest way to use a value from an active record model is to write your own getter and setter. Subject can be saved as a string to a varchar column. The spiffier way to do this is to use composed of in current rails or the new attributes API in rails 5, and we're missing a Sean Griffin's talk on that right
14:44
now, which I'm sad, but it'll be on confreaks.tv. For complex values that are made up of more values, like a street address value might have a country value. Maybe you'll save it to multiple columns or maybe its own table that you reach by a foreign key. You'll just keep modeling your tables the same way that makes the most sense to you. Nothing changes at the database layer. This is just OO design.
15:03
Then when we test values, we can do without a lot of things, because they're very small and predictable. If a value is built up out of other values, it's trivial for our tests to integrate down through them. We don't use mocks and stubs, because we don't need to ensure that side effects do or don't take place. And we only write assertions on the result of calling methods, because nothing else will change.
15:22
Additionally, we can do really cool things like automatically generate property-based tests, though I don't have time to get into that. So that's values. They're immutable, and they have no side effects. They're often quite small. I'm sure you've guessed we're going to fill in the rest of this chart, but it's going to go a lot quicker, because we're only loosening those two restrictions.
15:41
So this is the halfway point of the talk. I don't know how you're feeling. I kind of threw you in the deep end, like, you know, my cat after his bath. No. But I think generally people have this. I'm not seeing too many confused faces, but this is a good time to just kind of take a breath, and if you have questions, you should wave at me, like, right now.
16:04
Okay. We'll keep rolling. So here's email. It's another value object. It decomposes the raw string of an email into other values. We can see three strategies at work. First, message ID just extracts the message ID header to build a value, and it's done. That's all.
16:23
Second, subject extracts the subject header, or it falls back to a placeholder if the header is missing. It also builds a value. And then, finally, from extracts the from header and returns a string. I look through the code, and I don't really do anything special with the address. So there isn't any point in making them more than a string. Not everything has to be of value. We extract values because they're help, not
16:44
because it's something we always have to do. This is a talk about practicality, not about, like, religious extremism in our design. All those values we saw didn't have identities. If you have two subjects that are equal, it doesn't matter whether you're using the one in variable A or variable B. And that's the same with the email itself.
17:02
If all the headers and all the body is the same, you all have the same value. Whereas with people, if you find another Peter Harkins, it doesn't matter that we happen to have the same name or we have individual identities. Even if we were roommates and had the same address or other attributes were the same, we'd still have separate identities. And the flip side of that is true. Even if I change my name, my address, my phone number, every attribute you keep in your
17:23
user table, I still have the same thread of constant identity. And mutability is right there in our definition of identity. So we need something separate from values. And we're going to call those mutable objects that have identity entities. But unlike values, entities also don't have side effects. So let's look at two examples.
17:42
First, we're going to have the new message class with all those value objects and more pulled out, message gets pretty thin. We initialize with the call number that unique ID for each message that we extracted to a value. And we just saw the email, the value that builds up more values like subject. And slug is the ID for the mailing list that this message belongs to.
18:01
We didn't look at that example because it's not really different from call ID or call number, but it's used in the URLs and headlines. And we delegate some methods down to values so that outside code isn't violating the law of demeanor to get at the data it needs. The code that interacts with message didn't even change much at all. But message is not just another value. The email and the slug instance variables are mutable.
18:22
Email is mutable because sometimes I find a better copy of an email in a new archive and I want to reimport. Maybe it has better headers or it fixes a character encoding issue because those are all over the place. And slug is mutable because sometimes an email gets cc'd to two mailing lists at once and I file them both in the same list instead of splitting them up. So to make really sure it's clear, if the email and the slug instance variable can
18:44
change, that means it can be replaced with a new email or slug, but the email itself never mutates. Like it's the difference between a variable and the thing that's stored in it. So speaking of list, here's the list entity. It looks pretty similar. The slug is the identity and the various fields are mutable. There's our friend equalizer again because
19:02
it's just handy. One of the interesting things is how little code there tends to be in entities. Their job is to have an identity and wrap up values, not run a lot of code for us. The decisions about our business rules naturally get pushed down into our values and the dependencies of which classes we use float up into our entities. We're going to see more of this later. And you're probably thinking, okay, entity
19:23
is a fancy name for active record model and they're really close but they're not the same. The biggest difference is that active record models have side effects, lots of them. When we call a model, we don't know if it will trigger a save or reload an association or call into some other web of models that have side effects. But this talk isn't about that academic
19:41
distinction, so I want to talk about how to extract entities from our models or at least make our models look more like entities because these rules can be applied partially and the farther we go down this road, the better our code looks. We can extract entities by following the steps we've already seen, figure out what their identity is, extract as many mutable values as possible and drive out side effects
20:01
to adapters and shells, which are the two things that I'm going to fill in that chart with. If we're going to use active record models as entities themselves, we have a lot of side effects to deal with. And here comes the part of this talk that's going to sound ridiculous and scary. Remember, you can follow this rule partially and even if you don't follow it, I hope the rule helps you understand part of why our models become so hard to work with over time.
20:23
I think you shouldn't let your models call there or especially other models' query methods, life cycle methods or your custom methods that have side effects. I know this is really kind of out there as opposed to the standard active record design pattern, but it works. And if we lift up these side effects to adapters and shells to direct them from outside, which I'll talk about in a minute,
20:41
we can still use all of active records' wonderful features for queries and saving without snarling up our model layer. When methods have side effects, we really quickly lose the ability to reason about what's happening and why. If all of my active record models were littered with calls to global variables, like you would scream at that code, but when I call those global variables the database and hide
21:01
them behind methods named find and create, it just seems totally normal. So when we test these entities, we use a couple more tools from our tool box like factories and stubs, but if we're using them for more than a small convenience, our code is probably poorly structured. The famous example is when we use factory
21:20
record, excuse me, factory girl to save ten records to the database just to test one small method on one model because our model has so many side effects that reach out to that big global variable in the sky. That's the thing I most want to get away from. The real difference from testing values is that rather than only assert a method call, return the right thing, we tend to assert that after calling a method, the
21:40
entity is now in the right state. Like if I update the user and change my phone number, it's not that that method returns something I care about. It's that now the user has that phone number. That's entities. They're mutable. They have no side effects. Immutable values cannot call mutable entities because then they would effectively mutate, too. So there's a natural hierarchy forming. But you wonder, like, how the
22:02
hell do I get anything done? If you can't save things to your database or, like, read from the Twitter API, your API never does your app never does any work that the customers can see. So I want to look at the bottom right corner of the grid where objects are immutable but do have side effects. And I call these adapters. Here's our first one. And it's named for Alistair Cockburn's ports and adapters or hexagonal pattern.
22:23
I don't know why he gave it two names, but it seems to have two. When Shibery is building those unique call numbers, it incorporates a run ID, a unique incrementing integer. Because Shibery doesn't store it in SQL database, I needed some custom code to generate these in a transaction, which involves a bunch of commands, too, and queries from a Redis database that I left out of this slide because
22:42
they're not particularly relevant. But I'm showing you the run ID service because it's the one I could trim to fit on a slide and also because there's a caveat on it, which you can guess with all that blank space. Even though it's against the rule that adapters are immutable, it was more convenient for the code that calls this to mutate by for it to mutate by caching the latest value. This object is not perfectly immutable,
23:04
and that's okay. The rules tell me where to go, and my code improved by following them. But on occasion, it's also improved by bending or breaking them. Knowing about the rules certainly helped me decompose better objects than just dumping all this into call number or dumping all of that into message. But maybe I'll revisit this code or move that last bit of some mutability.
23:22
I don't know what the future will hold. But for now, I have the words to describe what makes this object more complicated. Adapters tend to be really thin wrappers around external services. We're going to use stubs to fake out the result of calling an external service, and we'll use mocks for the specific purpose of making sure that that call to an external service was well formed. And there's not a lot of benefits in checking
23:44
results because your test usually looks like you set up a mock to return some value, and then you call into the adapter, and then you make sure the result you get from that is the value you just mocked. So that's adapters. And we don't call or excuse me, unlike values can't call adapters, values and entities
24:01
don't have side effects. So they don't call adapters. Adapters call into values and entities. Otherwise, the values and entities would look like they had side effects too. There's a natural hierarchy that forms to help keep us from the situation where we want to test one method but need to put several methods in the database first. And then we have our code broken up in these three little boxes. So let's look at the last box, which is
24:22
not this box. That's my other cat. I learned from Aaron Patterson that every RailsConf talk needs some cats in it. So here's a worker. It's a job that gets queued after the messages have been filed away. It takes a slug a year and a month, which it calls sim for short, and it caches the total of how many threads and messages
24:40
the list had in a month. It fills in that table that we saw in the early screen shot. The perform method is a little bit cute, but it had to be to fit on the one slide. And all of these methods are one-liners, but rather than have a method with a couple local variables, I broke it up so I could write isolated tests for the parts and then just one integrated smoke test. If you squint, this is function composition. This is Closure's threading macro or F Sharp's
25:02
pipe operator. And working from right to left, inside out, first thing it does is it deserializes that key, and then next it loads up all the threads for that month from the repository, which is the adapter that interfaces to the database. If threads were an active record model, the thread repo is the adapter that I would have calling the thread scope or life cycle
25:21
methods. We don't have time to dig into that code, but threads are entities because messages get added to them over time or even removed if they were misfiled. So then it creates a value from all of those threads, and then it stores that value back in the database. This is a little procedural imperative program. It uses the adapters to isolate the parts
25:41
of the code that needs to talk to the database, then it uses the entities to get immutable things, a value for doing some work, and then it records the result. Let's look at one more little program. If you haven't seen Sinatra before, this is a route and a controller action all wrapped up in one. So when somebody gets at the page for a list to display all those counts, this is
26:00
the code that runs. It just goes and talks to some adapters, and then it builds up a result. And rather than use another adapter to save things back to the database, it uses Haml to return a Web page. But it has almost exactly the same shape of talk to adapters, talk to the things that they give you, and then do a little bit of work at the end.
26:22
These programs are called shells. They coordinate those specialized objects to do meaningful work. It's their job to manage the dependencies, and the classes they use make all of the decisions. We keep them because they don't have the restrictions on we keep them small because they don't have restrictions on mutability and side effects, so they're harder to reason about. A shell might not be a single method or
26:41
a single object like the month count worker, and it might have several methods, like the Web front end has a half a dozen URLs at least. When adding new code, it's easiest to first add a first draft to the shell and then extract pieces out into entities and then values and adapters according to the rules we've talked about, which is another way of saying that we can take the code we already write
27:01
and break it down by these rules when it helps us to write better programs, not all of the time. So finally, testing these shells. We're going to use all of the tools in our toolbox here. We haven't talked much about fixtures, but I only like to use them with real-world input, especially the stuff that's invalid. Otherwise, we can end up in the situation
27:21
where changing one piece of test data can cause more than one test to fail, and we won't know if that test failure was intentional or incidental. We might test on the result we get, like that Web page we rendered, or we might check that the right thing is in the database or that we told SendGrid to send an email. This is the only place where we write integrated end-to-end tests. I'll write one happy path test where everything is wonderful to make sure all the parts are
27:43
talking to each other, and then as needed, I'll write regression tests when I'm fixing a bug I can't isolate it. But I want to think hard on those regression tests and try and break things down to write more isolated code, especially contract and collaboration tests. And there's one thing missing, which is the question mark that's floating over your head, which is like this is kind of bullshit
28:01
because this looks nothing like my Rails app. So I have to add one thing to my slide, which is other. Right now almost all of our code is mutable, and all of it has side effects, especially database access. It's not tidy little shells. It's that big ball of mud where it's hard to follow the path of execution, where we fight about how to write just enough tests,
28:21
where we have all the bad things I opened this talk with. And I promised to write tests for each of the types of objects, so yeah, I don't know. I mean, every test is integrated. Even the ones that lived in the folder called unit were integrated, and we have to use every trick and tool we have just to have a little bit of confidence our code works. Dealing with those big buckets of other code is why I took so many notes on great books and talks like the ones on these slides
28:43
and then experimented and experimented until I realized I had I was seeing some rules for decomposing my code, and I had a lot more understanding of why more experienced developers gave the good advice they did. And I'm really glad to have shared these rules and how you can use them to decompose your code into reliable, comprehensible pieces. They've worked for me on a non-trivial
29:02
project, and they've worked for me on client code. So here's some of the great tools I used along the way. I wish I had time to demo them all. And on the right, there's some more example code you can look at. The Shibery app is up on my GitHub now, and two-factor auth is a gem I made over this winter that's a moderate expression
29:21
of these rules. I don't explicitly follow them, but you can see the outline of the shape as things are decomposed. Cuba and Lotus are alternative web frameworks that are just interesting for being small and Trailblazer, Data Mapper, and Ruby Object Mapper are great for seeing Ruby code that's organized really differently from the rails we're used to seeing. There's a Trailblazer talk next up on the fourth floor.
29:44
So if you stretch your brain, you can see how these rules apply and use them to understand other code and your own. And Haskell's down in the corner hiding. I know it's really got an intimidating reputation, but it's interesting because in it, everything is immutable, and side effects are explicitly encapsulated in a way that's just not possible
30:04
to do in Ruby. I think if you work through a beginner course or book, it will teach you about programming in general, but then also about Ruby. Lastly, I know I'm not the only person experimenting along these lines. I certainly didn't write all of those nice gems. If you have or you want to, please get in touch with me.
30:21
I think there's a lot of room for us to experiment in Ruby and Rails to get everyone's app a lot happier as they get bigger. The experiments that we do expand what it means to be idiomatic Ruby, and they give us new tools and new understandings. So all these slides with comprehensive speaker notes are available right now. And if you thought this was interesting, you can sign up to get some more details
30:42
and examples as I have a little time to catch up on sleep. And I would really love to hear about your experiments, however they turn out, whether that's here or by email. So there's my contact info. This has been What Comes After MVC. And we've got a few minutes for Q&A, so let me thank you all for your time and your kind attention.