GraphQL Vs REST - Glenn Engstrand

Transcription

GraphQL vs RESTby Glenn EngstrandIf you are in a technology company whose application stackincludes Node.js and you are considering writing your nextmicroservice in GraphQL and / or TypeScript, then this article isfor you. I developed, evaluated and compared two microservices;one in GraphQL and TypeScript, the other in Express andJavaScript. Here we cover that analysis in terms of architecture,historical context, design, code, and performance under load.The predominant approach tomodern API design is known asREST which was introduced byRoy Fielding in 2000. In 2015,Facebook introduced a competingapproach called GraphQL whichhas steadily risen in popularitysince then. There are about twotechnology conferences per yeardevoted to GraphQL. Dependingon how you measure this,GraphQL currently gets about aquarter to a half as muchattention as REST. For those ofyou who are into "Crossing theChasm" style technology adoptiontrends, GraphQL is now in what isknown as the Early Adopterphase. With REST, you typicallyCopyright 2019 Glenn Engstrand.code handlers for each path andmethod combination. Eachhandler creates the entireresponse to each request. WithGraphQL, you define a singleschema. You are supposed tocode resolvers for each field thenlet the framework stitch theindividual field responsestogether in order to form acoherent response to the inboundrequest.p. 1 of 10 pp

technology adoption, it isconsidered to be in the EarlyMajority phase most probablydue to it being the defaultlanguage for the Angular webframework.Microsoft first released theTypeScript programminglanguage in 2012. TypeScript istranspiled to JavaScript whichgets run. What makesTypeScript more interestingthan JavaScript is that statictype checking transpiler whichlets you catch more bugs inyour code sooner. TypeScriptwas originally intended to berun in the web browser. I firstdeveloped the Node basednews feed microservice inJanuary 2017 but I choseJavaScript back then becauseTypeScript wasn't really readyyet for running server-side inNode. Interest in TypeScript isstill a small fraction (one tenthto one fifth) when compared toJavaScript. In terms ofCopyright 2019 Glenn Engstrand.I thought that now might be agood time to see how matureserver side TypeScript is interms of microservicedevelopment. I also wanted toexplore GraphQL in order tofully understand its merits. So Ideveloped a rudimentary newsfeed microservice that issimilar to my originalJavaScript microservice basedon the Express framework(feed 4) only this time inTypeScript and based on theApollo Server for GraphQL(feed 10). All of this code isopen source that you can findin my personal github repo. Ineeded to evaluate feed 10under load so I also had toenhance the load testapplication (load) and the APIgateway (proxy) in my test labbecause GraphQL APIs are verydifferent from RESTful APIs.p. 2 of 10 pp

ArchitectureIn order to better understand the relative merits of these twomicroservices, let us compare and contrast their respectivesoftware architectures.We want to be able to makea valid comparison betweenfeed 4 (Express andJavaScript) and feed 10(GraphQL and TypeScript)so both microservices needto be as feature identical aspossible. This means thatthere are a lot of similaritiesbetween the twoimplementations.Both microservices are polyglotpersistent. They both use MySqlfronted by Redis for participantsand friends, Cassandra forinbound and outbound feed items,and Elasticsearch for keywordbased search. Both microservicesrun in Node which has embeddedwithin it Google's open source V8engine for running singlethreaded JavaScript.There are also profound differences in the architectures ofthese two microservices.Node provides the applicationwith a single-threadedruntime so every method callhas to be non-blocking. Forfeed 4, I used the callbackmechanism where eachfunction is passed as part ofits arguments two functionsfor handling success anderror results. For feed 10, Iused the async and awaitmechanism which depends onpromises.Copyright 2019 Glenn Engstrand.Having to rewrite a low-levelcomponent in order to makesomething as fundamentalas unit tests working is not agood sign in terms ofsoftware maturity.The API design for feed 4 issomewhat RESTful. In the URL foreach API call, the path identifiesthe main entity; participants,friends, inbound, and outbound.The HTTP method is POST forcreates and GET for fetches.p. 3 of 10 pp

Here is an illustrative REST example which fetches the inboundfeed items for participant 4 from feed 4.curl {FEED URL}/inbound/4[{“occurred”:”2019-11-02”, "subject":"test subject", “story”:”test story”}]The GraphQL API design for feed 10 consists of three mutationsand two queries. There is a single schema where friends,inbound, and outbound are all attributes of each participant.In this example request, the query as before is shown only thistime in GraphQL. Notice that the request specifies what shouldget returned in the response.curl \-X POST \-H "Content-Type: application/json" \--data '{ "query": "query { participant(id: 4) { inbound { occurred, subject, story } } }"}' \ FEED ”:”2019-11-02”, "subject":"test subject",“story”:”test story”}]}}}Historical ContextIn the days before REST, API designwas RPC style where every endpointwas basically a Remote Procedure Call.Like America’s wild, wild West periodin history, this was a very chaotic timefor APIs. Developers designed them inorder to expedite the immediatefeature at hand. Typically, this includedintroducing hidden side effects to theAPIs such that you had no idea if theirbehavior was a feature or a bug. WhatREST did was introduce some basicrules of the road that made it easier toreason about APIs, to learn them, andto write test automation for them.Copyright 2019 Glenn Engstrand.For a large enterprisewith a complex businessmodel, RESTful APIstend to have a largenumber of endpoints.Without adequate andup-to-datedocumentation, it couldbe difficult to figure outwhich endpoint to call.Perhaps that is whySwagger (also known asOpen API) has steadilyincreased in popularityover the past five years.p. 4 of 10 pp

With GraphQL, you have a single endpoint. There are queries,mutations, and a schema. Queries allow you to write little miniprograms you send to that single endpoint that fetch data in thecontext of the schema. Mutations, however, are just straight upRPCs.DesignWhat are the major differences between the technology designsof these two microservices?The API for feed 4 is based on Node http module for accessingthe same Swagger specification Elasticsearch.as the feed 3 - 9implementations. Thisimplementation uses a Nodepackage called swagger-toolswhich reads the Swagger specthen generates the routing forthat spec within the Expressframework. That routing mapseach request to thecorresponding controller which,in turn, invokes the appropriateservice. There are usually twohandlers for each entity. One forGET and one for POST. Theoutbound entity has a thirdhandler for keyword basedsearch. The feed 4implementation uses the lowlevel drivers for Cassandra,MySql, and Redis. It uses theCopyright 2019 Glenn Engstrand.p. 5 of 10 pp

In feed 10, the schema, queries, and the mutations are specifiedas a set of type definitions. The connections to the underlyingdatastores are all opened and used to initialize the services forparticipants, friends, inbound, and outbound. In the resolvers,each field in the schema and all of the mutations are mapped totheir respective service calls. The GraphQL server is theninitialized with these type definitions and resolvers then startedin order for the service to begin listening on the configured port.Instead of the low level MySql driver, the feed 10 service usesTypeORM where annotated TypeScript classes map relationaldatabase tables to and from objects. The actual GraphQL serveritself comes from the graphql-yoga project. The other viablealternative for Node is the Apollo server which is more popular. Ichose the graphql-yoga project because it wraps the Apolloserver in a way that is simpler to comprehend and easier to use.CodeWhat was learned in the actual coding of these twomicroservices?Copyright 2019 Glenn Engstrand.p. 6 of 10 pp

Let’s start with somerudimentary static codeanalysis. In the feed 4microservice, there are a totalof 634 Lines of Code with a perfile average of 39 LoC. In thefeed 10 microservice, there area total of 422 LoC with a perfile average of 42 LoC.Cyclomatic complexity for feed4 is 189 and for feed 10 is 672.I could not find a workingpackage that computed thecyclomatic complexity forTypeScript so I first transpiledeach file into JavaScript thencomputed the cyclomaticcomplexity with the same toolthat I used for feed 4.Copyright 2019 Glenn Engstrand.I was not successfully able tocreate a working unit test forthis service. I tried manydifferent test frameworks andmocking libraries. The troubleis with transpiling the servicecode that uses the Redis client.That driver is old school withcallbacks. I use a package,called then-redis, which wrapsthe Redis client in promisessuch that it can be called bythe async and awaitmechanism. This worksperfectly in the service itselfbut confuses the transpile forthe unit test run whichcomplains that the return valuefor redis.get method should beboolean instead of a string.p. 7 of 10 pp

Could I have found a way around this problem? Sure. I couldhave written my own Redis wrapper in such a way as tocompletely hide the actual Redis client. Part of the evaluation toany technology choice includes the entire ecosystem of relatedcomponents. Having to rewrite a low-level component in order tomake something as fundamental as unit tests working is not agood sign in terms of software maturity.PerformanceHow did the GraphQL on TypeScript microservice compare tothe Express on JavaScript microservice when it came toperformance under load?The usual performance analysisfor these microservices focuseson capturing and analyzing theper minute throughput andlatency of the create outboundcall because that API does themost work. For feed 10,throughput was 9,817 RPM witha mean latency of 5 ms, amedian latency of 4 ms, and a99th percentile of 15 ms. Forfeed 4, throughput was 12,629RPM with a mean latency of 5ms, a median latency of 5 ms,and a 99th percentile of 12 ms.Copyright 2019 Glenn Engstrand.p. 8 of 10 pp

I also wanted to analyze the performance for the createparticipant calls because that endpoint exercises MySql which isa big difference between the two implementations. The feed 10microservice uses TypeORM instead of the low level MySqldriver. For feed 10, average throughput was 3,874 RPM with anaverage latency of 10 ms. For feed 4, throughput was 4,995 RPMwith an average latency of 7 ms.The feed 10 service had profiling turned on during its load test.A significant amount of time was spent parsing the GraphQLrequests and in the TypeORM module but not quite enough tocompletely account for the performance differences.ConclusionWhich is better for microservice development in 2019? GraphQLor Express? TypeScript or JavaScript?Copyright 2019 Glenn Engstrand.p. 9 of 10 pp

The GraphQL on TypeScriptservice was 22% less efficientthan the Express on JavaScriptservice yet required a third lesscode to implement. Perhaps theformer would be morecompelling than the latter if youwere willing to pay a biggercloud bill in order to achieve afaster feature velocity. For realworld applications, there is a lotof devops maturity needed forfeature velocity so your resultsmay vary.Subjectively, I would say thatthe GraphQL on TypeScriptcode was less complex thanthe Express on JavaScriptcode. The framework orientedstyle of GraphQL may requirea little getting used to byserver-side JavaScriptdevelopers. Some of thatcomplexity got pushed over tothe applications that call themicroservice becauseGraphQL APIs are harder toconsume than RESTful APIs.Before REST API design was like the wild, wild, West.I prefer static typing which causes me to favor TypeScript overJavaScript but there are still some compelling maturity issuesthat need to be resolved before I would feel completelycomfortable recommending TypeScript on Node right now. Partof the reason why feed 10 was less complex than feed 4 wasbecause async and await is a lot easier to code than callbacks.Modern JavaScript that runs in Node can also use the async andawait mechanism.I feel like GraphQL queries could be a good fit for orchestrationservices. Also known as Backends for Frontends, these types ofservices don’t access databases directly. Instead, BfFsorchestrate the calling of other microservices (known as dataAPIs) in order to fetch data. In that way, a GraphQL servicecould act as a simplifying facade over a complex collection ofRESTful microservices.Copyright 2019 Glenn Engstrand.p. 10 of 10 pp

and friends, Cassandra for inbound and outbound feed items, and Elasticsearch for keyword based search. Both microservices run in Node which has embedded within it Google's open source V8 engine for running single-threaded JavaScript. There are also profound differences in the architectures of these two microservices. Node provides the application with a single-threaded runtime so every method .