Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

GraphQL with .NET Core

163 views

Published on

GraphQL with .NET Core

Published in: Internet
  • Be the first to comment

  • Be the first to like this

GraphQL with .NET Core

  1. 1. @MarkLechtermann Mark Lechtermann @MarkLechtermann GraphQL with .NET CORE
  2. 2. @MarkLechtermann What‘s wrong with REST?
  3. 3. @MarkLechtermann Architectural Styles and the Design of Network-based Software Architectures DISSERTATION DOCTOR OF PHILOSOPHY Roy Thomas Fielding 2000
  4. 4. @MarkLechtermann
  5. 5. @MarkLechtermann 2000
  6. 6. @MarkLechtermann
  7. 7. @MarkLechtermann The internet in the year 2000
  8. 8. @MarkLechtermann not before 2002
  9. 9. @MarkLechtermann not before 2004
  10. 10. @MarkLechtermann 2019
  11. 11. @MarkLechtermann 2000 vs 2019
  12. 12. @MarkLechtermann Web 2000 GET http://example.com static HTML
  13. 13. @MarkLechtermann Web 2000 GET http://example.com static HTML
  14. 14. @MarkLechtermann Web 2019 I want JSON! I want HTML! I want JS! I want IOT data! I want a stream!
  15. 15. @MarkLechtermann Web 2019 I want JSON! I want HTML! I want JS! I want IOT data! I want a stream!
  16. 16. @MarkLechtermann Again: What‘s wrong with REST?
  17. 17. @MarkLechtermann Over-fetching GET http://example.com/api/v1/whiskys/1 { "name":"ArdbegTEN", "strength":46, "distillery":"Ardbeg", "size":"700 ml", "prize":45.00, … }
  18. 18. @MarkLechtermann Over-fetching GET http://example.com/api/v1/whiskys/1 { "name":"ArdbegTEN", "strength":46, "distillery":"Ardbeg", "size":"700 ml", "prize":45.00, … } But all I wanted was the prize!
  19. 19. @MarkLechtermann Under-fetching GET http://example.com/api/v1/whiskys "items":[ "http://example.com/api/v1/whiskys/1" "http://example.com/api/v1/whiskys/2" … ] GET http://example.com/api/v1/whiskys/1 GET http://example.com/api/v1/whiskys/2 ...
  20. 20. @MarkLechtermann Under-fetching GET http://example.com/api/v1/whiskys "items":[ "http://example.com/api/v1/whiskys/1" "http://example.com/api/v1/whiskys/2" … ] GET http://example.com/api/v1/whiskys/1 GET http://example.com/api/v1/whiskys/2 ... But all I wanted was a list of names!
  21. 21. @MarkLechtermann REST Properties ● Performance ● Reliability ● Simplicity ● Scalability ● Modifiability ● Portability
  22. 22. @MarkLechtermann REST Benefits ● Performance (with HTTP2) ● Well-established ● Media types ● Decoupling server and client
  23. 23. @MarkLechtermann The problem with REST ● Difficult to implement correctly ● Tooling for clients ● API description format – Swagger, RAML, API Blueprint, Odata...? ● It's not a Standard
  24. 24. @MarkLechtermann We call it a "REST"-API
  25. 25. @MarkLechtermann Richardson Maturity Model HATEOAS HTTP Verbs Resources Swamp of POX
  26. 26. @MarkLechtermann Let's be honest! HATEOAS?
  27. 27. @MarkLechtermann But what about OpenAPI/Swagger?
  28. 28. @MarkLechtermann OpenAPI/Swagger kills Hypermedia!
  29. 29. @MarkLechtermann Nobody stops us from using the endpoint directly!
  30. 30. @MarkLechtermann REST without Hypermedia is CRUD over HTTP!
  31. 31. @MarkLechtermann
  32. 32. @MarkLechtermann
  33. 33. @MarkLechtermann react.js conf 2015 not only for React!
  34. 34. @MarkLechtermann Knots and Edges not Resources
  35. 35. @MarkLechtermann Spec ● https://graphql.github.io/graphql-spec/ ● Latest stable version – June 2018
  36. 36. @MarkLechtermann Let's build an App
  37. 37. @MarkLechtermann Whisky Distillery 0..1 * WhiskyApp
  38. 38. @MarkLechtermann Whisky Distillery 0..1 * WhiskyApp Iknow! Independentbottler,blends,… butKISS!
  39. 39. @MarkLechtermann Whisky +Name : string +Age : uint +Strength: float + Size: uint Distillery +Name : string +Owner : string +SpiritStills : uint +WashStills : uint +Capacity : uint64 +Region : string 0..1 * WhiskyApp
  40. 40. @MarkLechtermann
  41. 41. @MarkLechtermann
  42. 42. @MarkLechtermann We need Components!
  43. 43. @MarkLechtermann whiskys
  44. 44. @MarkLechtermann whiskys name age
  45. 45. @MarkLechtermann whiskys name age distillery
  46. 46. @MarkLechtermann whiskys name age distillery name owner
  47. 47. @MarkLechtermann { whiskys { name age distillery { name owner } } }
  48. 48. @MarkLechtermann { whiskys { name age distillery { name owner } } } GraphQL Query APPROVED
  49. 49. @MarkLechtermann
  50. 50. @MarkLechtermann A query language for your API
  51. 51. @MarkLechtermann GraphQL Feature ● Query ● Mutation ● Subscription
  52. 52. @MarkLechtermann Schema
  53. 53. @MarkLechtermann Schema schema { query: WhiskyRootQuery mutation: WhiskyRootMutation }
  54. 54. @MarkLechtermann Scalar Types type Whisky { MyField1 : ID // unique identifier String MyField2 : Int MyField3 : Float // signed double-precision MyField4 : String // UTF-8 MyField5 : Boolean }
  55. 55. @MarkLechtermann Type type WhiskyRootQuery { whiskys: [Whisky] whisky(id: ID): Whisky }
  56. 56. @MarkLechtermann Lists and Non-Null type WhiskyRootQuery { whiskys: [whisky!]! whisky(id: ID!): whisky }
  57. 57. @MarkLechtermann Interfaces interface Drink { id: ID! name: String! }
  58. 58. @MarkLechtermann Implements Interface type Whisky implements Drink { region: WhiskyRegion }
  59. 59. @MarkLechtermann Enumerations enum WhiskyRegion { LOWLANDS HIGHLANDS SPEYSIDE CAMPELTOWN ISLAY THE ISLANDS }
  60. 60. @MarkLechtermann Query
  61. 61. @MarkLechtermann Query query { whiskys { id name } } "data" { "whiskys" : [ { "id" : "1", "name": "Ardbeg Ten" } ] }
  62. 62. @MarkLechtermann Query { whiskys { id name } } "data" { "whiskys" : [ { "id" : "1", "name": "Ardbeg Ten" } ] }
  63. 63. @MarkLechtermann Query with Arguments { whisky(id: "1") { id name } } "data" { "whisky" : { "id" : "1", "name": "Ardbeg Ten" } }
  64. 64. @MarkLechtermann Alias { first : whisky(id:"1") { name } second : whisky(id:"2") { name } } { "data": { "first": { "name": "Ardbeg TEN" }, "second": { "name": "Ardbeg Uigeadail" } } }
  65. 65. @MarkLechtermann Fragments { first : whisky(id:"1") { ...myFields } second : whisky(id:"2") { ...myFields } } fragment myFields on WhiskyType{ name id strength } { "data": { "first": { "name": "Ardbeg TEN", "id": "1", "strength": 46 }, … } }
  66. 66. @MarkLechtermann Variables query Compare($a: ID!, $b: ID!){ first : whisky(id:$a) { ...myFields } second : whisky(id:$b) { ...myFields } } fragment myFields on WhiskyType { name id strength } { "data": { "first": { "name": "Ardbeg TEN", "id": "1", "strength": 46 }, … } }
  67. 67. @MarkLechtermann Directives - include query GetWhisky($id: ID!, $dInfo: Boolean = false) { whisky(id:$id) { id name destillery @include(if: $dInfo) { name } } } {"id": "1", "dInfo": true} { "data": { "whisky" : { "id" : "12, "name" : "Ardbeg TEN" "destillery" : { "name" : "Ardbeg" } } } }
  68. 68. @MarkLechtermann Directives - skip query GetWhisky($id: ID!, $dInfo: Boolean = false) { whisky(id:$id) { id name destillery @skip(if: $dInfo) { name } } } {"id": "1", "dInfo": true} { "data": { "whisky": { "id": 1, "name": "Ardbeg TEN", } } }
  69. 69. @MarkLechtermann Mutation
  70. 70. @MarkLechtermann mutation mutation { deleteWhisky(id: "1") } { "data" : { "deleteWhisky" : true } }
  71. 71. @MarkLechtermann mutation mutation { addWhisky( destilleryId : "1" whisky : { name : "MyWhisky" age : 0 size : 70 strength : 40 } ) { id name } } { "data" : { "addWhisky" : { "id" : "123", "name" : "MyWhisky", } } }
  72. 72. @MarkLechtermann Let‘s start with .NET Core
  73. 73. @MarkLechtermann .NET Libraries ● graphql-dotnet/graphql-dotnet (~3000) ● ckimes89/graphql-net ( ~700) ● ChilliCream/hotchocolate ( ~300)
  74. 74. @MarkLechtermann We use graphql-dotnet/graphql-dotnet in this example
  75. 75. @MarkLechtermann Code First or Schema First
  76. 76. @MarkLechtermann $ dotnet new webapi $ dotnet add package GraphQL $ dotnet add package GraphQL.Server.Transports.AspNetCore # Optional: $ dotnet add package GraphQL.Server.Ui.GraphiQL $ dotnet add package GraphQL.Server.Ui.Playground $ dotnet add package GraphQL.Server.Ui.Voyager Create a new project
  77. 77. @MarkLechtermann public class WhiskyType : ObjectGraphType<WhiskyEntity> { public WhiskyType() { Field<NonNullGraphType<IdGraphType>>().Name("id"); Field(entity => entity.Name).Description(""); } } Add Types
  78. 78. @MarkLechtermann public class WhiskyQuery : ObjectGraphType { public WhiskyQuery() { this.Field<WhiskyType>( name: "whisky", resolve: context => new WhiskyType()); } Add Query
  79. 79. @MarkLechtermann public class WhiskyAppSchema : Schema { public WhiskyAppSchema(IDependencyResolver resolver) : base(resolver) { Query = resolver.Resolve<WhiskyQuery>(); Mutation = ... } } Add a Schema
  80. 80. @MarkLechtermann public void ConfigureServices(IServiceCollection services) { services.AddScoped<WhiskyType>(); services.AddScoped<WhiskyQuery>(); services.AddScoped<ISchema, WhiskyAppSchema>(); services.AddScoped<IDependencyResolver>( s => new FuncDependencyResolver(s.GetRequiredService)); services.AddSingleton<IDocumentExecuter, DocumentExecuter>(); services.AddSingleton<IDocumentWriter, DocumentWriter>(); services.AddGraphQL(); } Add Services
  81. 81. @MarkLechtermann app.UseGraphQL<ISchema>("/graphql"); // Optional: app.UseGraphiQLServer(new GraphiQLOptions()); app.UseGraphQLPlayground(new GraphQLPlaygroundOptions()); app.UseGraphQLVoyager(new GraphQLVoyagerOptions()); Add Middleware
  82. 82. @MarkLechtermann N + 1 Problem and Batching!
  83. 83. @MarkLechtermann Use a Dataloader
  84. 84. @MarkLechtermann services.AddSingleton<IDataLoaderContextAccessor, DataLoaderContextAccessor>(); services.AddSingleton<DataLoaderDocumentListener>(); DataLoader - Services
  85. 85. @MarkLechtermann public WhiskyType( IDataLoaderContextAccessor accessor) { … Field<DestilleryType, DestilleryEntity>() .Name("destillery") .ResolveAsync(context => { var loader = accessor.Context.GetOrAddBatchLoader<string, DestilleryEntity>("whisky_destillery", ... ); return loader.LoadAsync(context.Source.Id); }); } DataLoader
  86. 86. @MarkLechtermann Pro GraphQL ● Easy to learn ● Vendor agnostic ● Pragmatic ● Contract ● Introspection
  87. 87. @MarkLechtermann Contra GraphQL ● Content negotiation ● Media type support ● Caching ● "Only" POST – What about GET, DELETE, PUT?
  88. 88. @MarkLechtermann API Gateway REST JSON over HTTP OData gRPC
  89. 89. @MarkLechtermann What are the alternatives?
  90. 90. @MarkLechtermann REST! ;-) ...
  91. 91. @MarkLechtermann … with OData
  92. 92. @MarkLechtermann OData @ Build 2019 ● Microsoft PowerApps – https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/webapi/overview ● Build2019: Microsoft Graph powers the Microsoft 365 platform – https://developer.microsoft.com/en-us/office/blogs/build-2019-microsoft-graph-powers-the-microsoft-365-platform/ ● Graph Explorer – https://developer.microsoft.com/en-us/graph/graph-explorer ● Expamples: – https://graph.microsoft.com/v1.0/me/?$select=givenName – https://graph.microsoft.com/v1.0/me?$select= displayName, skills
  93. 93. @MarkLechtermann Contra OData ● Strong coupling with the database ● Query only with GET – Long and complex URL
  94. 94. @MarkLechtermann docker run -p 5000:5000 marklechtermann/whiskygraphqlapp Docker Image
  95. 95. @MarkLechtermann https://github.com/marklechtermann/ whiskygraphqlapp Source Code
  96. 96. @MarkLechtermann Thanks! Any Questions?

×