Brie Bunge shared at the GraphQL Summit 2019 how Airbnb achieved a massive code migration to Apollo+GraphQL. Although GraphQL technology has been around for a long time and has proven interface flexibility and performance, it has always been difficult to migrate it at a large code level.
Aribnb has adopted an incremental and non-regression-back approach for GraphQL migration on a large scale. This share introduces the practice summary of Airbnb’s GraphQL migration from three aspects.
1 Migration Status
Airbnb started migrating GraphQL last year, and more and more teams are adopting it. This year, a hybrid technology and product team developed a mobile Web application using PWA’s technical solution, which brought a huge improvement in user experience. We also promoted GraphQL through this project.
You can see that traffic from GraphQL has been steadily increasing since this year. Mobile Web apps are the main source of traffic, and of course we’re seeing traffic from desktop PCS, and iOS and Android engineers are constantly migrating systems to GraphQL.
GraphQL is used for 5.8% of online traffic, and by the end of the year that figure will be around 10%. The first functions to be migrated are search page, detail page and order page, and the subsequent complete business process including landing page, payment page, favorites page, travel planning page and message page will all use GraphQL.
2 Migration Policy
Our original technology stack is mainly composed of React+Redux, and the server API uses Rest API. We expect to adopt React, Apollo and GraphQL technology stacks in the future.
On the migration strategy, we have several options:
- It’s always tempting to rewrite all the code, but it often takes time to fit into a functional redesign
- Pause and refactor, which is often possible in smaller teams or modules, but can also cause a lot of problems
- Incremental adoption, we found that this is probably the only way that works in large projects, so let’s talk about how does that work
The premise condition
We divided the entire migration into five steps, each of which resulted in a fully functional, rollback-free, releasable version. Before we begin, there are a few prerequisites:
- With the GraphQL infrastructure set up on the back end, you can follow the links below for more details
- On the front end, we need to use TypeScript because we can generate TS type files directly from schema definitions. This becomes the only data standard on the front and back ends. TypeScript is a mandatory option to ensure end-to-end typing correctness. Development will be faster, and development will be more confident.
The image below shows a GraphQL query statement on the left and TypeScript type files automatically generated using the Apollo tool on the right so that we can ensure that the type is correct and Null error checking is available. TypeScript is already the official development language on Airbnb’s front end, and more than 3 million lines of code have been migrated to TypeScript. If you’re interested in how to migrate to TypeScript, check out the first half of this year’s JSConf share. Before we migrated to GraphQL+Apollo, we converted a lot of JS code to TypeScript using internal tools.
Step 1: REST to GraphQL
The first step is to migrate the REST interface to GraphQL to verify end-to-end integration and the generation of TypeScript type files. At this stage, we won’t change any React components and API return data structures.
Let’s take the room reservation interface as an example. We use the Apollo Client to make GraphQL requests. First, we need to ensure that the data format returned by the GraphQL request is consistent with the original REST interface. If you implement GraphQL queries manually, this can be tedious and error-prone.
We took advantage of GraphQL Playground, which provides the ability to auto-complete fields while validating results in real time.
We also have the ability to use field aliases in GraphQL, which is very useful because the original REST interface has a snake name, and GraphQL has a camel name.
We automatically generate thousands of lines of TypeScript type declaration code from the Schema, which is awesome! This used to be cumbersome, requiring manual coding based on server requests, and difficult to synchronize interface changes.
Second, we need to look at the transform code (ADAPT). There are a few changes to the request and response data structures on our back end, so we created the Adapter utility methods that can convert one object into another. Through adapter conversion, we ensured that the GraphQL request and Response were completely consistent with the original REST interface.
We ensured that there would be an online version for each step of migration, so we implemented the online version of GraphQL after this step.
Step 2: TypeScript types
The goal of this step is to start using TypeScript type definition files that are automatically generated. This is a great way to improve our migration confidence and get an error boost at compile time if a type problem occurs, but we don’t affect runtime behavior.
One problem with TS Type files is Null checking, as shown in the figure above, which often requires layer upon layer nullation of the contents of the object. We solved this problem by using an IDX NPM library. Of course Optional Channing is already a JavaScript TC39 proposal and is supported in TypeScript version 3.7.
Step 3: Use Apollo Hooks
On Airbnb we like to use React Hooks, which eliminate a lot of template code, and work well with TypeScript. Apollo Hooks provide a set of functions that can be used to replace the old HOC implementation.
Currently we are using the Apollo Client, while we are still using Redux Actions to trigger events, and the component is also using the Redux Store.
We refactored the system to use Apollo HOC or Hooks, and updated component data to replace the Redux Store with Apollo Cache data sources. We’re still keeping the Redux Store because some of the data hasn’t been migrated yet, but it’s pretty much dead.
Step 4: Bottom-up componentized query
In the first step of migrating to GraphQL, we kept the existing REST request and Response, but there were a lot of redundant fields. So at this stage, we will crop the fields of the query statement.
We start with the leaf node of the component, find the fields required by the current node, and define the corresponding query criteria. We also automatically generate TypeScript type files to help check for missing fields.
Then we will integrate the query condition of the child node to the parent node until the App node, and the process can be repeated until the whole tree is transformed. At this point, the App node has a complete set of integrated query conditions. Since each node only gets the data it needs, we can almost assume that the query conditions of the App node are also approximately the most concise data format.
Step 5: State management
When migrating GraphQL, we are always concerned about how the current Redux Store handles it. We found that the capabilities of the Redux Store can often be replaced by React local state or Context, and Apollo data apis. This has several benefits:
- More consistent mental models
- Reduce template code
- Better caching capability
Through these five steps, we migrated GraphQL step by step, and each step was guaranteed to go live without rollback.
3 Next Plan
The Service Worker interface is preloaded
The goal is to trigger GraphQL queries as early as possible to make the user experience better. We use the Service Worker implementation to do this.
This figure compares the experience of using server side rendering (SSR) and Service Worker. In the upper part of the figure, SSR is used. It can be seen that there is a lot of white screen time due to waiting for server rendering, and then the whole content is directly loaded. With a Service Worker, users can see the skeleton layout of the application right from the start, and page rendering is fast because a lot of static resources are already locally cached.
This is the debugging result of a page using SSR method. First, we get the server request, which also contains GraphQL query, then parse HTML and download JS, and render the page step by step. When React renders the page, users can operate the page.
When using Service Worker mode, as a large number of resources are read from the local cache, HTML parsing and page rendering will be carried out at the first time, and then GraphQL query will start, data will be returned, and the page can interact.
You can see that there is a big gap between the page starting to load and the GraphQL query launch, so there is a lot of room for optimization.
We can register GraphQL requests on the route rather than at the page level. This allows the GraphQL request to be initiated at the beginning of the page, which decouples the GraphQL request from the React page rendering. In this scenario, we saved 400ms and improved performance by 23%. This optimization was particularly noticeable on low-profile devices, where we simulated a 6 x slower CPU device and we achieved a 50% improvement.
Unified Schema
Another improvement is the adoption of a unified Schema. Let’s first look at Aribnb’s SOA services. SOA services are mainly divided into three layers: data layer, aggregate data layer, and presentation service layer.
We use GraphQL as a unified front and back end communication access layer, and unify data integration capability in this layer. We can also make better use of caching capabilities.
We take advantage of batch requests and caching capabilities, which reduce multiple requests for the same data.
Write in the last
A few final takeaways:
- Using Apollo and TypeScript gives migration a lot of confidence
- Incremental architectural evolution is the best approach for a large existing project, with incremental migration strategies leading to a working, releasable system
- The combination of Apollo+GraphQL makes for a very good front-end data base capability
“Dad” has been engaged in Internet research and development for more than 10 years, and has worked in IBM, SAP, Lufax, Ctrip and other domestic and foreign IT companies. Currently, he is in charge of the big front-end technology team related to catering in Meituan, and regularly shares his thoughts and summaries on big front-end technology, investment and financial management, and personal growth.