Distributed Web Applications with GraphQL (N26)

Distributed Web Applications with GraphQL (N26)

By their nature, user facing applications are generally designed in a monolithic style. But what if we could build distributed web apps that are more like microservices? With GraphQL, Apollo and React we have the necessary tools to create a modular and scalable architecture. However, there are some challenges to overcome: merging different API graphs, handling authorization and loading JavaScript modules, to name a few. In this talk we’re going to explore practical solutions to all of them.

F9bf9c0311137e6c4878d74cd54a8e41?s=128

Maximilian Fellner

December 06, 2018
Tweet

Transcript

  1. BUILDING DISTRIBUTED WEB APPLICATIONS WITH GRAPHQL Maximilian Fellner

  2. Hi, I'm Max

  3. @mxfellner github.com/mfellner

  4. more than 500 employees over 2 million customers 22 markets

    and over €1.5 billion in monthly transaction volume
  5. * not a real bank

  6. BY READING THIS TEXT YOU AGREE WITH THE TERMS AND

    CONDITIONS OF THE PRESENTATION.
  7. FULL STACK

  8. None
  9. LET'S BUILD A WEB APP

  10. None
  11. None
  12. None
  13. None
  14. None
  15. None
  16. None
  17. None
  18. ARCHITECTURE

  19. None
  20. YOU REALIZE YOUR FRONTEND IS STILL A BIG BALL OF

    MUD
  21. MICRO SERVICES ✨

  22. FRONTEND MICRO SERVICES

  23. WHAT'S A MICRO SERVICE ANYWAY? independently deployable explicit interface organized

    around business capability communicate with simple protocols
  24. ALTERNATIVES FOR FRONTEND MICRO SERVICES OR "MICRO FRONTENDS"

  25. Portal website easy individual websites work independently not actually frontend

    micro services
  26. <iframe></iframe> ".htm" and ".html" redirect here. For other uses, see

    HTM. For the use of HTML on Wikipedia, see Help:HTML in wikitext. Hypertext Markup Language (HTML) is the standard markup language for creating web pages and web applications. With Cascading Style Sheets (CSS) and JavaScript, it forms a triad of cornerstone technologies for the World Wide Web.[4] HTML HTML (Hypertext Markup Language) simple, standard HTML tag secure Window.postMessage() slow difficult to style not well integrated
  27. HTML templates <html> <head> <script type="fragment" src="http://assets.domain.com"></script> </head> <body> <fragment

    src="http://header.domain.com"></fragment> <fragment src="http://content.domain.com" primary></fragment> <fragment src="http://footer.domain.com" async></fragment> </body> </html> Mosaic by Zalando fast consistent appearance fragments can include their own JavaScript no integration on the application level duplicate JS resources requires template server
  28. JavaScript components consistent appearance consistent behavior integration on the application

    level all modules must use the same technology bad code can break the entire application
  29. HOW TO LOAD JAVASCRIPT MODULES FOR COMPONENT-BASED WEB APPLICATIONS

  30. ECMAScript modules // my-component.mjs import React from "react"; export const

    MyComponent = () => React.createElement("h1", null, "Hello, World!"); // application.js async function main() { const { MyComponent } = await import("https://cdn.com/my-component.mjs"); }
  31. React.lazy // application.jsx import React, { lazy, Suspense } from

    "react"; const MyComponent = lazy(() => import("https://cdn.com/my-component.mjs") ); const Application = () => { return ( <Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense> ); }; New API since React 16.6.
  32. Support for ECMAScript modules dynamic import .mjs import maps https://developers.google.com/web/fundamentals/primers/modules

  33. 1. // https://github.com/umdjs/umd/blob/master/templates/returnExports.js 2. (function(root, factory) { 3. if (typeof

    define === "function" && define.amd) { 4. // AMD. Register as a named module. 5. define('MyComponent', ['react'], factory); 6. } else if (typeof module === "object" && module.exports) { 7. // Export for Node.js. 8. module.exports.MyComponent = factory(require('react')); 9. } else { 10. // Browser global (root is window). 11. root.MyComponent = factory(root.React); 12. } 13. })(typeof window !== "undefined" ? window : this, function(React) { 14. // Export a React component. 15. return () => 16. React.createElement("h1", null, "Hello, World!"); 17. }); UMD modules: universal module de nition
  34. Advantages of UMD modules Supported in the browser and under

    Node.js Webpack can create them automatically Dependency (imports) management works! AMD needs a loader (require.js)
  35. LOADING UMD MODULES WITH GRAPHQL

  36. The GraphQL query query myUmdQuery($url: String!) { myUmdModule @umd(url: $url)

    { MyComponent } } const variables = { url: "https://cdn.com/my-component.umd.js" }; We use a custom directive to implement the loading magic ✨
  37. 1. const ModuleLoader = ({ url }) => ( 2.

    <Query 3. query={gql` 4. query myUmdQuery($url: String!) { 5. myUmdModule @umd(url: $url) { 6. MyComponent 7. } 8. } 9. `} 10. variables={{ url }} 11. > 12 {({ error loading data }) => { The module loader component
  38. Loading UMD modules with Apollo Supports server-side rendering Fine control

    over queries Queries can return additional information Requires custom directive implementation (with Apollo link)
  39. universal-umd-fetch // Load an AMD-compatible module with require.js function umdfetch_browser(url:

    string): Promise<any> { return new Promise(resolve => { window.require([url], result => resolve(result)); }); } // Download a CommonJS module and evaluate it in Node.js async function umdfetch_server(url: string): Promise<any> { const response = await fetch(url); const code = await response.text(); return vm.runInContext(code, sandbox); } http://requirejs.org https://nodejs.org/api/vm.html
  40. apollo-link-umd import umdfetch from "@n26/universal-umd-fetch"; import { ApolloLink, /* …

    */ } from "apollo-link"; class ApolloLinkUmd extends ApolloLink { public request( operation: Operation, forward?: NextLink ): Observable<FetchResult> | null { // If directive `@query` matches, use umdfetch: return umdfetch(url); } } https://www.apollographql.com/docs/link/overview.html
  41. Summary React components can be easily loaded over the network

    UMD (and AMD) modules are still useful Apollo can load (almost) anything with GraphQL
  42. MICRO FRONTEND ARCHITECTURE

  43. Dealing with legacy REST APIs apollo-link-rest GraphQL gateway

  44. Apollo link A link is a function that takes an

    operation and returns an observable. Links can be chained together between the client and the server. https://www.apollographql.com/docs/link/overview.html
  45. apollo-link-rest import { ApolloClient } from "apollo-client"; import { InMemoryCache

    } from "apollo-cache-inmemory"; import { RestLink } from "apollo-link-rest"; const restLink = new RestLink({ uri: "https://backend.com/api" }); const client = new ApolloClient({ link: restLink, cache: new InMemoryCache() }); query tagsQuery { tags @rest(type: "[Tag]", path: "/tags", endpoint: "v2") { tag_name posts @type(name: "Post") { post_name } } } https://www.apollographql.com/docs/link/links/rest.html
  46. GraphQL gateway A monolithic web service Connects to multiple REST

    APIs
  47. Apollo server & data sources const { RESTDataSource } =

    require("apollo-datasource-rest"); class MoviesAPI extends RESTDataSource { constructor() { super(); this.baseURL = "https://movies-api.example.com"; } async getMovie(id) { return this.get(`/movies/${id}`); } async getMostViewedMovies(limit = 10) { const data = await this.get("movies", { per_page: limit, order_by: "most_viewed" }); return data.results; } }
  48. Schema stitching and namespaces Independent GraphQL schemas are merged into

    one Schema type names may collide
  49. No namespaces in GraphQL https://github.com/facebook/graphql/issues/163

  50. graphql-tools + schema- transforms declare function transformSchema( targetSchema: GraphQLSchema, transforms:

    Array<Transform> ): GraphQLSchema; interface Transform { transformSchema?: (schema: GraphQLSchema) => GraphQLSchema; transformRequest?: (request: Request) => Request; transformResult?: (result: Result) => Result; } modify types modify root fields (Query, Mutation) https://www.apollographql.com/docs/graphql-tools/schema-transforms
  51. Authorization https://graphql.org/learn/authorization Delegate authorization logic to the business logic layer

    - Facebook
  52. Centralized authorization How can we centralize authorization logic but decentralize

    authorization information?
  53. GraphQL directives directive @auth( permission: String ) on OBJECT |

    FIELD_DEFINITION type Mutation { writeData(input: MyInput!): MyOutput! @auth(permission: "data:write") } We can handle authorization in the directive implementation Permissions are declared in the GraphQL schema https://graphql.org/learn/queries/#directives https://www.apollographql.com/docs/graphql-tools/schema- directives.html#Enforcing-access-permissions
  54. Not so fast… Directives cannot be read through introspection https://github.com/facebook/graphql/issues/300

  55. …but we can just use plain descriptions instead ♂ type

    Mutation { "@auth permission: 'data:write'" writeData(input: MyInput!): MyOutput! }
  56. Summary Micro frontends and micro backends can exist together We

    can deal with REST APIs Namespacing and authorization can be handled
  57. Thanks! speakerdeck.com/mfellner/distributed-web- applications-with-graphql maximilianfellner.eu