Derailing Our Application: How and Why We Are Decoupling Our Code from Rails


Summarized using AI

Derailing Our Application: How and Why We Are Decoupling Our Code from Rails

Fito von Zastrow and Alan Ridlehoover • July 08, 2025 • Philadelphia, PA • Talk

Summary

"Derailing Our Application: How and Why We Are Decoupling Our Code from Rails" is a talk from RailsConf 2025 delivered by Fito von Zastrow and Alan Ridlehoover, detailing Cisco Meraki's journey in evolving their large-scale Rails monolith to better manage growth and complexity.

Main Topic

The presentation centers on Meraki’s approach to decoupling business logic from Rails-specific constructs in their 4+ million line monolithic Rails system. Their goal is to improve codebase modularity, testability, team autonomy, and scalability without fully abandoning Rails.

Key Points

  • Historical Context & Growth Trajectory

    • Meraki’s Rails application began in 2006, growing rapidly after Cisco’s acquisition in 2012.
    • The codebase ballooned from 250,000 lines to over 4 million lines, and contributors increased from 38 to over 600.
    • The initial lack of Rails best practices led to complex, entangled models, controllers, and views.
  • Convex & Concave Payout Concepts

    • The presenters introduce economics concepts (convex vs. concave payouts) to relate to different stages in software development: explore (experiment), expand (growth), and extract (optimize).
    • Meraki identifies itself in the 'expansion' phase, requiring scalable engineering practices.
  • Motivation for Decoupling

    • Rails’ default patterns (MVC) became limiting at large scale.
    • Increasing code complexity hindered understanding, testing, and velocity.
    • Shared models/controllers created organizational bottlenecks for change and improvement.
  • Modularization Practices

    • Extracting Business Logic:
      • Moved logic out of controllers, views, and models into plain Ruby objects (POROs).
      • Enabled smaller, more targeted unit tests and improved testability.
      • Examples use "coffee machine" domain to illustrate extracting classes for update actions, configurations, and broadcasting details.
    • Establishing Boundaries:
      • Created namespaced "/domain" directories (e.g., domain/coffee_machine/) to give teams ownership and reduce cognitive load.
      • Allowed teams to organize these spaces according to their needs (by subdomain, pattern, etc.).
    • Reducing Model Bloat:
      • Shifting away from the "skinny controller, fat model" approach to avoid monolithic, coupled models.
  • Improvements Realized

    • Test coverage in modularized code increased to over 97% vs. 78% in the rest of the application.
    • Method complexity reduced by 30% in modularized areas.
    • Easier debugging and issue ownership due to clearer boundaries.
  • Challenges Encountered

    • Team autonomy sometimes leads to inconsistent application of best practices or domain boundaries, requiring active discussion and balance.
  • Looking Forward

    • Some teams have wrapped their domain in APIs and begun extracting services from the monolith where appropriate.
    • Most teams are still progressing through the 'extraction' phase of modularization and decoupling.

Main Takeaways

  • Decoupling business logic from Rails constructs improves modularity, testability, and scalability in large codebases.
  • Introducing clear domain boundaries empowers teams with greater autonomy and accountability, while also reducing complexity and increasing code quality.
  • The approach allows for incremental refactoring and acknowledges the historical context and distinct needs of different teams within a growing organization.

Derailing Our Application: How and Why We Are Decoupling Our Code from Rails
Fito von Zastrow and Alan Ridlehoover • Philadelphia, PA • Talk

Date: July 08, 2025
Published: July 23, 2025
Announced: unknown

Cisco Meraki is probably the largest Rails shop you’ve never heard of. Our 2 million line Rails monolith has been under continuous development since 2007. As with any large codebase, ours is not without challenges. In “Derailing Our Application,” we share how our architecture has evolved to decouple 800,000 lines of Ruby code from Rails constructs in order to enable us to scale our team to 1,000 engineers and beyond without losing the benefits of Rails. Join us on the track less traveled! All aboard!

RailsConf 2025

00:00:20.000 We're a little behind. So, uh, welcome. Thank you for coming. Uh, my name is Alan Ridleh Hoover. My pronouns are he,
00:00:25.920 him, and I use uh excuse me, I'm a senior engineering manager at Cisco Mari.
00:00:34.000 Hi everyone, I'm Fito Fonastro. I also use a pronounce he him and I'm a staff software engineer also at Cisco Maraki.
00:00:43.440 There we go. Okay, so we call this talk derailing our application, but that's a little bit of a misnomer. We're not
00:00:49.760 actually removing Rails from our app. Rather, we're decoupling our business logic from Rails constructs because we
00:00:56.640 found that Rails was both getting in our way and not providing enough structure at our scale. Now, before we dive into
00:01:03.280 that though, there are a few things we need to kind of establish up front, some core concepts and some common language.
00:01:11.360 The first is a quick lesson in economics about convex and concave payouts. I promise this is only going to take a
00:01:17.360 minute. Um, a convex payout is one that
00:01:22.960 only requires a small investment uh and usually results in a small loss but
00:01:28.080 occasionally pays off really big. This is venture capital. This is you making
00:01:33.360 lots of small little investments in lots of small little experiments to see if any of them are going to pay off.
00:01:40.079 The opposite of that is a concave payout. This is one that requires a large investment and usually results in
00:01:47.360 a small reliable profit like banking. Uh but it can result in a very large loss.
00:01:54.000 I'm sure we all remember 2008. Um this is also kind of like retirement. Um you
00:02:00.479 have a nest egg, you want it to grow, but you can't afford to take any losses. So you plan carefully and you invest
00:02:07.119 conservatively. All right. Does everybody have those two concepts, convex and concave? Yeah. All
00:02:14.080 right, cool. Uh, I can't do a show of hands because I'm blinded, by the way.
00:02:19.680 Um, okay. So, that's the end of the economics lesson. Now, let's see if we can apply that concept to software
00:02:25.840 development. So, there's this guy, his name is Kent Beck. You may have heard of him. Uh, and
00:02:32.400 he has this talk called 3X. uh and in the talk he shows you how the convex and
00:02:37.760 concave payout curves are actually a single curve. Together they form an
00:02:43.280 S-shaped curve that represents the application development life cycle and three phases including exploration,
00:02:50.720 expansion, and extraction. In the explore phase, you're in the
00:02:56.000 convex portion of the curve. The goal here is to find the thing that's going to pay off really big.
00:03:02.319 In the expand phase, it's all about growth. You found that big thing. It's taking off and now your job is to scale
00:03:09.920 it as fast as you can. And then finally, once growth slows to a
00:03:15.280 reasonable like understandable amount and you've accumulated a large customer base whom you no longer can afford to
00:03:22.159 lose, the application enters what's called the extraction phase. Here you've
00:03:27.360 reached that concave portion of the curve and your goal is to eek out as much value as possible without risking
00:03:34.799 any losses. So knowing where you are on that 3x
00:03:40.400 application development life cycle can actually help you make decisions uh about what uh what to do with your
00:03:48.000 application. And so where are we on this curve? Well, when we started putting
00:03:54.080 together this talk, Theo and I thought, "Wow, we're probably in the expan in the extract phase." Um, but no, we did some
00:04:02.159 research. We looked into the data and it's pretty clear we are still expanding. Um, and to understand the
00:04:08.480 challenges that we're facing and why we're decoupling ourselves from Rails, let's take a look at how we got here to
00:04:13.840 this point. So, Moroi was founded in 2006, just 11 days after Twitter. We
00:04:20.639 were a startup with a crazy idea that that for a service that no one else was providing, remote network management
00:04:27.040 over the cloud. So you can set up a device and manage it using a website
00:04:32.960 instead of having to plug into it. So we set about exploring this space. It was completely uncharted territory.
00:04:40.080 Rails 1.0 had just shipped in December of 2005 and we were founded in April of
00:04:45.120 2006, which is when we started work on our dashboard. Now, at that point,
00:04:50.320 people were still writing fat controllers. The blog post hadn't been published yet. No one knew the Rails
00:04:56.240 wave. Hadn't been published. We hadn't yet lived through the Ruby 1 N upgrade. I don't know how many of you remember
00:05:01.759 that fiasco. A lot of you. Um, there were no restful routes. You couldn't
00:05:08.639 chain active record queries and bundler didn't exist yet. So, you had to do dependent dependency management
00:05:14.400 manually. Plus, we weren't well-grounded, eloquent, or practical.
00:05:22.320 But that didn't stop Cisco from acquiring Moro in December of 2012
00:05:27.600 because they recognized that Moroi was innovating incredibly fast and that despite that's despite the fact that the
00:05:34.639 central concepts in our application were laid down well before any of those best
00:05:39.680 practice practices were established. So by today's standards, our code was kind
00:05:44.960 of a mess. That's not an actual class diagram, but it's not far off.
00:05:50.800 So we just been acquired. All of a sudden, we had all these new customers coming to us who wanted products and
00:05:57.280 features we didn't have yet. Um, and so our our Rails app grew a whole bunch
00:06:03.039 despite the mess. uh we went on into full-on expansion mode
00:06:09.759 at the end of 2012 when we were acquired. There were 38 38 active
00:06:15.440 contributors to the codebase and we had about 250,000 lines of Ruby. Three years
00:06:21.039 later, at the beginning of 2016, there were twice as many contributors and over half a million lines of Ruby code. Then
00:06:28.960 three years later, which is about the time f Theo and I joined in 2019, the engineering team had doubled again
00:06:34.960 and there were nearly a million lines of Ruby code. Three years after that, there
00:06:40.880 were 250 monthly contributors and over 1.7 million lines of Ruby code.
00:06:47.360 And then Cisco decided in 2022 that all of Cisco's most powerful
00:06:55.280 devices called our Catalyst series, these are the $100,000 switches that telos buy. All of those devices would be
00:07:02.880 manageable via the Moroi dashboard. Um, which meant we needed to add several
00:07:08.000 hundred new products to our website. Uh so in the three years since then monthly
00:07:14.400 act active contributors have doubled again more than we're over 600 uh at
00:07:19.440 this point and our codebase is now 4.2 million lines of Ruby in just that short
00:07:26.319 time. So during the explore phase we built the
00:07:33.680 central concepts of our application without the benefits of modern Rails best practices. By today's standards,
00:07:39.120 the code was pretty messy. But at only a quarter of a million lines of code, it was probably pretty
00:07:45.280 manageable for those 40 people. Early in the expansion phase, we saw significant growth in both our codebase
00:07:51.759 and our team due to the constant barrage of new feature requests. The goal in the
00:07:57.280 code ba the growth in the codebase led to some astonishingly large models and controllers.
00:08:02.639 um that caused complexity to increase, making the code harder to understand,
00:08:07.680 harder to change, and harder to test. And without adequate test coverage, we were unable to move as quickly and as
00:08:14.160 confidently as we desired. Furthermore, because the models and controllers were shared across the
00:08:20.080 organization, no one individual or team could act to improve the code without
00:08:26.080 gaining consensus across the organization or imposing their solution
00:08:31.840 on everyone else. So, we wanted to increase our velocity
00:08:36.880 as we continue to scale. And to do so, we needed we felt like we needed to look beyond the MVC pattern that Rails
00:08:42.959 architecture supports in order to reduce the complexity and increase the testability of our application.
00:08:50.000 We also felt like Rails wasn't giving us enough guidance about how to structure our application to support a team at our
00:08:56.160 scale. We needed to change our approach in order to address these issues. FTO,
00:09:01.680 would you like to explain how? My pleasure. Right.
00:09:07.440 I'm clicking impair so I'm actually just going to use the keyword but um all right
00:09:13.760 so like Alan mentioned we wanted to reduce complexity and increase the stability so we chose to modularize our
00:09:21.120 codebase in order to address those two concerns and we also wanted to improve autonomy and accountability and so we
00:09:29.200 added boundaries within our monolith to support these qualities now let's look
00:09:34.320 at how we're applying these concepts to our Rails app. Okay. The first thing we did was we
00:09:40.880 wanted to teach people how to remove the business logic from our controllers. Now, this is modularization. You're
00:09:47.120 taking big things and you're breaking them into smaller things. So, we broke our large controller actions into much
00:09:53.760 smaller objects. Now, we started there for a couple of reasons. First, our business logic is the most
00:10:01.040 valuable code we have. It's really what differentiates us from our competitors and it's also where developers are
00:10:06.560 spending most of their time adding features and fixing bugs. So that's where we get the most or the biggest
00:10:12.640 return on investment from having high test coverage. Second, controllers can be really
00:10:19.360 difficult to test. They often require complex setups in order to just exercise
00:10:24.800 every code path in a controller action. And the more logic you have in that
00:10:30.240 action, the more cumbersome testing becomes. And eventually people just stop testing or opt for manual testing
00:10:38.320 instead, which seems easier, but it's actually slower and more errorprone.
00:10:45.200 So we restructure our code to make it easier to test via simpler, quicker unit
00:10:50.720 tests that require less setup. All right, we're going to look at an example. Um, now the code in these
00:10:57.360 examples is kind of made up. It was actually
00:11:02.480 inspired by real production code. It was transformed to use uh concepts related
00:11:09.519 to coffee machines instead of networking with maybe some help from AI. The shape
00:11:14.720 of the code is inspired by production. All right. This is a controller that
00:11:21.519 handles requests to update the settings of multiple coffee machines that belong to a cafe. Now, don't feel like you need
00:11:28.640 to understand the code. Instead, I want you to focus on the shape of the code and the variety of
00:11:35.200 colors that you see on the screen. Both of those at a glance are indicators that there are lots of different concepts
00:11:41.279 here, tons of things going on. So, I'm going to walk you through some highle things that are happening here.
00:11:48.720 There is an active record transaction which contains a loop and this is
00:11:54.320 looping over an array of arrays that contains a coffee maker ID and the
00:11:59.680 corresponding changes to that machine's configuration. Now inside the loop is where most of the
00:12:05.680 business logic lives. The code here is doing things like validating input or
00:12:10.959 populating errors, serializing a hash of changes, and finally saving these
00:12:17.040 settings. Now, back outside the loop, there's a little bit more business logic. The
00:12:22.560 controller is creating a cafe event, which uh is probably consumed by a different part of the system,
00:12:28.639 potentially some kind of notification system. And then finally, know there's so much
00:12:35.200 going on here. So for good measure, we add some aggressive exception handling and an insure block in charge of
00:12:41.360 rendering a response. Now again, there's a lot going on here, but we don't have to solve all these
00:12:47.519 problems at once. In fact, our approach has been to first focus on the body of
00:12:52.959 the loop. Now, some caveats here before we go there. It is important to ensure that
00:12:59.360 you have at least some highlevel tests. You just want to have enough coverage to tell you that uh you're not completely
00:13:06.320 breaking the method. So assuming you have that and that can be hard but assuming you have that uh we can get
00:13:13.279 started with the extraction. All right. So here we've extracted a class called update water tank settings.
00:13:21.680 This class encapsulates the procedure that was inside the loop and is now only concerned with a single coffee maker.
00:13:29.600 Now, this makes testing the logic much easier since you no longer have to think about HTTP concepts. You also don't have
00:13:37.040 to think about the loop. You can just focus on the effects of this class um
00:13:42.079 that it has on the settings of a single coffee machine.
00:13:47.360 But there's still a lot of logic left in the controller. So the next step is to
00:13:52.560 extract the remaining business logic out into another class.
00:13:57.600 So here we've created a new class. Um we called it update coffee machine settings and move the rest of the business logic
00:14:04.399 there. And if you look at the controller, it now looks like a whole lot more like a scaffolded controller.
00:14:10.480 It is only responsible for handling the HTTP request and sending the response back. Now, it collaborates though with
00:14:17.600 the new class that we just extracted to build the response.
00:14:22.720 Now, this new class on the other hand still has several responsibilities and admittedly a lot more than we'd like,
00:14:29.680 but we're going to defer that work to the next person that touches this file because our goal here is not to fix
00:14:36.800 everything at once. It's just to make it easier to test our business logic. And
00:14:42.079 so, now that we took that out of the controller, we've accomplished that goal. So we can move on for now.
00:14:48.639 Now as I mentioned earlier, extracting business logic out of the controller will help us with simplicity and
00:14:54.160 testability. But these extractions alone are not enough to promote autonomy and
00:14:59.760 accountability. For that we need some walls within our codebase.
00:15:05.600 And these boundaries are going to reduce the cognitive load across the entire engineering organization.
00:15:12.160 An engineer now no longer needs to know about all four million lines of code in order to make a change or fix a bug.
00:15:18.959 Rather, they can just focus on their code on the code their team owns. And they're free to improve that code
00:15:26.000 without having to gain organizational consensus. So we started with all the code in the
00:15:33.199 controller. So that was in the controller directory. And now we've extracted classes and we have the
00:15:38.880 ability to move that code into a different directory. So here we introduced a domain directory
00:15:44.639 with a coffee machine subdirectory and we moved the new classes there. Now this
00:15:50.560 directory structure provides a name space where we can put all of the business logic that's related to coffee
00:15:56.880 machines. It is also allowing the team to that's
00:16:02.240 in charge of the coffee machine features to take ownership and experiment with
00:16:07.920 different approaches without requiring that consensus that I I talked about before from the entire organization.
00:16:13.279 They're also now accountable for the level of test coverage or if there's you know test flakiness that's coming from
00:16:18.320 this directory. Plus, with that coffee machine namespace
00:16:24.320 in place, we can actually go and rename some of the classes that we extracted to leverage that information that's already
00:16:30.160 conveyed by the namespace.
00:16:35.920 All right, the next step we took was to teach people how to remove business
00:16:41.759 logic out um or from our views, specifically ERB files.
00:16:48.480 Now, views present a different challenge. Um, ERB files are special in that they
00:16:54.880 can't be processed by community standard uh, code quality tools like flog and
00:17:00.000 simple cuff which we use both. Um, so these view tests are also difficult
00:17:07.360 to write and they can give you a false sense of uh security because you have to
00:17:12.400 set the instance variables that the controller would normally set for you. And once you do that, now it's possible
00:17:18.880 for you to go change the controller code without breaking that view test.
00:17:24.799 Also, the embedded Ruby syntax makes it harder to read plain old Ruby. So the more logic you have in there, the harder
00:17:30.080 it is to understand what's going on. So we restructured our views to make the code easier to test and easier to
00:17:36.799 understand. And so as a bonus, we can now measure the quality of our code. We can see what the flux score is and get
00:17:43.919 coverage information. So let's look at an example.
00:17:48.960 All right. Here on the left we have a controller that's in charge of rendering configuration properties for a coffee
00:17:55.039 machine. The controller itself is not doing much. It's just finding the coffee machine and then it's setting a few
00:18:01.520 instance variable and then it renders the template. Now on the right we have the view or the ERB template and this
00:18:09.360 template contains the business logic that decides which properties get rendered under what conditions. Now the
00:18:16.160 output is just a text file with key value pairs are separated by new line characters. Just so that you have an
00:18:23.039 idea it looks like this. In this case it's just a plain text
00:18:28.240 response with a list of key value pairs. It's not even HTML. So when we looked at this response, we determined that we
00:18:34.799 don't really need ERB for this. A plain old Ruby object or a porro should be
00:18:40.320 enough. Um and again before refactoring this you
00:18:45.520 should make sure that there's at least some highle tests covering this. And assuming you have that
00:18:51.440 um let's get started. So here's the controller and the ERB template as we saw it a moment ago. Let's replace the
00:18:57.360 view with a portal. All right. So here we've created a new
00:19:02.640 class called coffee machine configuration. Now this class encapsulates the business
00:19:07.760 logic that's in charge of building those configuration properties. And we've also pulled the code that calculated the
00:19:14.640 instance variables that used to be in the controller into the new class.
00:19:20.000 Now we did that because the variables were actually deeply embedded in the views logic. The controller had no use
00:19:27.039 for those variables other than setting them so that the view can then access them and that was coupling the view in
00:19:32.080 the controller. However, we decided in this case to leave the code that finds the coffee
00:19:38.480 machine in the controller because the controller does use that logic to determine what the correct HTTP response
00:19:44.320 should be. The new class on the other hand does not even need to be instantiated if there is no coffee
00:19:50.080 machine found. Now the benefits of this particular
00:19:56.080 extraction are that the coffee machine configuration is now much easier to test than an ERB view. We can get coverage
00:20:03.600 and complexity information for the new class and the controller is also much easier to test since it's responsible
00:20:10.240 for less business logic. All right, let's look at where this code should go. So we started with coding
00:20:17.600 controllers and views and we then replace the view with a pure Ruby object.
00:20:23.919 So we can now put that uh new coffee machine configuration class in the domain coffee machine directory and
00:20:31.280 rename it to just configuration since uh coffee machine is already in the name space.
00:20:39.360 Okay, one more. The next thing we thought people to remove was the business logic
00:20:46.320 from our models. Now, this might seem unnecessary or counterintuitive to some of you given
00:20:52.880 that, you know, skinny controllers, fat models has been considered a best practice in the the Ruby Rails community
00:20:59.039 for a while. Um, and you know, they models just seem to fit seem to be the
00:21:04.880 place to put your business logic. However, there's a few reasons to move your business logic out of your models.
00:21:11.760 Most of our models represent these core concepts in our application and it makes them magnets for code. And the more code
00:21:19.200 gets added to these models, the lower the cohesion and the higher the chance for these methods that you're writing
00:21:25.360 your models to be implicitly coupled with each other. So in other words, these models just
00:21:32.000 tend to become junk roars. that diagram that Allan showed of the uh
00:21:37.440 mess of wires. We have lots of models that look like that. Um but also more
00:21:42.720 importantly, it makes it hard to write to draw boundaries around the code that's in models and to, you know, help
00:21:49.360 people take ownership of that code. So once again, we restructured our code to
00:21:55.360 make it easier to test and to draw boundaries. So let's take a look.
00:22:02.400 All right. On the left, we have a controller action that's in charge of broadcasting details about a coffee machine.
00:22:09.039 So, the controller here is going to query the database to find the correct coffee machine and then it's going to call broadcast details um on the coffee
00:22:16.720 machine model. Now, on the right, we have the coffee machine model. The broadcast details method checks various
00:22:23.440 attributes on the model and then it builds a string that contains the machine's name, recent activity, and
00:22:30.000 maybe the supply status. So this is going up so you like devices in the network can see how the coffee machine
00:22:36.480 is doing. Now before I show the extraction again I
00:22:41.919 should uh repeat that you should make sure that there is at least a highle test and assuming you have that
00:22:50.720 um let's extract the code. So here we move the logic from the broadcast
00:22:57.039 details method into a new class called coffee machine broadcast details.
00:23:02.640 The class receives a coffee machine object and performs the same logic to build a string that contains the
00:23:09.520 machine's name, recent activity, and supply status. Now this new class has a few advantages.
00:23:17.760 The first one is that it is no longer implicitly coupled to the model attributes anymore. It's not just
00:23:23.760 magically grabbing the data it needs. Um, this also makes it more extensible. As long as it receives an object that
00:23:30.320 responds to these same methods, it can be used in other contexts. For example, you could pass it an immutable data
00:23:37.200 object that provides the same information. Second, it's easier to test it since the
00:23:43.520 coffee machine object that you're passing in your initializer can be more easily stopped in your tests.
00:23:49.440 And then finally, the code in the class is no longer accessible to other methods in the model. We don't think about this
00:23:55.679 a lot, but this by pulling this logic out into a new class, you are preventing
00:24:02.080 that incidental coupling via referencing other methods or instance variables that might be set in this method.
00:24:08.880 So we have those benefits, but also one thing we wanted to be able to do is to
00:24:14.400 move it inside a boundary. We can do that now. So we started with code in um a
00:24:20.720 controller and a model. We then extracted a class out of the model and we can put that new coffee machine
00:24:27.600 broadcast details class in the domain coffee machine directory and maybe just call it broadcast details.
00:24:36.640 All right, that was a brief look at some examples of how we've extracted our code
00:24:42.400 out of these Rails concepts and started to build out these domain um spaces.
00:24:49.840 But to summarize the approach, we are putting business logic in plain old Ruby
00:24:54.960 objects and we're moving these objects into these namespaces so that we can
00:25:00.159 create boundaries around the domain concepts. Now, if we bring all of this together, all the classes we've
00:25:06.159 extracted, the directory structure is going to look like this.
00:25:11.279 Now, you may have noticed that these classes are not organized by pattern or framework concept. That's that tends to
00:25:18.320 be the first thing uh people who have a lot of experience with Rails notice like wait
00:25:23.600 um you know this update settings and update water tank settings classes, they could be considered service objects,
00:25:30.159 right? Controllers are delegating to them. And on the other hand, the broadcast details and configuration classes are
00:25:36.640 more like serializers, right? They take in some data and return to you some uh more data in a different shape.
00:25:43.679 We have explicitly decided not to be prescriptive about how the code should
00:25:49.120 be organized inside the domain directories. And we did this because it allows teams
00:25:55.039 to decide what works best for them. teams may be in different parts of their
00:26:00.559 own journey, different sizes of teams, different levels of expertise. And so
00:26:05.840 some of them organized by subdomains like they might create a water tank subdomain and move some of the code
00:26:11.120 there. And others chose to organize by pattern, for example, services and serializers.
00:26:17.039 Sometimes they actually go back and forth, whatever works for them. All right, we're just about done. I'm
00:26:24.320 going to hand it back to Alan to close us out. Okay.
00:26:30.320 All right. So there are a few ideas we want you to
00:26:35.520 from this talk. Um first remember the convex and concave payouts. Two
00:26:40.799 different investment models that can be combined into a single curve uh to model
00:26:45.840 an application's development life cycle with three phases explore, expand, and extract.
00:26:52.320 Understanding where you are on that chart can actually help you make informed decisions about the investments you're making in your application.
00:27:00.000 And understanding the route your application took to get where it is today not only builds empathy with the
00:27:05.120 developers who built it, but it also helps you think about and prepare for the application you want to have tomorrow.
00:27:12.240 Then finally, we wanted to share our approach. And due to how old our app is and due to the growth trajectory we've
00:27:17.919 been on, we found that Rails was kind of getting in our way. and also not providing enough structure.
00:27:25.039 Um, Rails and the lack of best practices in our early life led us to some super
00:27:31.440 large models and controllers with B business logic everywhere even in our views. I'm surprised I didn't get a veto
00:27:37.600 didn't get a groan when we mentioned views. Um, it's all react now.
00:27:43.120 Yeah. Uh, historically, this has kind of caused us to struggle with complexity and with testing. Now modularizing our
00:27:50.080 code allowed us to work on smaller more testable things and adding uh domain
00:27:55.919 boundaries um has allowed us to uh kind of pull apart well the modularization has
00:28:01.679 allowed us to pull apart these central concepts and then put them into areas that are bounded so that uh each team
00:28:09.200 has their own uh boundary their own domain. uh and when we've added those boundaries between domains, it's given
00:28:15.600 the teams autonomy to make improvements to their own code as well as accountability for that code.
00:28:23.679 Now, couple of things that we've seen that have turned out to be really great. Um first, we're seeing a massive
00:28:31.120 increase in test coverage. Now, the average coverage for all of our modularized code, the stuff in those
00:28:36.720 domain folders, is over 97%. uh the rest of the codebase as a whole
00:28:43.120 is about 78%. Which is close to that magic management number of 80 but like which 20% do you
00:28:50.159 not want me to test? Um so 20% is you know it's not a panacea but
00:28:57.200 it's going to it's going to be a big difference um from 97 to 78.
00:29:03.360 We're also seeing corresponding decreases in complexity. The average method complexity in our modularized
00:29:09.120 code is 30% lower than the codebase as a whole. Smaller things are less complex
00:29:15.440 and easier to test. We've also seen a big improvement. Flaky
00:29:20.799 tests and SEV one responses since it's now easy to tell which team owns the bit of code at the center of the problem.
00:29:29.200 We've also had a couple of rough spots. Uh one in particular is worth mentioning. Um, probably the hardest
00:29:34.880 thing about this has been the autonomy. Giving teams autonomy within their domains means that they might not
00:29:41.200 leverage the techniques we've described here. It's their choice. Um, and in some
00:29:46.240 cases, teams have used their autonomy in ways that have caused issues outside their domain. And the discussion around
00:29:52.640 how far that autonomy extends can be challenging. So, what's next? Well, most of the teams
00:30:00.240 uh at our organization are still in the extraction phase of this process, but there are other phases in the future.
00:30:06.000 Um, some of our teams have moved this far along. They've been able to wrap their domain in an API uh and then even
00:30:14.000 extract their service from the from the monolith. Now, we don't always encourage that necessarily, but in in the cases
00:30:20.399 I'm talking about, it made total sense. Um, and we expect that as other teams get further into the extraction portion
00:30:26.320 of the story, they may look at doing something similar.
00:30:31.520 Okay, that's about all the time we have. If you're interested to know more about our journey, uh, you can reach out to us
00:30:37.760 here or you can come find us at the booth. Uh, or to plug the silent
00:30:42.960 auction, you can talk to the modularization team. Uh, and just bid on the silent auction item. Um,
00:30:49.039 that' be me. So, uh that's it. Thank you.
Explore all talks recorded at RailsConf 2025
Manu Janardhanan
Christopher "Aji" Slater
Hartley McGuire
Yasuo Honda
Ben Sheldon
Chad Fowler
John Athayde
Mike Perham
+77