Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Maximilian Fellner

December 06, 2018
Tweet

More Decks by Maximilian Fellner

Other Decks in Programming

Transcript

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

    and over €1.5 billion in monthly transaction volume
  2. BY READING THIS TEXT YOU AGREE WITH THE TERMS AND

    CONDITIONS OF THE PRESENTATION.
  3. WHAT'S A MICRO SERVICE ANYWAY? independently deployable explicit interface organized

    around business capability communicate with simple protocols
  4. <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
  5. 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
  6. 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
  7. 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"); }
  8. 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.
  9. 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
  10. 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)
  11. 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 ✨
  12. 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
  13. 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)
  14. 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
  15. 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
  16. Summary React components can be easily loaded over the network

    UMD (and AMD) modules are still useful Apollo can load (almost) anything with GraphQL
  17. 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
  18. 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
  19. 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; } }
  20. 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
  21. 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
  22. …but we can just use plain descriptions instead ♂ type

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

    can deal with REST APIs Namespacing and authorization can be handled