Netto Farah

Rescuing legacy codebases with GraphQL and Rails

Rescuing legacy codebases with GraphQL and Rails

by Netto Farah

In this talk titled "Rescuing Legacy Codebases with GraphQL and Rails" presented at Euruko 2017, Netto Farah shares a compelling nine-month success story of upgrading a legacy Rails application using GraphQL. The presentation emphasizes the importance of selecting the right architecture and technologies when revamping an existing codebase to improve maintainability and performance.

Key Points Discussed:
- Introduction and Background:
- Netto expresses excitement to share his experience and acknowledges the diversity at the event.
- Legacy Codebase Challenges:
- The challenges faced with IFTTT's seasoned Rails 3 monolithic application, which had multiple API versions and was becoming increasingly difficult to deploy and test.
- Team Composition and Project Goals:
- Mention of the small team comprising backend and mobile developers under tight deadlines to rebuild a new product while managing existing features.
- Incremental Approach for Legacy Code Rescue:
- Rather than moving to a microservices architecture, Netto explains the strategy of creating a hybrid approach using a rich API through GraphQL, allowing them to serve specific client needs without fully splitting the codebase.
- GraphQL Introduction:
- GraphQL is described as a flexible API query language that allows defining and querying APIs effectively, reducing ambiguities associated with traditional APIs.
- It helps improve maintainability due to its strong type system and built-in documentation.
- Implementation Impacts:
- Using GraphQL as an integration layer allowed the team to build new functionalities outside of the Rails app while maintaining the monolithic core.
- Positive outcomes included a 60% reduction in database operations and enhanced performance, thanks to better handling of queries and integration approaches.
- Challenges with GraphQL:
- Issues like N+1 query problems were also discussed, with strategies to overcome these through better schema design and optimizations.
- Concluding Remarks:
- Netto encouraged the audience to explore GraphQL further and highlighted opportunities for joining his team.

Conclusions and Takeaways:
The session concluded with strong advocacy for GraphQL's use in modernizing legacy applications, emphasizing its role in improving performance while maintaining the integrity and function of existing monoliths. As developers consider future upgrades, factoring in a client-centered API design is crucial for successful project outcomes.

00:00:08.059 Hi everyone! Thank you so much for being here to attend my talk. There are multiple reasons why I'm excited to give this talk today. The first reason is that it is very humbling to participate in this conference with such a diverse panel of speakers.
00:00:13.860 I would like to ask everybody to give a round of applause to all the speakers and the organization for putting together something so great! The second reason why I'm excited to give this talk is that I get to tell a story. This isn't an overnight failure story; I've had many of those too, but this is actually a nine-month long success story for my team.
00:00:36.570 The third reason why I love to share this talk is because it's about a technology I'm very excited about. I got involved in it a couple of years ago and have enjoyed working with GraphQL. I've enjoyed working with Ruby on Rails for multiple years, and there are many things I'm excited to discuss.
00:01:14.039 If you don't know me, my name is Netto. I currently live in the Bay Area and flew in from San Francisco. I'm an engineering manager at Segment, but this is not a story about Segment. Also, I'm originally from Brazil, and I've been hearing so much Portuguese here. So, Brazilians, please say hi! There are a bunch of us here.
00:01:28.859 This is a story about when I worked at IFTTT, which stands for "If This Then That." I will tell you the story of how my team and I helped build the next generation of their current project. For those not familiar with what IFTTT does, it's exactly what it stands for: if this, then that. You can connect different online services and make them work for you in such ways. An example would be: if I take a photo on Instagram with the hashtag Euruko, I want that photo to be tweeted automatically for me.
00:02:04.670 You can do all sorts of things with IFTTT. You can back up all your photos to a Dropbox folder, automate searches on Craigslist, or connect any other online service in creative ways. A little context: IFTTT is an app that is loved by millions of users and is used to make these connections between different online services. We're talking about billions of API calls every day just to make those connections and post data. IFTTT has a website, an iOS app, and an Android app.
00:02:48.560 When we started this project, the tech stack was a seasoned Rails 3 monolithic app. It had multiple versions for their APIs: API v1, v2, v3, an internal API, and a developer API. As is normal with many seasoned Rails apps, and I bet a lot of people here have experienced this too, over time it becomes very challenging to deploy, iterate, and run tests.
00:03:14.410 I remember that even booting our app sometimes took about 40 seconds just for the Rails server to start. To make things even more interesting, I was the only web developer working on it at that time. We then received the news that we were going to build an entirely new product. This situation is very common when you're working at a startup. You are still trying to figure out product-market fit while building a product that people love. Sometimes, you have to change a lot of things.
00:03:53.029 However, there was a caveat: we had a nine-month deadline to rebuild everything. We knew changes were necessary, as the app was barely bootable in development. We were going to have to make drastic changes, add a bunch of new features, deprecate some old features, and change a few others. We aimed to build a radically different UI, and I knew I couldn't do that alone.
00:04:31.830 This led to an entire discussion about how to rescue a legacy Rails codebase or any other codebase. The first instinct might be to say, "Oh, let's just split it into microservices, and everything will work fine." But there's a full discussion to be had about majestic monoliths versus microservices. It’s not a binary decision; both options come with their pros and cons, and it’s really a choice that you have to make while being aware of the consequences.
00:05:06.210 Another crucial point to keep in mind is that we had a very small team: I was working on the back end, and we had a web front-end developer, two Android engineers, and two iOS engineers. So, with such limited resources and tight deadlines, the more we thought about it, we realized that there might be a compromise we could make. We could create a hybrid approach with a rich API and build more specific clients instead of diving headfirst into microservices.
00:05:41.910 We decided to take an incremental approach, and that's what this talk will focus on. However, the moment you make a decision to go towards a distributed route, you must figure out how to make these different apps communicate. The first thing that comes to mind is through the database. If you do this, you'll get the typical recommendation from anyone you talk to.
00:06:06.850 Let’s dive into that a little bit. Why are database-driven integrations so tempting? One reason is that you get a really nice granularity when querying your data because you're co-located with your database. It's easy to load everything you need with one round trip. Most importantly, when considering distributed systems, you have ACID guarantees provided by your SQL database. You don’t have to worry as much about transactions or rollbacks since those are handled by the database.
00:07:14.230 But why can they be challenging? One reason is that you end up repeating your abstractions everywhere. If your database is your integration point, every system that connects to it has to re-implement those abstractions throughout the entire system. Rollbacks are almost impossible because you’re dealing with the structure of the data. Likewise, if you deploy and change your database schema, and you have five different services connecting to it directly, how do you coordinate their releases effectively?
00:08:30.600 The natural other option that most people go with is APIs. However, APIs aren't without their challenges either. One challenge when building APIs is that they have to cater to many use cases. You might have an internal system that needs data, or you might have a website that requires different data formats and transport mechanisms. This adds to the complication.
00:09:18.520 Ambiguity in API design is another problem. To solve ambiguity, you rely on documentation and conventions. Yet conventions come with their own challenges. For instance, there’s a universal sign for 'okay,' which everyone generally understands, except in some cultures, it can mean something offensive. Transparency in communication is vital.
00:10:08.290 Now, let’s talk about GraphQL. GraphQL had just come out as an open-source solution by Facebook, and there was a lot of hype around it. Many people were discussing it, so we decided to explore whether it could help solve our issues. One way to understand GraphQL is that it's a language specification for defining and querying APIs. There are three main characteristics of GraphQL to keep in mind.
00:10:48.180 The first characteristic is that you can describe your data with types. Let's say I create a type called 'RubyGem.' This type can have fields like 'name' (a string), 'downloads' (an integer for the number of downloads), and 'versions' (an array of the version type). When using GraphQL, there's a strong contract enforced by types that guarantee the shape of your data. The second characteristic is that you can load only what your client needs. This allows clients to dictate what they want out of your API.
00:12:30.610 The third characteristic of GraphQL is predictable results. When you combine type contracts with the query language, you get results that you can reliably expect. For example, an API call might return that the Ruby gem 'Rails' has a specific number of downloads. One main takeaway from this talk is that when you write GraphQL, you focus on the client Needs.
00:13:45.280 Additionally, GraphQL and Ruby form an excellent combination. Let me show you some code. I worked with the rubygems.org codebase on GitHub to create a new GraphQL API for it. The tool I’m using here is called GraphiQL; it’s a user interface for exploring GraphQL APIs. Remember the types I mentioned? Once you define types, you get autogenerated documentation.
00:14:56.640 In my GraphQL API, there's a field called 'RubyGems.' When I type 'RubyGems,' I can request a collection of all available Ruby gems. I can see the names of all the Ruby gems in my local database. For example, let's say I want to check the number of downloads for a specific gem. I can query the number of downloads as well as look into versions that ever published for the Ruby gem. This allows me to see useful information about all the versions.
00:16:21.230 The nice thing about GraphQL is that it facilitates building a better API. For example, I can resolve a Ruby gem based on its name, which allows me to define an argument that’s a required string type. If I make a query using the Ruby gem's name, it will return the corresponding metadata for that gem. The ability to define different types and versions adds to the richness of the API.
00:18:03.820 Now, after creating and building the GraphQL API on top of IFTTT's monolithic Rails app, we have taken challenges and temptations of database-driven integrations, and traditional API-based integrations, and combined their benefits. We can maintain the granularity we desire and serve different clients exactly the data they need.
00:18:53.370 One of the main advantages we have gained is improved maintainability. Thanks to the types used in GraphQL, documentation comes built-in, and this encourages us to define descriptions for those types, streamlining the implementation. Eventually, what we did was to use GraphQL as an integration layer for the services we wanted to expose.
00:19:40.760 Instead of breaking up the entire monolith into different microservices, we built new functionalities outside of the monolith and connected them through GraphQL. The current architecture allows clients to interact with the monolith via the GraphQL API and treat it as a source of truth without worrying about direct database interactions.
00:20:27.530 However, while the approach worked well, it hasn't been without challenges. For instance, we often dealt with N+1 query issues. N+1 queries are common in many Rails codebases due to the convenience of loading data dynamically. When creating requests in GraphQL, it’s challenging for the server to know upfront what data a client will need, which exacerbates this issue.
00:21:11.940 Let’s look at an example of schema design. I created a simple schema for a restaurant with a recipe type, which also included ingredients and vendors. However, if I needed to generate a report including all the recipes and their respective ingredients supplied by different vendors, the API calls could balloon quickly, resulting in significant latency in our application.
00:22:29.660 After investing time and thought into these issues, I realized that the data models were strikingly similar and could be mapped one-to-one. Drawing on ActiveRecord's built-in features, I could translate GraphQL queries into efficient database calls. The GraphQL gem has introspection capabilities that make this translation work possible.
00:23:14.980 This meant that GraphQL could handle dynamic requests while eliminating N+1 issues. When we implemented these code adjustments, we noticed a remarkable 60% drop in database operations, which significantly accelerated our services and reduced operating costs.
00:24:17.840 Additionally, the implementation offered us the ability to selectively choose our database environment based on whether a query included a mutation or was read-only. This routing of queries mostly segregated read and write operations, contributing positively to the application’s performance.
00:25:02.050 As we capitalized on these optimizations, we also improved our monitoring and error-tracking. Since GraphQL applications typically function through one endpoint, traditional monitoring tools could not efficiently capture query-specific data without additional modifications.
00:26:36.750 By integrating with our helpers, we were able to attach relevant metadata regarding the queries being processed. This adjustment provided clarity in error reports, allowing us to diagnose issues far more effectively and improve our debugging processes.
00:27:21.850 If you're interested in exploring GraphQL further, I encourage you to check out the GraphQL.org website, which contains numerous features that I didn’t cover here for the sake of time. There are also channels available on Slack specifically for Ruby and GraphQL, with friendly members eager to help.
00:28:06.800 Finally, if you want to try out GraphQL for yourself, feel free to visit the Ruby gem repository. My team is also hiring, so please check out Segment.com/jobs or feel free to come speak with me directly after this talk. I have plenty of giveaways like t-shirts, fidget spinners, or socks!
00:29:06.830 Thank you all for being here today! Please feel free to reach out with any questions or to chat further about GraphQL. I appreciate your time!