Abinash Mohapatra
Abinash Mohapatra
Talk Title
Building Flipkart Ads Platform
Talk Abstract
Have you been working on a project from scratch, severely timeboxed, uncompromising on DX, UX, security and performance. I am and Flipkart Ads Platform is just about that. We help advertisers create ads on our platform and we used GraphQL to serve our demands and give them the best experience.
Talk Description
INTRO It was early 2019 and, we, Flipkart (one of the largest eCommerce companies in India) set out to build the “Flipkart Ads Platform”. It was a beastly task and we wanted to make everything right. Things that were important for us were: 1. Consumer-centricity - UX and polished UI is one of the core principles for building products at Flipkart. 2. Solving data centric problems - Solving overfetching/underfetching and the n+1 problem. 3. Reduce overall code bloat - Coming from facebook flux lib, to over redux, we feel that there’s considerable code bloat for fundamental operations like data fetching. We wanted to explore more and promising alternatives if they existed. 4. Developer experience - DX is usually one more than ever, neglected area in a fast paced team. Be it dependencies on backend teams for data or the lack of a rigid contract signed in blood that potentially doesn’t cause a big fatal crash on production, things like these do reduce the overall productivity of the developer over a span of time. TECH STACK Our team had expertise in React, Styled Components for UI and we stuck by them. For areas mentioned above, we chose Apollo, for our server and client implementation. To add, an SDL first (we create schemas first, then implement resolvers) approach with TypeScript was chosen. To help with the development, we used tooling like: Graphql-code-generator (to generate TS types from our graphql schemas), graphql-import, Merge-graphql-schemas and lodash merge to distribute our schemas and resolvers. This helps ensure separation of concerns between various schemas and resolvers, while finally stitching them before instantiating Apollo Server. To summarise our architecture, UI/client talks to an Apollo Server. The apollo server in turn talks to various data provider systems via REST, collates and massage the response and sends it back to the client. TYPESCRIPT and GRAPHQL GraphQL is a typed system i.e the server’s schema (input request types or server response) have a well defined type. However, with TS in the system, it’d be a mess to re-create the types/interfaces defined on the server for TS. As discussed, we follow a SDL first development approach i.e. we define our server graphql schemas first. Now that we have our schemas, it’d be great if we could automatically create the corresponding TS interfaces or say make our resolvers typed. To help with this, we used “graphql-code-generator” tool. To add, this tool also generates react apollo components and HOCs with TS typings. This saves a lot of bandwidth for creating TS interfaces and resolver typings. This tool works with developer defined plugins that runs on a graphql schemas to generate TS interfaces. For e.g. a sample config file for the tool: schema: http://localhost:3000/graphql documents: ./src/*/.graphql generates: ./src/types.ts: plugins: - typescript - typescript-react-apollo FILES in GRAPHQL Files are an important entity in our ecosystem. Apollo Server 2.0 now provides a custom “Upload” scalar which serves file uploads. On the client, Apollo allows multipart requests for file uploads. On the server, the multipart request is processed and the resolver is provided with an upload argument that resolves into an object containing a “stream”. This is particularly important as we can pipe the stream to our data servers directly. Downloading files is however, still unsupported and is implemented via a traditional REST call. AUTHORISATION GraphQL greatly serves as a gateway for data aggregation. Our application however is role based, thereby requiring stringent access control. We needed to run URL and API auth for every request. We evaluated if we can run this on our apollo server as well. However, we wanted to keep the process as declarative as possible. For this, we used “custom directives” available in Apollo Server 2.0. For e.g. declare @auth on FIELD_DEFINITION type X { flight_number: ID details: String @auth(requires: [“P1”]) } ensures that the filed will resolve to a value that’s dictated via the “@auth” directive. The implementation of the directive is to extend the “SchemaDirectiveVisitor” class available in Apollo Server. Implementing two main methods “visitFieldDefinition” and/or “visitObject” help in controlling how the resolver for the types (against which the directive is used) should resolve into. For e.g.: class AuthDirective extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField<any, any>) { // …… } } To add, we use the custom directive not just at a field level, but at type levels as well: type Y @auth(requires:[“P3”, “P4”]) { ….. Z: String @auth(requires:[“P4”]) } // or type Query { getX: X @auth(requires:[“P1”]) } To however use/apply the directive, Apollo needs to know about it and hence we pass the directive’s class to Apollo server’s constructor: const server = new ApolloServer({ typeDefs, resolvers, schemaDirectives: { auth: AuthDirective } }); Custom directives truly abstract away the heavy-lifting from developers and make it abundantly clear regarding the needs/requirements for a given field/type. STATE MANAGEMENT and PERFORMANCE CACHING For Apollo Client (AC), we use the InMemoryCache for caching and state management. However it’s important to evaluate the query performance. AC implements normalisation (converting the graph into a flat data structure while storing) for queries that it writes/reads to/from the cache. It does incur significant costs (data dependent) while normalisation and is NOT opt-out enabled. Hermes, is one potential alternate which stores data as a “normalised graph” instead of a “normalised map”. This looks promising and we’re evaluating if our current data volume would cause any performance regression. NETWORK BATCHING Our pages are made of segments which each segment potentially having its own data provider. This situation ends up in having multiple network calls from the client thereby increasing roundtrips. (Conditional) Batching network requests helps reduce the overall number of requests to our servers and thereby RTT. However, batching comes at a cost. The performance of a batch is as good as the slowest request in the batch. By “Conditional”, we mean use batching only when needed and to not batch for important/expensive/computationally-intensive requests. OPTIMISTIC RESPONSE To have a fast, 0 latency server response for mutations, we resort to “Optimistic response” wherein we add a manually created mutation response so that the UI changes could be instantly done.This also helps reduce number of network calls as the updated result (inclusive of the current mutation) is already present at the client. REACT and PERFORMANCE a. HOOKS - We use React hooks implementation over class based. For reasons related to better composability, a PULL based approach vs a PUSH based and also the fact that Hooks minify better. For e.g. Promise.resolve().then(value => getValue(value)); // PUSH // vs const value = await getSomeValue(); // PULL b. MEMOIZATION - React hooks provide memoization techniques for components using “React.memo”, while functions/callbacks can be memoized using “useCallback” hook. Computationally expensive items could be memoized using “useMemo” hook. CONCLUSION Building our ads platform has been nothing less than a roller coaster ride. When we look back at our fundamental pillars of development i.e. UI/UX, DX, Data etc. it seems we did do some justice to them. We’re not resting and are looking forward (and eagerly waiting for) to: 1. Experimenting with Hermes cache 2. Migrating our apollo client code from render props to hooks for further code bloat reduction and maintainability (and consistency).
ABOUT Abinash Mohapatra
I am Abinash. I am a UI engineer at Flipkart. I began my career here with building Flipkart Lite, India’s first large scale eCommerce PWA. My areas of focus in FK Lite have been mostly on performance, service workers, animations etc. I am a “customer first” centric developer and for me, UX is paramount. I have also worked on client side image processing like resizing, resampling. My areas of interest include Service Workers, Performance, Animations, JS/Storage Parallelism using Workers, WebAssembly. Recently I have been intrigued by GraphQL and have been working on it for around close to a year. Apart from this, I talk about Sneakers and bikes too !! And who doesn’t love Sneakers and bikes, as they say “You never see a motorcycle outside a psychiatrist’s office."
Bengaluru, Karnataka