├── part4
├── host.json
├── GraphQL
│ ├── sample.dat
│ ├── API
│ │ ├── app.js
│ │ ├── schema.js
│ │ └── resolvers.js
│ ├── function.json
│ └── index.js
├── .funcignore
├── proxies.json
├── solution
│ ├── host.json
│ ├── API
│ │ ├── Mutation.cs
│ │ ├── Data.cs
│ │ ├── Schema.cs
│ │ └── Query.cs
│ ├── solution.csproj
│ ├── GraphQL.cs
│ └── .gitignore
├── package.json
└── .gitignore
├── part5
├── host.json
├── .funcignore
├── proxies.json
├── package.json
├── solution
│ ├── host.json
│ ├── API
│ │ ├── Mutation.cs
│ │ ├── Data.cs
│ │ ├── Schema.cs
│ │ └── Query.cs
│ ├── solution.csproj
│ ├── GraphQL.cs
│ └── .gitignore
└── .gitignore
├── .gitignore
├── docs
├── .vuepress
│ ├── public
│ │ ├── favicon.ico
│ │ ├── Bit_Learning.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── apple-touch-icon.png
│ │ ├── workshop-apollo-web.png
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ └── site.webmanifest
│ ├── styles
│ │ └── index.styl
│ └── config.js
├── workshop
│ ├── 6.md
│ ├── 1.md
│ ├── README.md
│ ├── CODE_OF_CONDUCT.md
│ ├── 5.md
│ ├── 3.md
│ ├── 4.md
│ └── 2.md
├── README.md
└── pt-br
│ ├── README.md
│ ├── workshop
│ ├── 1.md
│ ├── README.md
│ ├── 5.md
│ ├── 3.md
│ └── 4.md
│ └── CODE_OF_CONDUCT.md
├── .editorconfig
├── part3
├── products
│ ├── products.csproj
│ ├── appsettings.Development.json
│ ├── appsettings.json
│ ├── Dockerfile
│ ├── README.md
│ ├── WeatherForecast.cs
│ ├── Program.cs
│ ├── Controllers
│ │ ├── DefaultController.cs
│ │ └── WeatherForecastController.cs
│ ├── Properties
│ │ └── launchSettings.json
│ └── Startup.cs
├── reviews
│ ├── reviews.csproj
│ ├── appsettings.Development.json
│ ├── appsettings.json
│ ├── Dockerfile
│ ├── README.md
│ ├── WeatherForecast.cs
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Controllers
│ │ ├── ReviewsController.cs
│ │ └── WeatherForecastController.cs
│ └── Startup.cs
└── docker-compose.yaml
├── CONTRIBUTING
├── part2
└── dotnet
│ ├── part1
│ └── app
│ │ ├── Query.cs
│ │ ├── app.csproj
│ │ └── Program.cs
│ └── part2
│ └── app
│ ├── app.csproj
│ ├── Mutation.cs
│ ├── Program.cs
│ ├── Data.cs
│ ├── Schema.cs
│ └── Query.cs
├── part1
├── package.json
└── app.js
├── package.json
├── LICENSE
└── README.md
/part4/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0"
3 | }
4 |
--------------------------------------------------------------------------------
/part5/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0"
3 | }
4 |
--------------------------------------------------------------------------------
/part4/GraphQL/sample.dat:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Azure"
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn.lock
3 | .DS_Store
4 | bin
5 | obj
6 | .vscode
--------------------------------------------------------------------------------
/part4/.funcignore:
--------------------------------------------------------------------------------
1 | *.js.map
2 | *.ts
3 | .git*
4 | .vscode
5 | local.settings.json
6 | test
7 | tsconfig.json
--------------------------------------------------------------------------------
/part4/proxies.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/proxies",
3 | "proxies": {}
4 | }
5 |
--------------------------------------------------------------------------------
/part5/.funcignore:
--------------------------------------------------------------------------------
1 | *.js.map
2 | *.ts
3 | .git*
4 | .vscode
5 | local.settings.json
6 | test
7 | tsconfig.json
--------------------------------------------------------------------------------
/part5/proxies.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/proxies",
3 | "proxies": {}
4 | }
5 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/HEAD/docs/.vuepress/public/favicon.ico
--------------------------------------------------------------------------------
/docs/.vuepress/public/Bit_Learning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/HEAD/docs/.vuepress/public/Bit_Learning.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/HEAD/docs/.vuepress/public/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/HEAD/docs/.vuepress/public/favicon-32x32.png
--------------------------------------------------------------------------------
/docs/workshop/6.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🍻 Conclusion and Challenges
3 | ---
4 |
5 | # 🍻 Conclusion and Challenges
6 |
7 | ## What we will build
8 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/HEAD/docs/.vuepress/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/workshop-apollo-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/HEAD/docs/.vuepress/public/workshop-apollo-web.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/HEAD/docs/.vuepress/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/HEAD/docs/.vuepress/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = false
9 | insert_final_newline = false
--------------------------------------------------------------------------------
/part3/products/products.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/part3/reviews/reviews.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/part5/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "part4",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "test": "echo \"No tests yet...\""
7 | },
8 | "dependencies": {
9 | },
10 | "devDependencies": {}
11 | }
12 |
--------------------------------------------------------------------------------
/CONTRIBUTING:
--------------------------------------------------------------------------------
1 | I welcome any changes like:
2 | - suggestions for clearer coder.
3 | - Fixing spelling errors
4 |
5 | For any of the above, please raise a PR
6 |
7 | and if you have a suggestion for a feature, let me know by raising an issue.
8 |
--------------------------------------------------------------------------------
/part2/dotnet/part1/app/Query.cs:
--------------------------------------------------------------------------------
1 | using GraphQL;
2 |
3 | namespace app
4 | {
5 | public class Query
6 | {
7 | [GraphQLMetadata("hello")]
8 | public string GetHello()
9 | {
10 | return "World";
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/part3/products/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/part3/reviews/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/part3/reviews/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
11 |
--------------------------------------------------------------------------------
/part4/GraphQL/API/app.js:
--------------------------------------------------------------------------------
1 | const schema = require('./schema');
2 | const resolvers = require('./resolvers');
3 |
4 | const { ApolloServer } = require("apollo-server");
5 | const server = new ApolloServer({ typeDefs: schema, resolvers });
6 |
7 | module.exports = server;
--------------------------------------------------------------------------------
/part3/products/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
11 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
2 |
--------------------------------------------------------------------------------
/part4/solution/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingExcludedTypes": "Request",
6 | "samplingSettings": {
7 | "isEnabled": true
8 | }
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/part5/solution/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingExcludedTypes": "Request",
6 | "samplingSettings": {
7 | "isEnabled": true
8 | }
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/part3/products/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
2 | WORKDIR /src
3 | COPY products.csproj .
4 | RUN dotnet restore
5 | COPY . .
6 | RUN dotnet publish -c release -o /app
7 |
8 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
9 | WORKDIR /app
10 | COPY --from=build /app .
11 | ENTRYPOINT ["dotnet", "products.dll"]
--------------------------------------------------------------------------------
/part3/reviews/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
2 | WORKDIR /src
3 | COPY reviews.csproj .
4 | RUN dotnet restore
5 | COPY . .
6 | RUN dotnet publish -c release -o /app
7 |
8 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
9 | WORKDIR /app
10 | COPY --from=build /app .
11 | ENTRYPOINT ["dotnet", "reviews.dll"]
--------------------------------------------------------------------------------
/part4/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "part4",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "test": "echo \"No tests yet...\""
7 | },
8 | "dependencies": {
9 | "apollo-server": "^2.7.0-alpha.3",
10 | "graphql": "^14.4.1",
11 | "node-fetch": "^2.6.0"
12 | },
13 | "devDependencies": {}
14 | }
15 |
--------------------------------------------------------------------------------
/part2/dotnet/part1/app/app.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/part2/dotnet/part2/app/app.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/part3/reviews/README.md:
--------------------------------------------------------------------------------
1 | Scaffold
2 |
3 | ```
4 | dotnet new webapi -o reviews --no-https
5 | ```
6 |
7 | Dockerfile
8 |
9 | ```
10 | touch Dockerfile
11 | ```
12 |
13 |
14 | Build
15 |
16 | ```
17 | docker build -t reviews-service .
18 | ```
19 |
20 | Run
21 |
22 | ```
23 | docker run -it --rm -p 3001:80 --name reviews-service-container reviews-service
24 | ```
--------------------------------------------------------------------------------
/part3/products/README.md:
--------------------------------------------------------------------------------
1 | Scaffold
2 |
3 | ```
4 | dotnet new webapi -o products --no-https
5 | ```
6 |
7 | Dockerfile
8 |
9 | ```
10 | touch Dockerfile
11 | ```
12 |
13 |
14 | Build
15 |
16 | ```
17 | docker build -t products-service .
18 | ```
19 |
20 | Run
21 |
22 | ```
23 | docker run -it --rm -p 3000:80 --name products-service-container products-service
24 | ```
--------------------------------------------------------------------------------
/part3/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 | services:
3 | product-service:
4 | build:
5 | context: ./products
6 | ports:
7 | - "8000:80"
8 | networks:
9 | - microservices
10 | review-service:
11 | build:
12 | context: ./reviews
13 | ports:
14 | - "8001:80"
15 | networks:
16 | - microservices
17 | networks:
18 | microservices:
--------------------------------------------------------------------------------
/part4/GraphQL/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "authLevel": "anonymous",
5 | "type": "httpTrigger",
6 | "direction": "in",
7 | "name": "req",
8 | "methods": [
9 | "get",
10 | "post"
11 | ]
12 | },
13 | {
14 | "type": "http",
15 | "direction": "out",
16 | "name": "res"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/part3/products/WeatherForecast.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace products
4 | {
5 | public class WeatherForecast
6 | {
7 | public DateTime Date { get; set; }
8 |
9 | public int TemperatureC { get; set; }
10 |
11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
12 |
13 | public string Summary { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/part3/reviews/WeatherForecast.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace reviews
4 | {
5 | public class WeatherForecast
6 | {
7 | public DateTime Date { get; set; }
8 |
9 | public int TemperatureC { get; set; }
10 |
11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
12 |
13 | public string Summary { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/part1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "part1",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node app.js",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "apollo-server": "^2.7.0-alpha.3",
15 | "graphql": "^14.4.1"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/part2/dotnet/part2/app/Mutation.cs:
--------------------------------------------------------------------------------
1 | using GraphQL.Types;
2 | using GraphQL;
3 | using System.Linq;
4 | using System.Collections.Generic;
5 |
6 | namespace app
7 | {
8 | public class Mutation
9 | {
10 | [GraphQLMetadata("createProduct")]
11 | public Product CreateProduct(Product product)
12 | {
13 | product.Id = Data.Products.Count() + 1;
14 | Data.Products.Add(product);
15 | return product;
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/part4/solution/API/Mutation.cs:
--------------------------------------------------------------------------------
1 | using GraphQL.Types;
2 | using GraphQL;
3 | using System.Linq;
4 | using System.Collections.Generic;
5 |
6 | namespace Microsoft
7 | {
8 | public class Mutation
9 | {
10 | [GraphQLMetadata("createProduct")]
11 | public Product CreateProduct(Product product)
12 | {
13 | product.Id = Data.Products.Count() + 1;
14 | Data.Products.Add(product);
15 | return product;
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/part5/solution/API/Mutation.cs:
--------------------------------------------------------------------------------
1 | using GraphQL.Types;
2 | using GraphQL;
3 | using System.Linq;
4 | using System.Collections.Generic;
5 |
6 | namespace Microsoft
7 | {
8 | public class Mutation
9 | {
10 | [GraphQLMetadata("createProduct")]
11 | public Product CreateProduct(Product product)
12 | {
13 | product.Id = Data.Products.Count() + 1;
14 | Data.Products.Add(product);
15 | return product;
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/part1/app.js:
--------------------------------------------------------------------------------
1 | const { gql } = require("apollo-server");
2 |
3 | const typeDefs = gql`
4 | type Query {
5 | hello: String
6 | }
7 | `
8 |
9 | const resolvers = {
10 | Query: {
11 | hello: () => "world"
12 | }
13 | };
14 |
15 | const { ApolloServer } = require("apollo-server");
16 | const server = new ApolloServer({ typeDefs, resolvers });
17 |
18 | server.listen().then(({ url }) => {
19 | console.log(`🚀 Server ready at ${url}`);
20 | });
--------------------------------------------------------------------------------
/part2/dotnet/part2/app/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using GraphQL;
3 | using GraphQL.Types;
4 |
5 | namespace app
6 | {
7 | class Program
8 | {
9 | static void Main(string[] args)
10 | {
11 | var schema = SchemaFactory.Create();
12 | var json = schema.Execute(_ =>
13 | {
14 | _.Query = "{ reviews { product { name } } }";
15 | });
16 |
17 | Console.WriteLine(json);
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/docs/.vuepress/styles/index.styl:
--------------------------------------------------------------------------------
1 | .theme-container
2 | .navbar, .navbar .links
3 | background-color #B24444
4 |
5 | .navbar .home-link .site-name
6 | color white
7 |
8 | .nav-links
9 | .nav-link, .repo-link, .dropdown-title, .nav-link.router-link-active
10 | color white
11 | .nav-link:hover, .dropdown-title:hover, .repo-link:hover
12 | color white
13 |
14 | .dropdown-item .nav-link
15 | color $textColor
16 |
17 | .sidebar-button
18 | color white
19 |
20 | .sidebar .nav-links
21 | .nav-link, .repo-link, .dropdown-title
22 | color $textColor
23 |
24 |
--------------------------------------------------------------------------------
/part4/GraphQL/API/schema.js:
--------------------------------------------------------------------------------
1 | const {
2 | gql
3 | } = require("apollo-server");
4 |
5 | const typeDefs = gql `
6 | type Product {
7 | id: ID,
8 | name: String
9 | }
10 |
11 | type Review {
12 | grade: Int,
13 | title: String,
14 | description: String,
15 | product: Product
16 | }
17 |
18 | input ProductInput {
19 | name: String
20 | }
21 |
22 | type Mutation {
23 | createProduct(product: ProductInput): Product
24 | }
25 |
26 | type Query {
27 | hello: String,
28 | products: [Product],
29 | product(id: ID!): Product,
30 | reviews: [Review]
31 | }
32 | `
33 |
34 | module.exports = typeDefs;
--------------------------------------------------------------------------------
/part2/dotnet/part1/app/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using GraphQL.Types;
3 | using GraphQL;
4 |
5 | namespace app
6 | {
7 | class Program
8 | {
9 | static void Main(string[] args)
10 | {
11 | var schema = Schema.For(@"
12 | type Query {
13 | hello: String
14 | }
15 | ", _ =>
16 | {
17 | _.Types.Include();
18 | });
19 |
20 | var json = schema.Execute(_ =>
21 | {
22 | _.Query = "{ hello }";
23 | });
24 |
25 | Console.WriteLine(json);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | heroImage: /Bit_Learning.png
4 | heroText: GraphQL Workshop
5 | tagline: Learn about GraphQL + .NET Core + Azure Functions with Cloud Advocates!
6 | actionText: Let's Get Started!
7 | actionLink: /workshop/
8 | features:
9 | - title: Explore the GraphQL API
10 | details: Learn about GraphQL's schema, resolvers, data types, query language, mutations and the GraphQL UI
11 | - title: Learn about Dockerized Microservices
12 | details: Make a simple Node.js Express API within a Docker container
13 | - title: Build a Serverless Function
14 | details: Use Azure functions within an Azure App
15 | footer: MIT Licensed | Copyright © 2020-present Microsoft | Made with 🍕 and 🍺 by Chris Noring, Jen Looper and Glaucia Lemos
16 | ---
--------------------------------------------------------------------------------
/part3/products/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Hosting;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.Hosting;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace products
11 | {
12 | public class Program
13 | {
14 | public static void Main(string[] args)
15 | {
16 | CreateHostBuilder(args).Build().Run();
17 | }
18 |
19 | public static IHostBuilder CreateHostBuilder(string[] args) =>
20 | Host.CreateDefaultBuilder(args)
21 | .ConfigureWebHostDefaults(webBuilder =>
22 | {
23 | webBuilder.UseStartup();
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/part3/reviews/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Hosting;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.Hosting;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace reviews
11 | {
12 | public class Program
13 | {
14 | public static void Main(string[] args)
15 | {
16 | CreateHostBuilder(args).Build().Run();
17 | }
18 |
19 | public static IHostBuilder CreateHostBuilder(string[] args) =>
20 | Host.CreateDefaultBuilder(args)
21 | .ConfigureWebHostDefaults(webBuilder =>
22 | {
23 | webBuilder.UseStartup();
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/docs/pt-br/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | heroImage: /Bit_Learning.png
4 | heroText: Workshop de GraphQL
5 | tagline: Aprenda sobre GraphQL + .NET Core + Azure Functions com os Cloud Advocates!
6 | actionText: Vamos Começar?!
7 | actionLink: /pt-br/workshop/
8 | features:
9 | - title: Explore a API do GraphQL
10 | details: Aprenda sobre o schema do GraphQL, resolvers, data types, query language, mutations e o GraphQL UI
11 | - title: Aprenda como Dockerizar seus Microsserviços
12 | details: Crie uma API simples com API do Express do Node.js dentro de um contêiner do Docker.
13 | - title: Crie uma função Serverless
14 | details: Aprenda a usar o Azure Functions dentro de uma aplicação Azure
15 | footer: MIT Licensed | Copyright © 2020-present Microsoft | Feito com 🍕 e 🍺 por Chris Noring, Jen Looper & Glaucia Lemos
16 | ---
--------------------------------------------------------------------------------
/part3/products/Controllers/DefaultController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace products.Controllers
9 | {
10 | public class Product
11 | {
12 | public int Id { get; set; }
13 | public string Name { get; set; }
14 | }
15 |
16 | public class ProductsStore
17 | {
18 | public static List Products = new List()
19 | {
20 | new Product()
21 | {
22 | Id = 1,
23 | Name = "Avengers - End Game"
24 | }
25 | };
26 | }
27 |
28 | [ApiController]
29 | public class DefaultController : ControllerBase
30 | {
31 | [Route("/")]
32 | public List GetProducts()
33 | {
34 | return ProductsStore.Products;
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "serverless-graphql-workshop",
3 | "version": "1.0.0",
4 | "description": "A workshop by Azure Cloud Developer Advocates to teach Serverless and GraphQL using Azure",
5 | "scripts": {
6 | "docs:build": "vuepress build docs",
7 | "docs:dev": "vuepress dev docs"
8 | },
9 | "keywords": [
10 | "vuepress",
11 | "nodejs",
12 | "javascript",
13 | "vue"
14 | ],
15 | "author": "Chris Noring <@chris_noring>, Glaucia Lemos <@glaucia_lemos86> ",
16 | "license": "MIT",
17 | "dependencies": {
18 | "vuepress": "^1.5.2"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/softchris/serverless-graphql-microservices.git"
23 | },
24 | "bugs": {
25 | "url": "https://github.com/softchris/serverless-graphql-microservices/issues"
26 | },
27 | "homepage": "https://github.com/softchris/serverless-graphql-microservices#readme"
28 | }
29 |
--------------------------------------------------------------------------------
/part4/solution/solution.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp2.1
4 | v2
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | PreserveNewest
15 |
16 |
17 | PreserveNewest
18 | Never
19 |
20 |
21 |
--------------------------------------------------------------------------------
/part5/solution/solution.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp2.1
4 | v2
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | PreserveNewest
15 |
16 |
17 | PreserveNewest
18 | Never
19 |
20 |
21 |
--------------------------------------------------------------------------------
/part3/reviews/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:54620",
8 | "sslPort": 0
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "weatherforecast",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "reviews": {
21 | "commandName": "Project",
22 | "launchBrowser": true,
23 | "launchUrl": "weatherforecast",
24 | "applicationUrl": "http://localhost:5000",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/part3/products/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:56149",
8 | "sslPort": 0
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "weatherforecast",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "products": {
21 | "commandName": "Project",
22 | "launchBrowser": true,
23 | "launchUrl": "weatherforecast",
24 | "applicationUrl": "http://localhost:5000",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/part4/GraphQL/index.js:
--------------------------------------------------------------------------------
1 | const server = require('./API/app');
2 |
3 | module.exports = async function (context, req) {
4 | if (req.query.query || (req.body && req.body.query)) {
5 | const query = (req.query.query || req.body.query);
6 | try {
7 | const result = await server.executeOperation({
8 | query
9 | });
10 |
11 | context.res = {
12 | // status: 200, /* Defaults to 200 */
13 | contentType: 'application/json',
14 | body: result
15 | };
16 | } catch(err) {
17 | context.res = {
18 | status: 500,
19 | body: "We messed up" + err
20 | };
21 | }
22 |
23 | } else {
24 | context.res = {
25 | status: 400,
26 | body: "Please pass a `query` on the query string or in the request body"
27 | };
28 | }
29 | };
--------------------------------------------------------------------------------
/part2/dotnet/part2/app/Data.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace app
4 | {
5 |
6 | public class Product
7 | {
8 | public int Id { get; set; }
9 | public string Name { get; set; }
10 | }
11 |
12 | public class Review
13 | {
14 | public int Grade { get; set; }
15 | public string Title { get; set; }
16 | public string Description { get; set; }
17 | public int Product { get; set; }
18 | }
19 | public class Data
20 | {
21 | public static List Products = new List()
22 | {
23 | new Product()
24 | {
25 | Id = 1,
26 | Name = "Avengers - End Game"
27 | }
28 | };
29 |
30 | public static List Reviews = new List()
31 | {
32 | new Review()
33 | {
34 | Grade = 5,
35 | Title = "Great movie",
36 | Description = "Great actor playing Thanos",
37 | Product = 1
38 | }
39 | };
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/part3/reviews/Controllers/ReviewsController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace reviews.Controllers
9 | {
10 | public class Review
11 | {
12 | public int Grade { get; set; }
13 | public string Title { get; set; }
14 | public string Description { get; set; }
15 | public int Product { get; set; }
16 | }
17 |
18 | public class ReviewsStore
19 | {
20 | public static List Reviews = new List()
21 | {
22 | new Review()
23 | {
24 | Grade = 5,
25 | Title = "Great movie",
26 | Description = "Great actor playing Thanos",
27 | Product = 1
28 | }
29 | };
30 | }
31 |
32 | [ApiController]
33 | public class DefaultController : ControllerBase
34 | {
35 | [Route("/")]
36 | public List GetReviews()
37 | {
38 | return ReviewsStore.Reviews;
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/part4/solution/API/Data.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Microsoft
4 | {
5 |
6 | public class Planet
7 | {
8 | public string Name { get; set; }
9 | }
10 | public class Product
11 | {
12 | public int Id { get; set; }
13 | public string Name { get; set; }
14 | }
15 |
16 | public class Review
17 | {
18 | public int Grade { get; set; }
19 | public string Title { get; set; }
20 | public string Description { get; set; }
21 | public int Product { get; set; }
22 | }
23 | public class Data
24 | {
25 | public static List Products = new List()
26 | {
27 | new Product()
28 | {
29 | Id = 1,
30 | Name = "Avengers - End Game"
31 | }
32 | };
33 |
34 | public static List Reviews = new List()
35 | {
36 | new Review()
37 | {
38 | Grade = 5,
39 | Title = "Great movie",
40 | Description = "Great actor playing Thanos",
41 | Product = 1
42 | }
43 | };
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/part5/solution/API/Data.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Microsoft
4 | {
5 |
6 | public class Planet
7 | {
8 | public string Name { get; set; }
9 | }
10 | public class Product
11 | {
12 | public int Id { get; set; }
13 | public string Name { get; set; }
14 | }
15 |
16 | public class Review
17 | {
18 | public int Grade { get; set; }
19 | public string Title { get; set; }
20 | public string Description { get; set; }
21 | public int Product { get; set; }
22 | }
23 | public class Data
24 | {
25 | public static List Products = new List()
26 | {
27 | new Product()
28 | {
29 | Id = 1,
30 | Name = "Avengers - End Game"
31 | }
32 | };
33 |
34 | public static List Reviews = new List()
35 | {
36 | new Review()
37 | {
38 | Grade = 5,
39 | Title = "Great movie",
40 | Description = "Great actor playing Thanos",
41 | Product = 1
42 | }
43 | };
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/part4/solution/GraphQL.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Microsoft.Azure.WebJobs;
6 | using Microsoft.Azure.WebJobs.Extensions.Http;
7 | using Microsoft.AspNetCore.Http;
8 | using Microsoft.Extensions.Logging;
9 | using GraphQL;
10 |
11 | namespace Microsoft
12 | {
13 | public static class GraphQL
14 | {
15 | [FunctionName("GraphQL")]
16 | public static async Task Run(
17 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
18 | ILogger log)
19 | {
20 | log.LogInformation("C# HTTP trigger function processed a request.");
21 |
22 | string query = req.Query["query"];
23 | var schema = SchemaFactory.Create();
24 |
25 | var json = schema.Execute(_ =>
26 | {
27 | _.Query = query;
28 | });
29 | return new OkObjectResult(json);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/part5/solution/GraphQL.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Microsoft.Azure.WebJobs;
6 | using Microsoft.Azure.WebJobs.Extensions.Http;
7 | using Microsoft.AspNetCore.Http;
8 | using Microsoft.Extensions.Logging;
9 | using GraphQL;
10 |
11 | namespace Microsoft
12 | {
13 | public static class GraphQL
14 | {
15 | [FunctionName("GraphQL")]
16 | public static async Task Run(
17 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
18 | ILogger log)
19 | {
20 | log.LogInformation("C# HTTP trigger function processed a request.");
21 |
22 | string query = req.Query["query"];
23 | var schema = SchemaFactory.Create();
24 |
25 | var json = schema.Execute(_ =>
26 | {
27 | _.Query = query;
28 | });
29 | return new OkObjectResult(json);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/part2/dotnet/part2/app/Schema.cs:
--------------------------------------------------------------------------------
1 | using GraphQL.Types;
2 | using GraphQL;
3 |
4 | namespace app
5 | {
6 | public class SchemaFactory
7 | {
8 | public static ISchema Create()
9 | {
10 | var schema = Schema.For(@"
11 | type Product {
12 | id: ID,
13 | name: String
14 | }
15 |
16 | type Review {
17 | grade: Int,
18 | title: String,
19 | description: String,
20 | product: Product
21 | }
22 |
23 | input ProductInput {
24 | name: String
25 | }
26 |
27 | type Mutation {
28 | createProduct(product: ProductInput): Product
29 | }
30 |
31 | type Query {
32 | hello: String,
33 | products: [Product],
34 | product(id: ID!): Product,
35 | reviews: [Review]
36 | }
37 | ", _ =>
38 | {
39 | _.Types.Include();
40 | _.Types.Include();
41 | _.Types.Include();
42 | });
43 | return schema;
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/part4/GraphQL/API/resolvers.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch');
2 |
3 | async function getReviews() {
4 | const res = await fetch('http://localhost:8001');
5 | const json = await res.json();
6 | return json;
7 | }
8 |
9 | async function getProducts() {
10 | const res = await fetch('http://localhost:8000');
11 | const json = await res.json();
12 | return json;
13 | }
14 |
15 | async function getProduct(id) {
16 | const products = await getProducts();
17 | return Promise.resolve(products.find(p => p.id == id));
18 | }
19 |
20 | function createProduct(product) {
21 | const newProduct = {
22 | ...product,
23 | id: products.length + 1
24 | };
25 | products = [...products, newProduct];
26 |
27 | return Promise.resolve(newProduct);
28 | }
29 |
30 | module.exports = {
31 | Query: {
32 | hello: () => "world",
33 | products: async () => getProducts(),
34 | product: async (_, {
35 | id
36 | }) => getProduct(id),
37 | reviews: async () => getReviews()
38 | },
39 | Review: {
40 | product: async (review) => getProduct(review.product)
41 | },
42 | Mutation: {
43 | createProduct: async (_, {
44 | product
45 | }) => createProduct(product)
46 | }
47 | };
--------------------------------------------------------------------------------
/part4/solution/API/Schema.cs:
--------------------------------------------------------------------------------
1 | using GraphQL.Types;
2 | using GraphQL;
3 |
4 | namespace Microsoft
5 | {
6 | public class SchemaFactory
7 | {
8 | public static ISchema Create()
9 | {
10 | var schema = Schema.For(@"
11 | type Product {
12 | id: ID,
13 | name: String
14 | }
15 |
16 | type Planet {
17 | name: String
18 | }
19 |
20 | type Review {
21 | grade: Int,
22 | title: String,
23 | description: String,
24 | product: Product
25 | }
26 |
27 | input ProductInput {
28 | name: String
29 | }
30 |
31 | type Mutation {
32 | createProduct(product: ProductInput): Product
33 | }
34 |
35 | type Query {
36 | hello: String,
37 | products: [Product],
38 | product(id: ID!): Product,
39 | reviews: [Review],
40 | planets: [Planet]
41 | }
42 | ", _ =>
43 | {
44 | _.Types.Include();
45 | _.Types.Include();
46 | _.Types.Include();
47 | });
48 | return schema;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/part5/solution/API/Schema.cs:
--------------------------------------------------------------------------------
1 | using GraphQL.Types;
2 | using GraphQL;
3 |
4 | namespace Microsoft
5 | {
6 | public class SchemaFactory
7 | {
8 | public static ISchema Create()
9 | {
10 | var schema = Schema.For(@"
11 | type Product {
12 | id: ID,
13 | name: String
14 | }
15 |
16 | type Planet {
17 | name: String
18 | }
19 |
20 | type Review {
21 | grade: Int,
22 | title: String,
23 | description: String,
24 | product: Product
25 | }
26 |
27 | input ProductInput {
28 | name: String
29 | }
30 |
31 | type Mutation {
32 | createProduct(product: ProductInput): Product
33 | }
34 |
35 | type Query {
36 | hello: String,
37 | products: [Product],
38 | product(id: ID!): Product,
39 | reviews: [Review],
40 | planets: [Planet]
41 | }
42 | ", _ =>
43 | {
44 | _.Types.Include();
45 | _.Types.Include();
46 | _.Types.Include();
47 | });
48 | return schema;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/part2/dotnet/part2/app/Query.cs:
--------------------------------------------------------------------------------
1 | using GraphQL.Types;
2 | using GraphQL;
3 | using System.Linq;
4 | using System.Collections.Generic;
5 |
6 | namespace app
7 | {
8 | public class Query
9 | {
10 | [GraphQLMetadata("hello")]
11 | public string GetHello()
12 | {
13 | return "World";
14 | }
15 |
16 | [GraphQLMetadata("products")]
17 | public List GetProducts()
18 | {
19 | return Data.Products;
20 | }
21 |
22 | [GraphQLMetadata("product")]
23 | public Product GetProductById(int id)
24 | {
25 | return Data.Products.SingleOrDefault( p => p.Id == id );
26 | }
27 |
28 | [GraphQLMetadata("reviews")]
29 | public List GetReviews()
30 | {
31 | return Data.Reviews;
32 | }
33 | }
34 |
35 | [GraphQLMetadata("Review", IsTypeOf = typeof(Review))]
36 | public class ReviewResolver
37 | {
38 | public string Title(Review review) => review.Title;
39 | public string Description(Review review) => review.Description;
40 | public int Grade(Review review) => review.Grade;
41 | public Product Product(ResolveFieldContext context, Review review)
42 | {
43 | return Data.Products.SingleOrDefault(p => p.Id == review.Product);
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/part3/reviews/Controllers/WeatherForecastController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace reviews.Controllers
9 | {
10 | [ApiController]
11 | [Route("[controller]")]
12 | public class WeatherForecastController : ControllerBase
13 | {
14 | private static readonly string[] Summaries = new[]
15 | {
16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
17 | };
18 |
19 | private readonly ILogger _logger;
20 |
21 | public WeatherForecastController(ILogger logger)
22 | {
23 | _logger = logger;
24 | }
25 |
26 | [HttpGet]
27 | public IEnumerable Get()
28 | {
29 | var rng = new Random();
30 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast
31 | {
32 | Date = DateTime.Now.AddDays(index),
33 | TemperatureC = rng.Next(-20, 55),
34 | Summary = Summaries[rng.Next(Summaries.Length)]
35 | })
36 | .ToArray();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/part3/products/Controllers/WeatherForecastController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace products.Controllers
9 | {
10 | [ApiController]
11 | [Route("[controller]")]
12 | public class WeatherForecastController : ControllerBase
13 | {
14 | private static readonly string[] Summaries = new[]
15 | {
16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
17 | };
18 |
19 | private readonly ILogger _logger;
20 |
21 | public WeatherForecastController(ILogger logger)
22 | {
23 | _logger = logger;
24 | }
25 |
26 | [HttpGet]
27 | public IEnumerable Get()
28 | {
29 | var rng = new Random();
30 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast
31 | {
32 | Date = DateTime.Now.AddDays(index),
33 | TemperatureC = rng.Next(-20, 55),
34 | Summary = Summaries[rng.Next(Summaries.Length)]
35 | })
36 | .ToArray();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Workshop - .NET Core & GraphQL
2 |
3 | [](https://postimg.cc/NLG2YL9D)
4 |
5 | ## 🚀 Resources & Tools Used
6 |
7 | * **[Visual Studio Code](https://code.visualstudio.com/?WT.mc_id=academic-0000-chnoring)**
8 | * **[Node.js](https://nodejs.org/en/)**
9 | * **[VuePress](https://vuepress.vuejs.org/)**
10 |
11 | ## ⭐️ Running the VuePress Project locally
12 |
13 | 1. To run this workshop locally on your machine, you'll need to install globally the VuePress with the command:
14 |
15 | ```bash
16 | > npm install -g vuepress
17 | ```
18 |
19 | 2. Now we need to install the dependencies. So, type the command below inside in the main root project: `package.json`
20 |
21 | ```bash
22 | > npm i
23 | ```
24 |
25 | 3. Now type the command below to run locally the project on your machine:
26 |
27 | ```bash
28 | > npm run docs:dev
29 | ```
30 |
31 | 4. Now open your browser in: `localhost:8080` and will open the main workshop page.
32 |
33 | > If you want to see the online version, **[CLICK HERE](https://aka.ms/graphql-workshop)**
34 |
35 | ## ❗ I would like to help and contribute to translate into other languages. How should I proceed?
36 |
37 | First of all, thank you very much. We really appreciate your contribution. You'll be helping more people learn a little bit more about: .NET Core + GraphQL + Azure Functions. Get in touch **[HERE](gllemos@microsoft.com)**. So we can explain how to translate this workshop to other language.
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/part3/products/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Hosting;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.Extensions.Configuration;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Microsoft.Extensions.Hosting;
11 | using Microsoft.Extensions.Logging;
12 |
13 | namespace products
14 | {
15 | public class Startup
16 | {
17 | public Startup(IConfiguration configuration)
18 | {
19 | Configuration = configuration;
20 | }
21 |
22 | public IConfiguration Configuration { get; }
23 |
24 | // This method gets called by the runtime. Use this method to add services to the container.
25 | public void ConfigureServices(IServiceCollection services)
26 | {
27 | services.AddControllers();
28 | }
29 |
30 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
31 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
32 | {
33 | if (env.IsDevelopment())
34 | {
35 | app.UseDeveloperExceptionPage();
36 | }
37 |
38 | app.UseRouting();
39 |
40 | app.UseAuthorization();
41 |
42 | app.UseEndpoints(endpoints =>
43 | {
44 | endpoints.MapControllers();
45 | });
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/part3/reviews/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Hosting;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.Extensions.Configuration;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Microsoft.Extensions.Hosting;
11 | using Microsoft.Extensions.Logging;
12 |
13 | namespace reviews
14 | {
15 | public class Startup
16 | {
17 | public Startup(IConfiguration configuration)
18 | {
19 | Configuration = configuration;
20 | }
21 |
22 | public IConfiguration Configuration { get; }
23 |
24 | // This method gets called by the runtime. Use this method to add services to the container.
25 | public void ConfigureServices(IServiceCollection services)
26 | {
27 | services.AddControllers();
28 | }
29 |
30 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
31 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
32 | {
33 | if (env.IsDevelopment())
34 | {
35 | app.UseDeveloperExceptionPage();
36 | }
37 |
38 | app.UseRouting();
39 |
40 | app.UseAuthorization();
41 |
42 | app.UseEndpoints(endpoints =>
43 | {
44 | endpoints.MapControllers();
45 | });
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/part4/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 |
24 | # nyc test coverage
25 | .nyc_output
26 |
27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
28 | .grunt
29 |
30 | # Bower dependency directory (https://bower.io/)
31 | bower_components
32 |
33 | # node-waf configuration
34 | .lock-wscript
35 |
36 | # Compiled binary addons (https://nodejs.org/api/addons.html)
37 | build/Release
38 |
39 | # Dependency directories
40 | node_modules/
41 | jspm_packages/
42 |
43 | # TypeScript v1 declaration files
44 | typings/
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
55 | # Output of 'npm pack'
56 | *.tgz
57 |
58 | # Yarn Integrity file
59 | .yarn-integrity
60 |
61 | # dotenv environment variables file
62 | .env
63 | .env.test
64 |
65 | # parcel-bundler cache (https://parceljs.org/)
66 | .cache
67 |
68 | # next.js build output
69 | .next
70 |
71 | # nuxt.js build output
72 | .nuxt
73 |
74 | # vuepress build output
75 | .vuepress/dist
76 |
77 | # Serverless directories
78 | .serverless/
79 |
80 | # FuseBox cache
81 | .fusebox/
82 |
83 | # DynamoDB Local files
84 | .dynamodb/
85 |
86 | # TypeScript output
87 | dist
88 | out
89 |
90 | # Azure Functions artifacts
91 | bin
92 | obj
93 | appsettings.json
94 | local.settings.json
--------------------------------------------------------------------------------
/part5/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 |
24 | # nyc test coverage
25 | .nyc_output
26 |
27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
28 | .grunt
29 |
30 | # Bower dependency directory (https://bower.io/)
31 | bower_components
32 |
33 | # node-waf configuration
34 | .lock-wscript
35 |
36 | # Compiled binary addons (https://nodejs.org/api/addons.html)
37 | build/Release
38 |
39 | # Dependency directories
40 | node_modules/
41 | jspm_packages/
42 |
43 | # TypeScript v1 declaration files
44 | typings/
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
55 | # Output of 'npm pack'
56 | *.tgz
57 |
58 | # Yarn Integrity file
59 | .yarn-integrity
60 |
61 | # dotenv environment variables file
62 | .env
63 | .env.test
64 |
65 | # parcel-bundler cache (https://parceljs.org/)
66 | .cache
67 |
68 | # next.js build output
69 | .next
70 |
71 | # nuxt.js build output
72 | .nuxt
73 |
74 | # vuepress build output
75 | .vuepress/dist
76 |
77 | # Serverless directories
78 | .serverless/
79 |
80 | # FuseBox cache
81 | .fusebox/
82 |
83 | # DynamoDB Local files
84 | .dynamodb/
85 |
86 | # TypeScript output
87 | dist
88 | out
89 |
90 | # Azure Functions artifacts
91 | bin
92 | obj
93 | appsettings.json
94 | local.settings.json
--------------------------------------------------------------------------------
/part4/solution/API/Query.cs:
--------------------------------------------------------------------------------
1 | using GraphQL.Types;
2 | using GraphQL;
3 | using System.Linq;
4 | using System.Collections.Generic;
5 | using System.Net.Http;
6 | using System.Threading.Tasks;
7 | using System.Text.Json;
8 |
9 | namespace Microsoft
10 | {
11 |
12 | public class Response
13 | {
14 | public List results { get; set; }
15 | }
16 |
17 | public class HttpHelper
18 | {
19 | public static async ValueTask Get(string url)
20 | {
21 | var options = new JsonSerializerOptions
22 | {
23 | PropertyNameCaseInsensitive = true,
24 | };
25 |
26 | HttpClient client = new HttpClient();
27 | var streamTask = client.GetStreamAsync(url);
28 | var response = await System.Text.Json.JsonSerializer.DeserializeAsync(await streamTask, options);
29 | return response;
30 | }
31 | }
32 | public class Query
33 | {
34 | [GraphQLMetadata("hello")]
35 | public string GetHello()
36 | {
37 | return "World";
38 | }
39 |
40 | [GraphQLMetadata("products")]
41 | public async Task> GetProducts()
42 | {
43 | return await HttpHelper.Get>("http://localhost:8000");
44 | }
45 |
46 | [GraphQLMetadata("product")]
47 | public Product GetProductById(int id)
48 | {
49 | return Data.Products.SingleOrDefault(p => p.Id == id);
50 | }
51 |
52 | [GraphQLMetadata("reviews")]
53 | public async Task> GetReviews()
54 | {
55 | return await HttpHelper.Get>("http://localhost:8001");
56 | }
57 |
58 | [GraphQLMetadata("planets")]
59 | public async Task> GetPlanets()
60 | {
61 | var response = await HttpHelper.Get("https://swapi.co/api/planets");
62 | return response.results;
63 | }
64 | }
65 |
66 | [GraphQLMetadata("Review", IsTypeOf = typeof(Review))]
67 | public class ReviewResolver
68 | {
69 | public string Title(Review review) => review.Title;
70 | public string Description(Review review) => review.Description;
71 | public int Grade(Review review) => review.Grade;
72 | public async Task Product(ResolveFieldContext context, Review review)
73 | {
74 | var products = await HttpHelper.Get>("http://localhost:8000");
75 | return products.SingleOrDefault(p => p.Id == review.Product);
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/part5/solution/API/Query.cs:
--------------------------------------------------------------------------------
1 | using GraphQL.Types;
2 | using GraphQL;
3 | using System.Linq;
4 | using System.Collections.Generic;
5 | using System.Net.Http;
6 | using System.Threading.Tasks;
7 | using System.Text.Json;
8 |
9 | namespace Microsoft
10 | {
11 |
12 | public class Response
13 | {
14 | public List results { get; set; }
15 | }
16 |
17 | public class HttpHelper
18 | {
19 | public static async ValueTask Get(string url)
20 | {
21 | var options = new JsonSerializerOptions
22 | {
23 | PropertyNameCaseInsensitive = true,
24 | };
25 |
26 | HttpClient client = new HttpClient();
27 | var streamTask = client.GetStreamAsync(url);
28 | var response = await System.Text.Json.JsonSerializer.DeserializeAsync(await streamTask, options);
29 | return response;
30 | }
31 | }
32 | public class Query
33 | {
34 | [GraphQLMetadata("hello")]
35 | public string GetHello()
36 | {
37 | return "World";
38 | }
39 |
40 | [GraphQLMetadata("products")]
41 | public async Task> GetProducts()
42 | {
43 | return await HttpHelper.Get>("http://localhost:8000");
44 | }
45 |
46 | [GraphQLMetadata("product")]
47 | public Product GetProductById(int id)
48 | {
49 | return Data.Products.SingleOrDefault(p => p.Id == id);
50 | }
51 |
52 | [GraphQLMetadata("reviews")]
53 | public async Task> GetReviews()
54 | {
55 | return await HttpHelper.Get>("http://localhost:8001");
56 | }
57 |
58 | [GraphQLMetadata("planets")]
59 | public async Task> GetPlanets()
60 | {
61 | var response = await HttpHelper.Get("https://swapi.co/api/planets");
62 | return response.results;
63 | }
64 | }
65 |
66 | [GraphQLMetadata("Review", IsTypeOf = typeof(Review))]
67 | public class ReviewResolver
68 | {
69 | public string Title(Review review) => review.Title;
70 | public string Description(Review review) => review.Description;
71 | public int Grade(Review review) => review.Grade;
72 | public async Task Product(ResolveFieldContext context, Review review)
73 | {
74 | var products = await HttpHelper.Get>("http://localhost:8000");
75 | return products.SingleOrDefault(p => p.Id == review.Product);
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/docs/workshop/1.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🍕 Introduction - What is GraphQL and Serverless?
3 | ---
4 |
5 | # 🍕 Introduction
6 |
7 | From GraphQL [website](https://graphql.org/learn/)
8 |
9 | > GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn't tied to any specific database or storage engine and is instead backed by your existing code and data.
10 |
11 | Doesn't that sound awesome?
12 |
13 | > English please!
14 |
15 | With GraphQL you can define a so called GraphQL server on which you can pose queries. You can also consume GraphQL from a client with a simple POST request or use component built for the purpose from vendors like Apollo or Prisma
16 |
17 | > Why would I want all that?
18 |
19 | One word *content-negotiation*
20 |
21 | > Ok, please explain
22 |
23 | With GraphQL you are able to ask for exactly the data you want at almost any depth you want.
24 |
25 | **Let's take an example**
26 |
27 | Imagine having a normal REST API and you want all the `orders`, `order items` and what `products` were ordered using `order_id` then you would probably pose queries like this:
28 |
29 | To get informations about an order,
30 | ```
31 | order-api/orders/{order_id}
32 | ```
33 |
34 | To get the order items, you would have to call something like this:
35 |
36 | ```
37 | order-api/order-items/{order_id}
38 | ```
39 |
40 | To know what products someone bought, you would have to do this
41 |
42 | ```
43 | order-api/order-items/{order_id}/product
44 | ```
45 |
46 | The exact implementation may vary but the point is that it is more than one REST request for all the data you need to present at a page. You could obviously solve that and build specific REST endpoints that builds that particular view.
47 |
48 | OR you use GraphQL and all you have to type is this:
49 |
50 | ```
51 | orders {
52 | created,
53 | who {
54 | name
55 | },
56 | items {
57 | price,
58 | quantity
59 | product {
60 | name
61 | }
62 | }
63 | }
64 | ```
65 |
66 | Impressed yet?
67 |
68 |
69 |
70 |
71 | ## What we will build
72 |
73 | This is a quite ambitious workshop. We will teach you to not only build and consume a GraphQL workshop but we will also teach you how microservices might fit in the mix and in doing so learn some Docker basics.
74 |
75 | We are not happy there. We want you to be able to learn some basics on Cloud and hosting. So for that we will look at something really amazing namely Serverless. Serverless as a concept means you don't have to focus on VMs or Web servers but just CODE. Sounds great right?
76 |
77 | What we will do is to take our GraphQL API, put it in a Serverless function and with a few click place that and our microservices in the Cloud and *voila*, the whole world is ready to use your creation.
78 |
79 | So that's
80 |
81 | API:
82 | - A GraphQL API
83 | - A couple of Microservices supporting our API
84 | - A serverless function
85 |
86 | Hosting
87 | - the CLOUD
88 |
89 |
90 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * file: config.js
3 | * description: file responsible for all application configuration
4 | * date: 07/24/2020
5 | * author: Glaucia Lemos & Chris Noring
6 | */
7 |
8 | module.exports = {
9 | base: '/',
10 | head: [['link', { rel: 'icon', href: '/favicon.ico' }]],
11 | logo: 'bit.png',
12 | title: 'Workshop about GraphQL + .NET Core + Azure Functions',
13 | description: 'Workshop about GraphQL + .NET Core + Azure Functions',
14 | markdown: {
15 | lineNumbers: true,
16 | },
17 | locales: {
18 | '/': {
19 | lang: 'en-US',
20 | title: 'Workshop about GraphQL + .NET Core + Azure Functions',
21 | description: 'Workshop about GraphQL + .NET Core + Azure Functions',
22 | },
23 | '/pt-br/': {
24 | lang: 'pt-BR',
25 | title: 'Workshop GraphQL + .NET Core + Azure Functions',
26 | description: 'Workshop GraphQL + .NET Core + Azure Functions',
27 | },
28 | },
29 | themeConfig: {
30 | repo: 'https://github.com/softchris/graphql-workshop-dotnet',
31 | editLinks: true,
32 | editLinkText: 'Do you found an error? So, help us to improve this worskhop',
33 | locales: {
34 | '/': {
35 | selectText: 'Languages',
36 | label: 'English',
37 | nav: [
38 | { text: 'Main Page', link: '/' },
39 | { text: 'Videos', link: 'https://channel9.msdn.com/Search?term=graphql&lang-en=true&WT.mc_id=workshop_graphql-github-chnoring' },
40 | { text: 'Doubts?', link: 'https://github.com/softchris/graphql-workshop-dotnet/issues' },
41 | { text: 'Code of Conduct', link: '/workshop/CODE_OF_CONDUCT' },
42 | ],
43 | sidebar: [
44 | { title: '🍕 Introduction - What is GraphQL and Serverless?', children: ['/workshop/1'] },
45 | { title: '🔎 The GraphQL API', children: ['/workshop/2'] },
46 | { title: '📦 Microservices and Docker', children: ['/workshop/3'] },
47 | { title: '☁️ Serverless Functions', children: ['/workshop/4'] },
48 | { title: '🚀 Deploy your app', children: ['/workshop/5'] }
49 | ],
50 | },
51 | '/pt-br/': {
52 | selectText: 'Idiomas',
53 | label: 'Português',
54 | nav: [
55 | { text: 'Página Principal', link: '/pt-br/' },
56 | { text: 'Videos', link: 'https://channel9.msdn.com/Search?term=graphql&lang-en=true&WT.mc_id=workshop_graphql-github-gllemos' },
57 | { text: 'Dúvidas', link: 'https://github.com/softchris/graphql-workshop-dotnet/issues' },
58 | { text: 'Código de Conduta', link: '/pt-br/CODE_OF_CONDUCT' },
59 | ],
60 | sidebar: [
61 | { title: '🍕 Introdução - O que é GraphQL e computação sem servidor?', children: ['/pt-br/workshop/1'] },
62 | { title: '🔎 API do GraphQL', children: ['/pt-br/workshop/2'] },
63 | { title: '📦 Microsserviços e o Docker', children: ['/pt-br/workshop/3'] },
64 | { title: '☁️ Funções sem servidor', children: ['/pt-br/workshop/4'] },
65 | { title: '🚀 Implantando sua aplicação', children: ['/pt-br/workshop/5'] }
66 | ]
67 | },
68 | }, // fim colchete de locales
69 | }
70 | }
--------------------------------------------------------------------------------
/docs/workshop/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Welcome to GraphQL + .NET Core + Azure Functions Workshop
3 | ---
4 |
5 | # Welcome to GraphQL + .NET Core + Azure Functions Workshop
6 |
7 | [](https://postimg.cc/DmrmkJKj)
8 |
9 | GraphQL in .NET Core is a little bit different than the JavaScript implementation. Not a whole lot though.
10 |
11 | What is the same are the following concepts
12 |
13 | - **Schema**, we still have the idea of a schema, something that specifies what Entities we have, what we can Query, what we can Mutate and so on
14 | - **Resolver**, we still have the concept of a Resolver, a piece of code that should be invoked when a Query or Mutation is made.
15 | - **GQL**, we still have the GraphQL Query Language to define our schema and we also use it to construct queries and mutations to try to read/write data
16 |
17 | > So what is different?
18 |
19 | The difference lies in how we resolve a query. The first thing we do when resolving a Query in GraphQL is to ensure the resource we ask for exist. If it does we invoke the corresponding Function. Below is same pseudo code the explains the difference in approach
20 |
21 | ## JS Approach
22 |
23 | ```js
24 | // JavaScript
25 | const resolverObject = {
26 | hello: function resolver() { return "hello"; }
27 | }
28 | ```
29 |
30 | In GQL we would ask for the resource `hello`. The inner core would find the resolver object and invoke the `resolver()` function.
31 |
32 | ## C# Approach
33 |
34 | ```csharp
35 | // C#
36 |
37 | public class Query
38 | {
39 | [GraphQLMetadata("hello")]
40 | public string GetHello()
41 | {
42 | return "World";
43 | }
44 | }
45 | ```
46 |
47 | Above we have the C# approach in which we have a `Query` class and a method that is decorated with an attribute class `GraphQLMetadata` and a parameter `hello` which is the resource it resolves. Invoking our `GetHello()` method would give us the answer we seek.
48 |
49 | That is a very brief explanation of how JavaScript and C# differs.
50 |
51 | ## What do I need to know to continue this workshop?
52 |
53 | To continue this workshop, it is necessary to have knowledge in:
54 |
55 | * **[C#](https://docs.microsoft.com/dotnet/csharp/tutorials/intro-to-csharp/?WT.mc_id=graphql_workshop-github-chnoring)**
56 | * **[.NET Core](https://docs.microsoft.com/dotnet/core/introduction?WT.mc_id=graphql_workshop-github-chnoring)**
57 | * **[GraphQL](https://graphql.org/learn/)**
58 | * **[Azure Functions](https://docs.microsoft.com/azure/azure-functions/functions-overview?WT.mc_id=graphql_workshop-github-chnoring)**
59 | * **[App Service](https://docs.microsoft.com/azure/app-service/app-service-web-get-started-dotnet?WT.mc_id=graphql_workshop-github-chnoring)**
60 |
61 | However, if not, we will send here for you some important resources and links for some free courses on:
62 |
63 | * **[Cursos Gratuitos - Microsoft Learn: C# & .NET Core](https://docs.microsoft.com/learn/browse/?products=dotnet&WT.mc_id=graphql_workshop-github-chnoring)**
64 | * **[Cursos Gratuitos - Microsoft Learn: Serverless & Azure Functions](https://docs.microsoft.com/learn/paths/create-serverless-applications/?WT.mc_id=graphql_workshop-github-chnoring)**
65 | * **[Tutoriais Azure App Service - Documentação Oficial](https://docs.microsoft.com/azure/app-service/overview?WT.mc_id=graphql_workshop-github-chnoring)**
66 |
--------------------------------------------------------------------------------
/docs/pt-br/workshop/1.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🍕 Introdução - O que é GraphQL e Computação sem servidor?
3 | ---
4 | # 🍕 Introdução
5 |
6 | Do [site](https://graphql.org/learn/) da GraphQL
7 |
8 | > O GraphQL é uma linguagem de consulta para a sua API e um runtime do servidor para executar consultas usando um sistema de tipos definido para os dados. A GraphQL não está vinculada a nenhum banco de dados ou mecanismo de armazenamento específico. Em vez disso, ela se baseia no código e nos dados existentes.
9 |
10 | Não parece incrível?
11 |
12 | > Agora traduz, por favor!
13 |
14 | Com o GraphQL, você poderá definir o chamado servidor GraphQL, no qual será possível executar consultas. Você também poderá consumir a GraphQL de um cliente com uma simples solicitação POST ou usar um componente criado para esta finalidade de fornecedores como Apollo ou Prisma
15 |
16 | > Por que eu desejaria tudo isso?
17 |
18 | Muito simples: *negociação de conteúdo*
19 |
20 | > Poderia explicar melhor?
21 |
22 | Com a GraphQL você pode solicitar exatamente os dados desejados em praticamente qualquer nível de profundidade.
23 |
24 | **Vejamos um exemplo**
25 |
26 | Imagine que você tem uma API REST normal e deseja obter todos os `orders`, `order items` e também deseja saber quais `products` foram pedidos usando a `order_id`. Para isso, você provavelmente executaria consultas como as seguintes:
27 |
28 | Para obter informações sobre um pedido,
29 | ```
30 | order-api/orders/{order_id}
31 | ```
32 |
33 | Para obter os itens de um pedido, você teria que chamar algo assim:
34 |
35 | ```
36 | order-api/order-items/{order_id}
37 | ```
38 |
39 | Para saber quais produtos alguém comprou, você teria que fazer isso
40 |
41 | ```
42 | order-api/order-items/{order_id}/product
43 | ```
44 |
45 | A implementação exata pode variar, mas o ponto é que são necessárias mais de uma solicitação REST para obter todos os dados que você precisa apresentar em uma página. Obviamente, você pode resolver isso criando pontos de extremidade REST específicos que geram cada exibição em particular.
46 |
47 | OU você pode usar a GraphQL e tudo o que precisará digitar é o seguinte:
48 |
49 | ```json
50 | orders {
51 | created,
52 | who {
53 | name
54 | },
55 | items {
56 | price,
57 | quantity
58 | product {
59 | name
60 | }
61 | }
62 | }
63 | ```
64 |
65 | Já se impressionou?
66 |
67 | ## O que vamos criar?
68 |
69 | Este é um workshop bastante ambicioso. Ensinaremos a você não apenas como criar e consumir uma consulta GraphQL, mas também como os microsserviços podem se ajustar ao mix e, com isso, aprender algumas noções básicas do Docker.
70 |
71 | Mas ainda não estamos satisfeitos. Queremos que você seja capaz de aprender algumas noções básicas sobre nuvem e hospedagem. Então, para isso, vamos examinar algo realmente incrível: a computação sem servidor. O conceito de computação sem servidor significa que você não precisa se concentrar em VMs ou servidores Web, mas apenas no CÓDIGO. Parece excelente, não é mesmo?
72 |
73 | O que faremos será pegar a nossa API da GraphQL, colocá-la em uma função sem servidor e, com apenas alguns cliques, por ela e os nossos microsserviços na nuvem e *pronto*: o mundo inteiro poderá usar sua criação.
74 |
75 | Então será
76 |
77 | API:
78 | - Uma API do GraphQL
79 | - Um par de microsserviços que dão suporte à nossa API
80 | - Uma função sem servidor
81 |
82 | Hospedagem
83 | - na nuvem
84 |
85 |
--------------------------------------------------------------------------------
/docs/pt-br/workshop/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Sejam bem-vindos(as) ao Workshop de GraphQL + .NET Core + Azure Functions
3 | ---
4 |
5 | # Sejam bem-vindos(as) ao Workshop de GraphQL + .NET Core + Azure Functions
6 |
7 | [](https://postimg.cc/DmrmkJKj)
8 |
9 | O GraphQL no .NET Core é um pouco diferente da implementação do JavaScript. Mas não tão diferente.
10 |
11 | Os seguintes conceitos são semelhantes
12 |
13 | - **Esquema**: ainda temos a ideia de um esquema. Algo que especifica quais entidades temos, o que podemos consultar, o que podemos fazer mutar e assim por diante
14 | - **Resolvedor**: ainda temos o conceito de um resolvedor, que é uma parte do código que deve ser invocada quando uma consulta ou mutação é feita.
15 | - **GQL**: ainda temos a linguagem de consulta GraphQL para definir nosso esquema e também a usamos para construir consultas e mutações para tentar ler/gravar dados
16 |
17 | > Então, o que é diferente?
18 |
19 | A diferença está na forma como resolvemos uma consulta. A primeira ação que efetuamos ao resolver uma consulta em GraphQL é garantir que o recurso que chamamos exista. Se ele existir, invocaremos a função correspondente. Abaixo está o mesmo pseudocódigo que explica a diferença na abordagem
20 |
21 | ## Abordagem do JS
22 |
23 | ```js
24 | // JavaScript
25 | const resolverObject = {
26 | hello: function resolver() { return "hello"; }
27 | }
28 | ```
29 |
30 | No GQL, solicitaríamos o recurso `hello`. O núcleo interno encontraria o objeto do resolvedor e invocaria a função `resolver()`.
31 |
32 | ## Abordagem do C#
33 |
34 | ```csharp
35 | // C#
36 |
37 | public class Query
38 | {
39 | [GraphQLMetadata("hello")]
40 | public string GetHello()
41 | {
42 | return "World";
43 | }
44 | }
45 | ```
46 |
47 | Acima, temos a abordagem do C# em que temos uma classe `Query` e um método que é decorado com uma classe de atributo `GraphQLMetadata` e um parâmetro `hello` que é o recurso que ele resolve. Invocar nosso método `GetHello()` nos forneceria a resposta que buscamos.
48 |
49 | Essa é uma explicação muito breve de como o JavaScript e o C# diferem.
50 |
51 | ## O que eu preciso saber para continuar esse workshop?
52 |
53 | Para dar continuidade nesse workshop, se faz necessário ter conhecimento em:
54 |
55 | * **[C#](https://docs.microsoft.com/dotnet/csharp/tutorials/intro-to-csharp/?WT.mc_id=graphql_workshop-github-gllemos)**
56 | * **[.NET Core](https://docs.microsoft.com/dotnet/core/introduction?WT.mc_id=graphql_workshop-github-gllemos)**
57 | * **[GraphQL](https://graphql.org/learn/)**
58 | * **[Azure Functions](https://docs.microsoft.com/azure/azure-functions/functions-overview?WT.mc_id=graphql_workshop-github-gllemos)**
59 | * **[App Service](https://docs.microsoft.com/azure/app-service/app-service-web-get-started-dotnet?WT.mc_id=graphql_workshop-github-gllemos)**
60 |
61 | Porém, caso não tenha, deixaremos alguns recursos e links importantes de alguns cursos gratuitos sobre:
62 |
63 | * **[Cursos Gratuitos - Microsoft Learn: C# & .NET Core](https://docs.microsoft.com/learn/browse/?products=dotnet&WT.mc_id=graphql_workshop-github-gllemos)**
64 | * **[Cursos Gratuitos - Microsoft Learn: Serverless & Azure Functions](https://docs.microsoft.com/learn/paths/create-serverless-applications/?WT.mc_id=graphql_workshop-github-gllemos)**
65 | * **[Tutoriais Azure App Service - Documentação Oficial](https://docs.microsoft.com/azure/app-service/overview?WT.mc_id=graphql_workshop-github-gllemos)**
66 |
67 |
68 |
--------------------------------------------------------------------------------
/docs/pt-br/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Código de Conduta
2 |
3 | ## Nossa promessa
4 |
5 | Com o interesse de fomentar uma comunidade aberta e acolhedora,
6 | nós, como colaboradores e administradores deste projeto, comprometemo-nos
7 | a fazer a participação deste projeto uma experiência livre de assédio
8 | para todos, independentemente da aparência pessoal, deficiência,
9 | etnia, gênero, idade, identidade ou expressão de gênero, identidade
10 | ou orientação sexual, nacionalidade, nível de experiência, porte físico,
11 | raça ou religião.
12 |
13 | ## Nossos padrões
14 |
15 | Exemplos de comportamentos que contribuem a criar um ambiente positivo incluem:
16 |
17 | * Usar linguagem acolhedora e inclusiva
18 | * Respeitar pontos de vista e experiências diferentes
19 | * Aceitar crítica construtiva com graça
20 | * Focar no que é melhor para a comunidade
21 | * Mostrar empatia com outros membros da comunidade
22 |
23 | Exemplos de comportamentos inaceitáveis por parte dos participantes incluem:
24 |
25 | * Uso de linguagem ou imagens sexuais e atenção ou avanço sexual indesejada
26 | * Comentários insultuosos e/ou depreciativos e ataques pessoais ou políticos (*Trolling*)
27 | * Assédio público ou privado
28 | * Publicar informação pessoal de outros sem permissão explícita, como, por exemplo, um endereço eletrônico ou residencial
29 | * Qualquer outra forma de conduta que pode ser razoavelmente considerada inapropriada num ambiente profissional
30 |
31 | ## Nossas responsibilidades
32 |
33 | Os administradores do projeto são responsáveis por esclarecer os padrões de
34 | comportamento e deverão tomar ação corretiva apropriada e justa em resposta
35 | a qualquer instância de comportamento inaceitável.
36 |
37 | Os administradores do projeto têm o direito e a responsabilidade de
38 | remover, editar ou rejeitar comentários, commits, código, edições
39 | na wiki, erros ou outras formas de contribuição que não estejam de
40 | acordo com este Código de Conduta, bem como banir temporariamente ou
41 | permanentemente qualquer colaborador por qualquer outro comportamento
42 | que se considere impróprio, perigoso, ofensivo ou problemático.
43 |
44 | ## Escopo
45 |
46 | Este Código de Conduta aplica-se dentro dos espaços do projeto ou
47 | qualquer espaço público onde alguém represente o mesmo ou a sua
48 | comunidade. Exemplos de representação do projeto ou comunidade incluem
49 | usar um endereço de email oficial do projeto, postar por uma conta de
50 | mídia social oficial, ou agir como um representante designado num evento
51 | online ou offline. A representação de um projeto pode ser ainda definida e
52 | esclarecida pelos administradores do projeto.
53 |
54 | ## Aplicação
55 |
56 | Comportamento abusivo, de assédio ou de outros tipos pode ser
57 | comunicado contatando a equipe do projeto **[AQUI](gllemos@microsoft.com)**. Todas as queixas serão revistas e investigadas e
58 | resultarão numa resposta necessária e apropriada à situação.
59 | A equipe é obrigada a manter a confidencialidade em relação
60 | ao elemento que reportou o incidente. Demais detalhes de
61 | políticas de aplicação podem ser postadas separadamente.
62 |
63 | Administradores do projeto que não sigam ou não mantenham o Código
64 | de Conduta em boa fé podem enfrentar repercussões temporárias ou permanentes
65 | determinadas por outros membros da liderança do projeto.
66 |
67 | ## Atribuição
68 |
69 | Este Código de Conduta é adaptado do [Contributor Covenant](https://www.contributor-covenant.org),
70 | versão 1.4, disponível em https://www.contributor-covenant.org/pt-br/version/1/4/code-of-conduct.html
71 |
--------------------------------------------------------------------------------
/docs/workshop/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | **[E-mail me HERE](gllemos@microsoft.com)**.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/part4/solution/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # Azure Functions localsettings file
5 | local.settings.json
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # MSTest test Results
34 | [Tt]est[Rr]esult*/
35 | [Bb]uild[Ll]og.*
36 |
37 | # NUNIT
38 | *.VisualState.xml
39 | TestResult.xml
40 |
41 | # Build Results of an ATL Project
42 | [Dd]ebugPS/
43 | [Rr]eleasePS/
44 | dlldata.c
45 |
46 | # DNX
47 | project.lock.json
48 | project.fragment.lock.json
49 | artifacts/
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # NCrunch
117 | _NCrunch_*
118 | .*crunch*.local.xml
119 | nCrunchTemp_*
120 |
121 | # MightyMoose
122 | *.mm.*
123 | AutoTest.Net/
124 |
125 | # Web workbench (sass)
126 | .sass-cache/
127 |
128 | # Installshield output folder
129 | [Ee]xpress/
130 |
131 | # DocProject is a documentation generator add-in
132 | DocProject/buildhelp/
133 | DocProject/Help/*.HxT
134 | DocProject/Help/*.HxC
135 | DocProject/Help/*.hhc
136 | DocProject/Help/*.hhk
137 | DocProject/Help/*.hhp
138 | DocProject/Help/Html2
139 | DocProject/Help/html
140 |
141 | # Click-Once directory
142 | publish/
143 |
144 | # Publish Web Output
145 | *.[Pp]ublish.xml
146 | *.azurePubxml
147 | # TODO: Comment the next line if you want to checkin your web deploy settings
148 | # but database connection strings (with potential passwords) will be unencrypted
149 | #*.pubxml
150 | *.publishproj
151 |
152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
153 | # checkin your Azure Web App publish settings, but sensitive information contained
154 | # in these scripts will be unencrypted
155 | PublishScripts/
156 |
157 | # NuGet Packages
158 | *.nupkg
159 | # The packages folder can be ignored because of Package Restore
160 | **/packages/*
161 | # except build/, which is used as an MSBuild target.
162 | !**/packages/build/
163 | # Uncomment if necessary however generally it will be regenerated when needed
164 | #!**/packages/repositories.config
165 | # NuGet v3's project.json files produces more ignoreable files
166 | *.nuget.props
167 | *.nuget.targets
168 |
169 | # Microsoft Azure Build Output
170 | csx/
171 | *.build.csdef
172 |
173 | # Microsoft Azure Emulator
174 | ecf/
175 | rcf/
176 |
177 | # Windows Store app package directories and files
178 | AppPackages/
179 | BundleArtifacts/
180 | Package.StoreAssociation.xml
181 | _pkginfo.txt
182 |
183 | # Visual Studio cache files
184 | # files ending in .cache can be ignored
185 | *.[Cc]ache
186 | # but keep track of directories ending in .cache
187 | !*.[Cc]ache/
188 |
189 | # Others
190 | ClientBin/
191 | ~$*
192 | *~
193 | *.dbmdl
194 | *.dbproj.schemaview
195 | *.jfm
196 | *.pfx
197 | *.publishsettings
198 | node_modules/
199 | orleans.codegen.cs
200 |
201 | # Since there are multiple workflows, uncomment next line to ignore bower_components
202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
203 | #bower_components/
204 |
205 | # RIA/Silverlight projects
206 | Generated_Code/
207 |
208 | # Backup & report files from converting an old project file
209 | # to a newer Visual Studio version. Backup files are not needed,
210 | # because we have git ;-)
211 | _UpgradeReport_Files/
212 | Backup*/
213 | UpgradeLog*.XML
214 | UpgradeLog*.htm
215 |
216 | # SQL Server files
217 | *.mdf
218 | *.ldf
219 |
220 | # Business Intelligence projects
221 | *.rdl.data
222 | *.bim.layout
223 | *.bim_*.settings
224 |
225 | # Microsoft Fakes
226 | FakesAssemblies/
227 |
228 | # GhostDoc plugin setting file
229 | *.GhostDoc.xml
230 |
231 | # Node.js Tools for Visual Studio
232 | .ntvs_analysis.dat
233 |
234 | # Visual Studio 6 build log
235 | *.plg
236 |
237 | # Visual Studio 6 workspace options file
238 | *.opt
239 |
240 | # Visual Studio LightSwitch build output
241 | **/*.HTMLClient/GeneratedArtifacts
242 | **/*.DesktopClient/GeneratedArtifacts
243 | **/*.DesktopClient/ModelManifest.xml
244 | **/*.Server/GeneratedArtifacts
245 | **/*.Server/ModelManifest.xml
246 | _Pvt_Extensions
247 |
248 | # Paket dependency manager
249 | .paket/paket.exe
250 | paket-files/
251 |
252 | # FAKE - F# Make
253 | .fake/
254 |
255 | # JetBrains Rider
256 | .idea/
257 | *.sln.iml
258 |
259 | # CodeRush
260 | .cr/
261 |
262 | # Python Tools for Visual Studio (PTVS)
263 | __pycache__/
264 | *.pyc
--------------------------------------------------------------------------------
/part5/solution/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # Azure Functions localsettings file
5 | local.settings.json
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # MSTest test Results
34 | [Tt]est[Rr]esult*/
35 | [Bb]uild[Ll]og.*
36 |
37 | # NUNIT
38 | *.VisualState.xml
39 | TestResult.xml
40 |
41 | # Build Results of an ATL Project
42 | [Dd]ebugPS/
43 | [Rr]eleasePS/
44 | dlldata.c
45 |
46 | # DNX
47 | project.lock.json
48 | project.fragment.lock.json
49 | artifacts/
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # NCrunch
117 | _NCrunch_*
118 | .*crunch*.local.xml
119 | nCrunchTemp_*
120 |
121 | # MightyMoose
122 | *.mm.*
123 | AutoTest.Net/
124 |
125 | # Web workbench (sass)
126 | .sass-cache/
127 |
128 | # Installshield output folder
129 | [Ee]xpress/
130 |
131 | # DocProject is a documentation generator add-in
132 | DocProject/buildhelp/
133 | DocProject/Help/*.HxT
134 | DocProject/Help/*.HxC
135 | DocProject/Help/*.hhc
136 | DocProject/Help/*.hhk
137 | DocProject/Help/*.hhp
138 | DocProject/Help/Html2
139 | DocProject/Help/html
140 |
141 | # Click-Once directory
142 | publish/
143 |
144 | # Publish Web Output
145 | *.[Pp]ublish.xml
146 | *.azurePubxml
147 | # TODO: Comment the next line if you want to checkin your web deploy settings
148 | # but database connection strings (with potential passwords) will be unencrypted
149 | #*.pubxml
150 | *.publishproj
151 |
152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
153 | # checkin your Azure Web App publish settings, but sensitive information contained
154 | # in these scripts will be unencrypted
155 | PublishScripts/
156 |
157 | # NuGet Packages
158 | *.nupkg
159 | # The packages folder can be ignored because of Package Restore
160 | **/packages/*
161 | # except build/, which is used as an MSBuild target.
162 | !**/packages/build/
163 | # Uncomment if necessary however generally it will be regenerated when needed
164 | #!**/packages/repositories.config
165 | # NuGet v3's project.json files produces more ignoreable files
166 | *.nuget.props
167 | *.nuget.targets
168 |
169 | # Microsoft Azure Build Output
170 | csx/
171 | *.build.csdef
172 |
173 | # Microsoft Azure Emulator
174 | ecf/
175 | rcf/
176 |
177 | # Windows Store app package directories and files
178 | AppPackages/
179 | BundleArtifacts/
180 | Package.StoreAssociation.xml
181 | _pkginfo.txt
182 |
183 | # Visual Studio cache files
184 | # files ending in .cache can be ignored
185 | *.[Cc]ache
186 | # but keep track of directories ending in .cache
187 | !*.[Cc]ache/
188 |
189 | # Others
190 | ClientBin/
191 | ~$*
192 | *~
193 | *.dbmdl
194 | *.dbproj.schemaview
195 | *.jfm
196 | *.pfx
197 | *.publishsettings
198 | node_modules/
199 | orleans.codegen.cs
200 |
201 | # Since there are multiple workflows, uncomment next line to ignore bower_components
202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
203 | #bower_components/
204 |
205 | # RIA/Silverlight projects
206 | Generated_Code/
207 |
208 | # Backup & report files from converting an old project file
209 | # to a newer Visual Studio version. Backup files are not needed,
210 | # because we have git ;-)
211 | _UpgradeReport_Files/
212 | Backup*/
213 | UpgradeLog*.XML
214 | UpgradeLog*.htm
215 |
216 | # SQL Server files
217 | *.mdf
218 | *.ldf
219 |
220 | # Business Intelligence projects
221 | *.rdl.data
222 | *.bim.layout
223 | *.bim_*.settings
224 |
225 | # Microsoft Fakes
226 | FakesAssemblies/
227 |
228 | # GhostDoc plugin setting file
229 | *.GhostDoc.xml
230 |
231 | # Node.js Tools for Visual Studio
232 | .ntvs_analysis.dat
233 |
234 | # Visual Studio 6 build log
235 | *.plg
236 |
237 | # Visual Studio 6 workspace options file
238 | *.opt
239 |
240 | # Visual Studio LightSwitch build output
241 | **/*.HTMLClient/GeneratedArtifacts
242 | **/*.DesktopClient/GeneratedArtifacts
243 | **/*.DesktopClient/ModelManifest.xml
244 | **/*.Server/GeneratedArtifacts
245 | **/*.Server/ModelManifest.xml
246 | _Pvt_Extensions
247 |
248 | # Paket dependency manager
249 | .paket/paket.exe
250 | paket-files/
251 |
252 | # FAKE - F# Make
253 | .fake/
254 |
255 | # JetBrains Rider
256 | .idea/
257 | *.sln.iml
258 |
259 | # CodeRush
260 | .cr/
261 |
262 | # Python Tools for Visual Studio (PTVS)
263 | __pycache__/
264 | *.pyc
--------------------------------------------------------------------------------
/docs/workshop/5.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🚀 Deploy your app
3 | ---
4 |
5 | # 🚀 Deploy your app
6 |
7 | ## What we will build
8 |
9 | There are two things we need to deploy:
10 |
11 | 1. **Our microservices**, to deploy those we need to upload them to a container registry. Once they are in the container registry then we can create service endpoints
12 | 2. **Our serverless API**, this is as simple to do as using VS Code and upload it by a mere click.
13 |
14 | ### Micro services prerequisites
15 |
16 | To do this part we need the Azure CLI installed:
17 |
18 | ```
19 | https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest&
20 | ```
21 |
22 | As we mentioned above there are two steps we need to take for each service:
23 |
24 | - **Upload** to container registry
25 | - **Create** a service endpoint
26 |
27 | **Create resource group**
28 | Let's create a Resource Group first:
29 |
30 | ```
31 | az group create --name [name of resource group] --location westeurope
32 | ```
33 |
34 | **Create container registry**
35 |
36 | After that we need to create a *container registry*
37 |
38 | ```
39 | az acr create --resource-group [name of resource group] --name [name of container registry, unique and only a-z or 0-9] --sku Basic --admin-enabled true
40 | ```
41 |
42 | ### Build, tag, push container + create endpoint
43 |
44 | We need to do these steps for product service and reviews service.
45 |
46 | **Build image**
47 |
48 | Let's first build our service:
49 |
50 | ```
51 | docker build -t products-service .
52 | ```
53 |
54 | **Find login server value**
55 |
56 | Next, we need to find out the `login server`. That's a two-step process
57 |
58 | First login to our container registry:
59 |
60 | ```
61 | az acr login --name [name of registry]
62 | ```
63 |
64 | Now query for the login server name with:
65 |
66 | ```
67 | az acr show --name [name of container registry] --query loginServer --output table
68 | ```
69 |
70 | **Tag image**
71 |
72 | Let's now use the value of *login server*
73 |
74 | ```
75 | docker tag products-service [loginServer]/products-service:v1
76 | ```
77 |
78 | **Push our image to the registry**
79 |
80 | ```
81 | docker push [loginServer]/products-service:v1
82 | ```
83 |
84 | Verify the push did its job with the following command:
85 |
86 | ```
87 | az acr repository list --name [name of registry] --output table
88 | ```
89 |
90 | **Create a service endpoint**
91 |
92 | There are two ways of doing this, either:
93 |
94 | 1. Azure CLI
95 | 2. Visually through the portal
96 |
97 | **Alt I - Azure CLI - create endpoint**
98 |
99 | Before we can create our endpoint we need the values for username and password, like so:
100 |
101 | ```
102 | az acr credential show --name --query "passwords[0].value"
103 | ```
104 |
105 | Now let's create a container in the Cloud from our pushed image:
106 |
107 | ```
108 | az container create --resource-group [resource group] --name aci-tutorial-app --image /[products-service or reviews-service]] --cpu 1 --memory 1 --registry-login-server [acrLoginServer] --registry-username [acrName] --registry-password [acrPassword] --dns-name-label [aciDnsLabel] --ports 80
109 | ```
110 |
111 | **ALT II - Visually create endpoint**
112 |
113 | To do this visually, we need to open up the portal and select to create a resource, like so:
114 |
115 | 
116 |
117 | Next select the correct template by typing `Web App for Containers`:
118 |
119 | 
120 |
121 | Thereafter fill in some mandatory fields:
122 |
123 | 
124 |
125 | Click the `Configure Container` section and select the correct container registry and the correct container (that you just created and uploaded).
126 |
127 | That's it, that should create the endpoint..
128 |
129 | ### Serverless API
130 |
131 | So how do we deploy the Serverless API?
132 |
133 | We need to revisit our Serverless app before we can deploy, why is that?
134 |
135 | Right now the external endpoints in it are pointing to local IP addresses.
136 |
137 | Looking at the file `Query.cs` we see this:
138 |
139 | ```csharp
140 | [GraphQLMetadata("reviews")]
141 | public async Task> GetReviews()
142 | {
143 | return await HttpHelper.Get>("http://localhost:8001");
144 | }
145 |
146 | [GraphQLMetadata("products")]
147 | public async Task> GetProducts()
148 | {
149 | return await HttpHelper.Get>("http://localhost:8000");
150 | }
151 | ```
152 |
153 | Both these should point to our new endpoints in Azure. To make that possible we change the above to:
154 |
155 | ```csharp
156 | [GraphQLMetadata("reviews")]
157 | public async Task> GetReviews()
158 | {
159 | return await HttpHelper.Get>(Environment.GetEnvironmentVariable("REVIEWS_URL", EnvironmentVariableTarget.Process));
160 | }
161 |
162 | [GraphQLMetadata("products")]
163 | public async Task> GetProducts()
164 | {
165 | return await HttpHelper.Get>(Environment.GetEnvironmentVariable("PRODUCTS_URL", EnvironmentVariableTarget.Process));
166 | }
167 | ```
168 |
169 | Now, when our function app is deployed to the Cloud it will read from it's AppSettings and populate `process.env`.
170 |
171 | > How do we get the above values to AppSettings?
172 |
173 | There are two ways
174 |
175 | 1. **Manually add an entry** in the AppSettings in the portal for the Azure Function App once we deployed it
176 | 2. **Store these values in the file `local.settings.json`** and as part of deploying our Azure App we select to copy values from this file to AppSettings
177 |
178 | We will show the latter
179 |
180 | **Store app keys in local.settings.json**
181 |
182 | Looking at the contents of `local.settings.json` it should look something like this:
183 |
184 | ```json
185 | {
186 | "IsEncrypted": false,
187 | "Values": {
188 | "AzureWebJobsStorage": "",
189 | "FUNCTIONS_WORKER_RUNTIME": "node"
190 | }
191 | }
192 | ```
193 |
194 | In the `Values` property add the needed keys:
195 |
196 | ```json
197 | {
198 | "IsEncrypted": false,
199 | "Values": {
200 | "AzureWebJobsStorage": "",
201 | "FUNCTIONS_WORKER_RUNTIME": "node",
202 | "PRODUCTS_URL": "",
203 | "REVIEWS_URL":""
204 | }
205 | }
206 | ```
207 |
208 | **Deploy the Azure Function App**
209 |
210 | Click the Azure logo on the left toolbar in VS Code.
211 |
212 | Sign in to Azure if you haven't already done so.
213 |
214 | 
215 |
216 | Click the deploy symbol
217 |
218 | 
219 |
220 | Select to `Create a new Function App`
221 |
222 | 
223 |
224 | It should start showing something like this:
225 |
226 | 
227 |
228 | When it's done it should say something like this:
229 |
230 | 
231 |
232 | **Transfer app keys**
233 |
234 | Now that you have an Azure Function App, right click it and select `Upload Local Settings`.
235 |
236 | 
237 |
238 |
239 | Everything should be in the Cloud now and working !
240 |
241 | ## Solution
242 |
243 | [SOLUTION workshop part 5](https://github.com/softchris/graphql-workshop-dotnet/tree/master/part5)
--------------------------------------------------------------------------------
/docs/pt-br/workshop/5.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🚀 Implantando sua aplicação
3 | ---
4 | # 🚀 Implantando sua aplicação
5 |
6 | ## O que vamos criar?
7 |
8 | Há duas coisas que precisamos implantar:
9 |
10 | 1. **Nossos microsserviços**: para implantá-los, precisamos carregá-los em um registro de contêiner. Quando estiverem no registro de contêiner, poderemos criar pontos de extremidade de serviço
11 | 2. **Nossa API sem servidor*:, isso é tão simples quanto usar o VS Code e carregá-lo com um simples clique.
12 |
13 | ### Pré-requisitos dos microsserviços
14 |
15 | Para fazer essa parte, precisamos da CLI do Azure instalada:
16 |
17 | ```bash
18 | https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest&
19 | ```
20 |
21 | Como mencionamos acima, há duas etapas que precisamos executar para cada serviço:
22 |
23 | - **Carregar** para o registro de contêiner
24 | - **Criar** um ponto de extremidade de serviço
25 |
26 | **Criando um grupo de recursos** Vamos criar primeiramente um Grupo de recursos:
27 |
28 | ```bash
29 | > az group create --name [name of resource group] --location westeurope
30 | ```
31 |
32 | **Criando um registro de contêiner**
33 |
34 | Depois disso, precisaremos criar um *[registro de contêiner](https://docs.microsoft.com/azure/container-registry/container-registry-tutorial-quick-task?WT.mc_id=graphql_workshop-github-gllemos)*
35 |
36 | ```bash
37 | > az acr create --resource-group [name of resource group] --name [name of container registry, unique and only a-z or 0-9] --sku Basic --admin-enabled true
38 | ```
39 |
40 | ### Compilando, marcando e enviando o contêiner por push + criar ponto de extremidade
41 |
42 | Precisamos seguir essas etapas para o serviço de produto e de revisões.
43 |
44 | **Criando imagem**
45 |
46 | Primeiro vamos criar nosso serviço:
47 |
48 | ```bash
49 | > docker build -t products-service .
50 | ```
51 |
52 | **Localizando o valor do servidor de logon**
53 |
54 | Em seguida, precisaremos descobrir o `login server`. Esse é um processo em duas etapas
55 |
56 | Primeiro, faça logon em nosso registro de contêiner:
57 |
58 | ```bash
59 | > az acr login --name [name of registry]
60 | ```
61 |
62 | Agora, consulte o nome do servidor de logon com:
63 |
64 | ```bash
65 | > az acr show --name [name of container registry] --query loginServer --output table
66 | ```
67 |
68 | **Marcando imagem**
69 |
70 | Agora vamos usar o valor do *servidor de logon*
71 |
72 | ```bash
73 | > docker tag products-service [loginServer]/products-service:v1
74 | ```
75 |
76 | **Enviando nossa imagem por push para o Registro**
77 |
78 | ```bash
79 | > docker push [loginServer]/products-service:v1
80 | ```
81 |
82 | Verifique se o push fez o trabalho dele com o seguinte comando:
83 |
84 | ```bash
85 | > az acr repository list --name [name of registry] --output table
86 | ```
87 |
88 | **Criando um ponto de extremidade do serviço**
89 |
90 | Há duas maneiras de fazer isso:
91 |
92 | 1. CLI do Azure
93 | 2. Visualmente, por meio do portal
94 |
95 | **Alt I – CLI do Azure – criar ponto de extremidade**
96 |
97 | Antes que possamos criar nosso ponto de extremidade, precisamos dos valores de nome de usuário e senha, desta forma:
98 |
99 | ```bash
100 | > az acr credential show --name --query "passwords[0].value"
101 | ```
102 |
103 | Agora, vamos criar um contêiner na nuvem com base em nossa imagem enviada por push:
104 |
105 | ```bash
106 | > az container create --resource-group [resource group] --name aci-tutorial-app --image /[products-service or reviews-service]] --cpu 1 --memory 1 --registry-login-server [acrLoginServer] --registry-username [acrName] --registry-password [acrPassword] --dns-name-label [aciDnsLabel] --ports 80
107 | ```
108 |
109 | **ALT II – Criando ponto de extremidade visualmente**
110 |
111 | Para fazer isso visualmente, precisamos abrir o portal e selecionar para criar um recurso, desta forma:
112 |
113 | 
114 |
115 | Em seguida, selecione o modelo correto digitando `Web App for Containers`:
116 |
117 | 
118 |
119 | Depois, preencha alguns campos obrigatórios:
120 |
121 | 
122 |
123 | Clique na seção `Configure Container` e selecione o registro de contêiner e o contêiner corretos (que você acabou de criar e carregar).
124 |
125 | Pronto. Isso deve criar o ponto de extremidade.
126 |
127 | ### API sem servidor
128 |
129 | Então, como implantamos a API sem servidor?
130 |
131 | Precisamos revisitar nosso aplicativo sem servidor antes de podermos implantar. Por quê?
132 |
133 | Neste momento, os pontos de extremidade externos estão apontando para endereços IP locais.
134 |
135 | Examinando o arquivo `Query.cs`, vemos isto:
136 |
137 | ```csharp
138 | [GraphQLMetadata("reviews")]
139 | public async Task> GetReviews()
140 | {
141 | return await HttpHelper.Get>("http://localhost:8001");
142 | }
143 |
144 | [GraphQLMetadata("products")]
145 | public async Task> GetProducts()
146 | {
147 | return await HttpHelper.Get>("http://localhost:8000");
148 | }
149 | ```
150 |
151 | Ambos devem apontar para nossos novos pontos de extremidade no Azure. Para tornar isso possível, alteramos o anterior para:
152 |
153 | ```csharp
154 | [GraphQLMetadata("reviews")]
155 | public async Task> GetReviews()
156 | {
157 | return await HttpHelper.Get>(Environment.GetEnvironmentVariable("REVIEWS_URL", EnvironmentVariableTarget.Process));
158 | }
159 |
160 | [GraphQLMetadata("products")]
161 | public async Task> GetProducts()
162 | {
163 | return await HttpHelper.Get>(Environment.GetEnvironmentVariable("PRODUCTS_URL", EnvironmentVariableTarget.Process));
164 | }
165 | ```
166 |
167 | Agora, quando nosso aplicativo de funções for implantado na nuvem, ele será lido no AppSettings dele e preencherá `process.env`.
168 |
169 | > Como obtemos os valores acima para AppSettings?
170 |
171 | Há duas maneiras
172 |
173 | 1. **Adicionar uma entrada manualmente** no AppSettings no portal para o aplicativo de funções do Azure depois que o implantarmos
174 | 2. **Armazenar esses valores no arquivo `local.settings.json`** e, como parte da implantação do nosso aplicativo do Azure, selecionamos para copiar valores desse arquivo para AppSettings
175 |
176 | Mostraremos esta última
177 |
178 | **Armazenar chaves de aplicativo em local.settings.json**
179 |
180 | Examinando o conteúdo de `local.settings.json`, ele deve ser semelhante a este:
181 |
182 | ```json
183 | {
184 | "IsEncrypted": false,
185 | "Values": {
186 | "AzureWebJobsStorage": "",
187 | "FUNCTIONS_WORKER_RUNTIME": "node"
188 | }
189 | }
190 | ```
191 |
192 | Na propriedade `Values`, adicione as chaves necessárias:
193 |
194 | ```json
195 | {
196 | "IsEncrypted": false,
197 | "Values": {
198 | "AzureWebJobsStorage": "",
199 | "FUNCTIONS_WORKER_RUNTIME": "node",
200 | "PRODUCTS_URL": "",
201 | "REVIEWS_URL":""
202 | }
203 | }
204 | ```
205 |
206 | **Implantando o Azure Functions na aplicação**
207 |
208 | Clique no logotipo do Azure na barra de ferramentas esquerda no VS Code.
209 |
210 | Entre no Azure se você ainda não tiver feito isso.
211 |
212 | 
213 |
214 | Clique no símbolo implantar
215 |
216 | 
217 |
218 | Selecione `Create a new Function App`
219 |
220 | 
221 |
222 | Isso deve começar a mostrar algo como isto:
223 |
224 | 
225 |
226 | Quando terminar, você verá algo como isto:
227 |
228 | 
229 |
230 | **Transferindo chaves da aplicação**
231 |
232 | Agora que você tem um aplicativo de funções do Azure, clique com o botão direito do mouse nele e selecione `Upload Local Settings`.
233 |
234 | 
235 |
236 |
237 | Tudo já deve estar na nuvem e funcionando!
238 |
239 | ## Solução
240 |
241 | [SOLUÇÃO parte 5 do Workshop](https://github.com/softchris/graphql-workshop-dotnet/tree/master/part5)
--------------------------------------------------------------------------------
/docs/workshop/3.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 📦 Microservices and Docker
3 | ---
4 |
5 | # 📦 Microservices and Docker
6 |
7 | ## Microservices
8 |
9 | Micro services are small dedicated services that concentrate on solving one problem in a domain. The idea is to break apart your monolith (a place where all your services live) into small independent services. Let's talk about the PROs and CONs of this approach
10 |
11 | **PROs**
12 |
13 | - **Can be developed and deployed independently**, deploying a Monolith app can take some time. A lot of developers working in the same codebase can make for complicated merges. With micro services all of that goes away, dedicated repos for each micro service. You can spin up or redeploy your service without spinning up a large machinery.
14 | - **Different teams can work on different services**, it becomes so much easier to scale up your IT operation with one team per micro service.
15 | - **Different services can be built in different programming languages**, your company no longer need to have *one* tech stack. With this degree of freedom this means that the developers you hire can use their favorite tools and programming language to build the service.
16 | - **Easy to scale** with an orchestrator like Kubernetes, because micro services are turning into containers it becomes really easy to scale up the number of Micro service instances that are needed to meet your user demands like a big sale or similar. Thanks to Kubernetes this is quite easy.
17 |
18 | **CONs**
19 |
20 | - you need to learn about containers cause that's how you usually serve up your micro services
21 | - orchestration becomes a problem you need to manage, you need to find a way to easily create containers, bring them up, bring the down
22 | - talking cross services is a thing you will need to manage
23 | - it takes a while to mentally learn to architect and *think* in micro services
24 |
25 | ## Docker
26 |
27 | Docker helps us create containers out of our micro services. Once our micro services are being served up as container we can push them to container registry in the Cloud. Thereafter we can have our Cloud Provider instantiate an app service from our container OR we can tell an orchestrator like Kubernetes to scale up our app in *n* instances so we can serve millions of customers.
28 |
29 | To be able to work efficiently with Docker in this workshop, we will learn the following concepts:
30 |
31 | - **Dockerfile**, a docker file is a recipe for what you are about to build. The file contains information such as what OS to base your image on, dependencies that needs to be installed and of course information on how to copy and run your app within the container.
32 | - **container**, a container is a runnable black box that only has the fraction the size a VM has. The reason for that is that the container talks to the host OS instead of having a full OS inside of the container.
33 | - **image**, an image is what you get when you build an artifact from a Dockerfile. An image isn't runnable and needs to b converted to a container first
34 | - **docker-compose**, docker-compose is a tool you use when you need to manage several containers at once. Without it, you would have to resort to adding creation, setup, teardown commands for each container, that means a lot of scripts and simply becomes hard to manage
35 |
36 | ## What we will build
37 |
38 | We will build two different micro services, giving us products and reviews respectively.
39 |
40 | For each service we will take the following steps:
41 |
42 | - **Create** a REST Service in Node.js + Express
43 | - **Define** a Dockerfile, we need one Dockerfile for each service
44 | - **Containerize**, we will create an image and container respectively, using docker-compose, so we have each container up and running and reachable from a browser
45 |
46 | ### Create a REST Service in .NET Core
47 |
48 | We will create two different services
49 | - `products service`, this will return a list of product
50 | - `reviews service`, this will contain info on a review and link to an id for a product
51 |
52 | **Products service**
53 |
54 | ```
55 | dotnet new webapi -o products --no-https
56 | ```
57 |
58 | Add the file `DefaultController.cs` to directory `Controllers` and give it the following content:
59 |
60 | ```csharp
61 | using System;
62 | using System.Collections.Generic;
63 | using System.Linq;
64 | using System.Threading.Tasks;
65 | using Microsoft.AspNetCore.Mvc;
66 | using Microsoft.Extensions.Logging;
67 |
68 | namespace products.Controllers
69 | {
70 | public class Product
71 | {
72 | public int Id { get; set; }
73 | public string Name { get; set; }
74 | }
75 |
76 | public class ProductsStore
77 | {
78 | public static List Products = new List()
79 | {
80 | new Product()
81 | {
82 | Id = 1,
83 | Name = "Avengers - End Game"
84 | }
85 | };
86 | }
87 |
88 | [ApiController]
89 | public class DefaultController : ControllerBase
90 | {
91 | [Route("/")]
92 | public List GetProducts()
93 | {
94 | return ProductsStore.Products;
95 | }
96 | }
97 | }
98 | ```
99 |
100 | Try it out by running `dotnet run` in the terminal. Go to a browser at `http://localhost:5000`. This should show a list of products.
101 |
102 | Bring down the server with `CTRL+C`.
103 |
104 | **Reviews service**
105 |
106 | ```
107 | dotnet new webapi -o reviews --no-https
108 | ```
109 |
110 | Add the file `DefaultController.cs` to directory `Controllers` and give it the following content:
111 |
112 | ```csharp
113 | using System;
114 | using System.Collections.Generic;
115 | using System.Linq;
116 | using System.Threading.Tasks;
117 | using Microsoft.AspNetCore.Mvc;
118 | using Microsoft.Extensions.Logging;
119 |
120 | namespace reviews.Controllers
121 | {
122 | public class Review
123 | {
124 | public int Grade { get; set; }
125 | public string Title { get; set; }
126 | public string Description { get; set; }
127 | public int Product { get; set; }
128 | }
129 |
130 | public class ReviewsStore
131 | {
132 | public static List Reviews = new List()
133 | {
134 | new Review()
135 | {
136 | Grade = 5,
137 | Title = "Great movie",
138 | Description = "Great actor playing Thanos",
139 | Product = 1
140 | }
141 | };
142 | }
143 |
144 | [ApiController]
145 | public class DefaultController : ControllerBase
146 | {
147 | [Route("/")]
148 | public List GetReviews()
149 | {
150 | return ReviewsStore.Reviews;
151 | }
152 | }
153 | }
154 | ```
155 |
156 | Try it out by running `dotnet run` in the terminal. Go to a browser at `http://localhost:5000`. This should show a list of products.
157 |
158 | Bring down the server with `CTRL+C`.
159 |
160 |
161 |
162 | ### Define a Dockerfile
163 |
164 | We need to do this once for each service.
165 |
166 | **Add Dockerfile to Products service**
167 |
168 | Go to our `products` directory and create a file called `Dockerfile`.
169 |
170 | Give it the following content:
171 |
172 | ```
173 | # Dockerfile
174 |
175 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
176 | WORKDIR /src
177 | COPY products.csproj .
178 | RUN dotnet restore
179 | COPY . .
180 | RUN dotnet publish -c release -o /app
181 |
182 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
183 | WORKDIR /app
184 | COPY --from=build /app .
185 | ENTRYPOINT ["dotnet", "products.dll"]
186 | ```
187 |
188 | **Add Dockerfile to Reviews service**
189 |
190 | Go to our `reviews` directory and create a file called `Dockerfile`. Give it the following content:
191 |
192 | ```
193 | # Dockerfile
194 |
195 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
196 | WORKDIR /src
197 | COPY reviews.csproj .
198 | RUN dotnet restore
199 | COPY . .
200 | RUN dotnet publish -c release -o /app
201 |
202 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
203 | WORKDIR /app
204 | COPY --from=build /app .
205 | ENTRYPOINT ["dotnet", "reviews.dll"]
206 | ```
207 |
208 | **Dockerize**
209 |
210 | We've created a Dockerfile for each service. Our project structure should now look something like this:
211 |
212 | ```
213 | products/
214 | -- .net core specific files
215 | Dockerfile
216 | reviews/
217 | -- .net core specific files
218 | Dockerfile
219 | ```
220 |
221 | Let's ensure we are at the root level and create a file called `docker-compose.yaml`. Give it the following content:
222 |
223 | ```yaml
224 | version: '3.3'
225 | services:
226 | product-service:
227 | build:
228 | context: ./products
229 | ports:
230 | - "8000:80"
231 | networks:
232 | - microservices
233 | review-service:
234 | build:
235 | context: ./reviews
236 | ports:
237 | - "8001:80"
238 | networks:
239 | - microservices
240 | networks:
241 | microservices:
242 | ```
243 |
244 | What the above file says is:
245 | For each service:
246 | 1. **run** the Dockerfile listed under `context`
247 | 2. **set up** a connection between host system port and container port `:`
248 | 3. **put** each container in network `microservices`
249 |
250 | Your project structure should now look like this:
251 |
252 | If this IS the first time we just need to run the command:
253 |
254 | ```
255 | docker-compose up -d
256 | ```
257 |
258 | This will build an image of each service, then create and run a container.
259 |
260 | If this is NOT the first time, you instead run the following command:
261 |
262 | ```
263 | docker-compose build
264 | docker-compose up -d
265 | ```
266 |
267 | NOTE, we run `build` command to ensure that any changes to the Dockerfile is being rebuilt into a new image.
268 |
269 | This should have started all services and you should be able to reach them on `http://localhost:8000` and `http://localhost:8001`.
270 |
271 | To take down the services type (no need for that yet):
272 |
273 | ```
274 | docker-compose down
275 | ```
276 |
277 | ## Solution
278 |
279 | [SOLUTION workshop part 3](https://github.com/softchris/graphql-workshop-dotnet/tree/master/part3)
280 |
--------------------------------------------------------------------------------
/docs/pt-br/workshop/3.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 📦 Microsserviços e Docker
3 | ---
4 | # 📦 Microsserviços e Docker
5 |
6 | ## Microsserviços
7 |
8 | Microsserviços são pequenos serviços dedicados que se concentram na resolução de um problema em um domínio. A ideia é separar o monolítico (local onde residem todos os serviços) em pequenos serviços independentes. Vamos falar sobre os prós e os contras dessa abordagem
9 |
10 | **Prós**
11 |
12 | - **Podem ser desenvolvidos e implantados de maneira independente**: a implantação de um aplicativo monolítico pode levar algum tempo. Quando muitos desenvolvedores trabalhando na mesma base de código as mesclagens podem ser complicadas. Com os microsserviços tudo isso desaparece, pois há repositórios dedicados para cada microsserviço. Você pode ativar ou reimplantar seu serviço sem ativar grandes recursos computacionais.
13 |
14 | - **Diferentes equipes podem trabalhar em serviços distintos**: fica muito mais fácil escalar verticalmente sua operação de TI com uma equipe por microsserviço.
15 |
16 | - **Diferentes serviços podem ser criados em linguagens de programação distintas**: sua empresa não precisa ter apenas *uma* pilha de tecnologia. Com esse grau de liberdade, os desenvolvedores contratados poderão usar as ferramentas e a linguagem de programação que preferirem para criar os serviços.
17 |
18 | - **Fácil de escalar**: com um orquestrador como o **[Kubernetes](https://docs.microsoft.com/azure/aks/?WT.mc_id=graphql_workshop-github-gllemos)**. Como os microsserviços estão se transformando em contêineres fica muito fácil escalar verticalmente o número de instâncias de microsserviços necessário para atender às demandas de usuário, como uma grande venda ou algo semelhante. Graças ao Kubernetes isso é muito fácil.
19 |
20 | **Contras**
21 |
22 | - Você precisa aprender mais sobre contêineres, pois é assim que você geralmente entrega seus microsserviços.
23 | - A orquestração se torna um problema que você precisa gerenciar. É necessário encontrar uma forma simples de criar contêineres, ativá-los e desativá-los.
24 | - Serviços cruzados é um assunto que você precisa gerenciar.
25 | - Leva um tempo para aprender a arquitetar mentalmente e *pensar* sobre microsserviços.
26 |
27 | ## Docker
28 |
29 | O **[Docker](https://docs.microsoft.com/dotnet/architecture/microservices/container-docker-introduction/docker-defined?WT.mc_id=graphql_workshop-github-gllemos)** ajuda a criar contêineres com base em nos microsserviços. Depois que os microsserviços estiverem sendo entregues como contêiner, poderemos efetuar push deles para o registro de contêiner na nuvem. Daí em diante, podemos fazer com que o Provedor de Nuvem instancie um serviço de aplicativo do contêiner OU podemos dizer a um orquestrador como o Kubernetes para escalar verticalmente o aplicativo em *n* instâncias de modo que possamos atender a milhões de clientes.
30 |
31 | Para poder trabalhar de maneira eficiente com o Docker neste workshop, aprenderemos os seguintes conceitos:
32 |
33 | - **Dockerfile** – Um arquivo do Docker é a receita para aquilo que você está prestes a criar. O arquivo contém informações como o sistema operacional no qual basear sua imagem, as dependências que precisam ser instaladas e, claro, informações sobre como copiar e executar o aplicativo dentro do contêiner.
34 |
35 | - **contêiner** – Um contêiner é uma caixa preta executável que tem apenas a fração do tamanho de uma VM. O motivo disso é que o contêiner se comunica com o sistema operacional do host em vez de manter um sistema operacional completo dentro do contêiner.
36 |
37 | - **imagem** – Uma imagem é aquilo que você obtém ao criar um artefato de um Dockerfile. Uma imagem não é executável e precisa primeiro ser convertida em um contêiner
38 |
39 | - **docker-compose** – O docker-compose é uma ferramenta usada quando você precisa gerenciar vários contêineres de uma só vez. Sem ele, você teria que recorrer à adição de comandos de criação, configuração e desmontagem para cada contêiner, isso significa muitos scripts e simplesmente se torna difícil de gerenciar
40 |
41 | ## O que vamos criar
42 |
43 | Criaremos dois microsserviços diferentes, proporcionando produtos e revisões, respectivamente.
44 |
45 | Para cada serviço, executaremos as seguintes etapas:
46 |
47 | - **Criar** um Serviço REST no Node.js + Expresso
48 | - **Definir** um Dockerfile – precisamos de um Dockerfile para cada serviço
49 | - **Colocar** em contêineres – criaremos uma imagem e um contêiner, respectivamente, usando o docker-compose, para termos cada contêiner pronto, funcionando e acessível em um navegador
50 |
51 | ### Criar um Serviço REST no .NET Core
52 |
53 | Criaremos dois serviços diferentes
54 | - `products service`: isso retornará uma lista de produtos
55 | - `reviews service`: isso conterá informações sobre uma revisão e um vinculará a uma ID de produto
56 |
57 | **Serviço de produtos**
58 |
59 | ```bash
60 | > dotnet new webapi -o products --no-https
61 | ```
62 |
63 | Adicione o arquivo `DefaultController.cs` ao diretório `Controllers` e dê a ele o seguinte conteúdo:
64 |
65 | ```csharp
66 | using System;
67 | using System.Collections.Generic;
68 | using System.Linq;
69 | using System.Threading.Tasks;
70 | using Microsoft.AspNetCore.Mvc;
71 | using Microsoft.Extensions.Logging;
72 |
73 | namespace products.Controllers
74 | {
75 | public class Product
76 | {
77 | public int Id { get; set; }
78 | public string Name { get; set; }
79 | }
80 |
81 | public class ProductsStore
82 | {
83 | public static List Products = new List()
84 | {
85 | new Product()
86 | {
87 | Id = 1,
88 | Name = "Avengers - End Game"
89 | }
90 | };
91 | }
92 |
93 | [ApiController]
94 | public class DefaultController : ControllerBase
95 | {
96 | [Route("/")]
97 | public List GetProducts()
98 | {
99 | return ProductsStore.Products;
100 | }
101 | }
102 | }
103 | ```
104 |
105 | Experimente executando o comando `dotnet run` no terminal. Vá para um navegador em `http://localhost:5000`. Isso deve mostrar uma lista de produtos.
106 |
107 | Desative o servidor com `CTRL+C`.
108 |
109 | **Serviço de Revisões**
110 |
111 | ```bash
112 | > dotnet new webapi -o reviews --no-https
113 | ```
114 |
115 | Adicione o arquivo `DefaultController.cs` ao diretório `Controllers` e dê a ele o seguinte conteúdo:
116 |
117 | ```csharp
118 | using System;
119 | using System.Collections.Generic;
120 | using System.Linq;
121 | using System.Threading.Tasks;
122 | using Microsoft.AspNetCore.Mvc;
123 | using Microsoft.Extensions.Logging;
124 |
125 | namespace reviews.Controllers
126 | {
127 | public class Review
128 | {
129 | public int Grade { get; set; }
130 | public string Title { get; set; }
131 | public string Description { get; set; }
132 | public int Product { get; set; }
133 | }
134 |
135 | public class ReviewsStore
136 | {
137 | public static List Reviews = new List()
138 | {
139 | new Review()
140 | {
141 | Grade = 5,
142 | Title = "Great movie",
143 | Description = "Great actor playing Thanos",
144 | Product = 1
145 | }
146 | };
147 | }
148 |
149 | [ApiController]
150 | public class DefaultController : ControllerBase
151 | {
152 | [Route("/")]
153 | public List GetReviews()
154 | {
155 | return ReviewsStore.Reviews;
156 | }
157 | }
158 | }
159 | ```
160 |
161 | Experimente executando `dotnet run` no terminal. Vá para um navegador em `http://localhost:5000`. Isso deve mostrar uma lista de produtos.
162 |
163 | Desative o servidor com `CTRL+C`.
164 |
165 | ### Definir um Dockerfile
166 |
167 | Precisamos fazer isso uma vez para cada serviço.
168 |
169 | **Adicionar o Dockerfile ao serviço de produtos**
170 |
171 | Vá para o diretório `products` e crie um arquivo chamado `Dockerfile`.
172 |
173 | Dê a ele o seguinte conteúdo:
174 |
175 | ```docker
176 | # Dockerfile
177 |
178 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
179 | WORKDIR /src
180 | COPY products.csproj .
181 | RUN dotnet restore
182 | COPY . .
183 | RUN dotnet publish -c release -o /app
184 |
185 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
186 | WORKDIR /app
187 | COPY --from=build /app .
188 | ENTRYPOINT ["dotnet", "products.dll"]
189 | ```
190 |
191 | **Adicionar o Dockerfile ao serviço de Revisões**
192 |
193 | Vá para o diretório `reviews` e crie um arquivo chamado `Dockerfile`. Dê a ele o seguinte conteúdo:
194 |
195 | ```docker
196 | # Dockerfile
197 |
198 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
199 | WORKDIR /src
200 | COPY reviews.csproj .
201 | RUN dotnet restore
202 | COPY . .
203 | RUN dotnet publish -c release -o /app
204 |
205 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
206 | WORKDIR /app
207 | COPY --from=build /app .
208 | ENTRYPOINT ["dotnet", "reviews.dll"]
209 | ```
210 |
211 | **Converter para Docker**
212 |
213 | Criamos um Dockerfile para cada serviço. Agora, a estrutura do projeto deve ser algo como o seguinte:
214 |
215 | ```
216 | products/
217 | -- .net core specific files
218 | Dockerfile
219 | reviews/
220 | -- .net core specific files
221 | Dockerfile
222 | ```
223 |
224 | Vamos verificar se estamos no nível raiz e criar um arquivo chamado `docker-compose.yaml`. Dê a ele o seguinte conteúdo:
225 |
226 | ```yaml
227 | version: '3.3'
228 | services:
229 | product-service:
230 | build:
231 | context: ./products
232 | ports:
233 | - "8000:80"
234 | networks:
235 | - microservices
236 | review-service:
237 | build:
238 | context: ./reviews
239 | ports:
240 | - "8001:80"
241 | networks:
242 | - microservices
243 | networks:
244 | microservices:
245 | ```
246 |
247 | O que o arquivo acima diz é: Para cada serviço:
248 | 1. **executar** o Dockerfile listado em `context`
249 | 2. **configurar** uma conexão entre a porta do sistema host e a porta do contêiner `:`
250 | 3. **colocar** cada contêiner na rede `microservices`
251 |
252 | Agora, a estrutura do projeto deve ser como o seguinte:
253 |
254 | Se É a primeira vez, só precisamos executar o comando:
255 |
256 | ```bash
257 | > docker-compose up -d
258 | ```
259 |
260 | Isso criará uma imagem de cada serviço, depois criará e executará um contêiner.
261 |
262 | Se NÃO É a primeira vez, em vez disso, execute o seguinte comando:
263 |
264 | ```bash
265 | > docker-compose build
266 | > docker-compose up -d
267 | ```
268 |
269 | Observe que executamos o comando `build` para garantir que as alterações feitas no Dockerfile sejam recriadas em uma nova imagem.
270 |
271 | Isso deve ter iniciado todos os serviços e você poderá acessá-los em `http://localhost:8000` e `http://localhost:8001`.
272 |
273 | Para desativar o tipo de serviços (sem necessidade ainda):
274 |
275 | ```bash
276 | > docker-compose down
277 | ```
278 |
279 | ## Solução
280 |
281 | [SOLUÇÃO parte 3 do Workshop](https://github.com/softchris/graphql-workshop-dotnet/tree/master/part3)
--------------------------------------------------------------------------------
/docs/workshop/4.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ☁️ Serverless Functions
3 | ---
4 |
5 | # ☁️ Serverless Functions
6 |
7 | Serverless is about writing functions in the Cloud that are fully managed. Fully managed means all we focus on is code. There's no OS, app server or anything else we need to care about, just code. This is called FaaS, functions as a service.
8 |
9 | There are two important concepts we need to know about to learn about Azure Functions:
10 |
11 | - **Trigger**, this is what starts the function, there are many things that can start a function like a HTTP request, a Queue message, a new database entry and so on
12 | - **Bindings**, bindings come in two different flavors input bindings and output bindings. The idea is to set up a connection to a data source with you not having to type any code to do so. Instead a JSON file is used to point things like connection string and exactly what data you want.
13 | - **Input binding**, an *input* binding means that we are reading data from a data source
14 | - **Output binding**, this means we are writing data to a data source
15 |
16 | ## Prerequisites
17 |
18 | To start working with Azure Functions we need the following
19 |
20 | - **azure functions core tools**, this is the core library
21 | - **vs code extension** (optional), this will however make authoring and deploying a whole lot easiser
22 |
23 | ### Azure Functions Core Tools
24 |
25 | Installing this looks a bit different on Linux, Mac and Windows.
26 |
27 | **Mac**
28 |
29 | ```
30 | brew tap azure/functions
31 | brew install azure-functions-core-tools
32 | ```
33 |
34 | **Windows**
35 |
36 | ```
37 | npm i -g azure-functions-core-tools --unsafe-perm true
38 | ```
39 |
40 | **Ubuntu**
41 |
42 | ```
43 | wget -q https://packages.microsoft.com/config/ubuntu/19.04/packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.deb
44 | ```
45 |
46 | ### VS Code Extension
47 |
48 | Install by going to this [link](vscode:extension/ms-azuretools.vscode-azurefunctions
49 | ) or open up VS Code and type `Azure Functions`
50 |
51 | 
52 |
53 |
54 | ## What we will build
55 |
56 | We will do the following:
57 |
58 | - **Create** an Azure Function
59 | - **Integrate** our GraphQL API
60 | - **Add** external endpoints to our GraphQL API
61 |
62 | ### Create an Azure Function
63 |
64 | We will use the fact that we have installed the Azure Function VS Code extension. It comes with some valuable commands that will help us scaffold the function we need.
65 |
66 | **Scaffold an Azure Function App + Function**
67 |
68 | An Azure Function needs to be hosted in an Azure Function App. To create an open up the Command Palette in VS Code (CTRL/CMD + SHIFT + P) or View/Command Palette. Thereafter type `Azure Functions: Create New Project` and select that.
69 |
70 | **First**, you will be prompted to what folder contains your project. The normal choice is the folder you are standing in.
71 |
72 | 
73 |
74 | **Secondly**, you are asked for what programming language you want the project to be, select `C#`
75 |
76 | 
77 |
78 | **Thirdly**, you are asked for what template to use for your first function in your Azure Functions project. Select `HttpTrigger`
79 |
80 | 
81 |
82 | **Fourth**, now it's asking what to name our function, we name it `GraphQL`, but you can name whatever you want
83 |
84 | 
85 |
86 | **Fifth**, lastly it's asking you for authhorization level, essentially what credentials you need to pass to be able to call it. Select *anonymous*
87 |
88 | 
89 |
90 | Now, we should have project overview looking something like this:
91 |
92 | ```
93 | .vscode/
94 | .funcignore
95 | host.json
96 | local.settings.json -- this contains app settings keys
97 | GraphQL.cs
98 | ```
99 |
100 | ### Integrate GraphQL
101 |
102 | So how do we go about integrating GraphQL?
103 |
104 | We need to do the following:
105 | - **Move** in the GraphQL files into our Azure project
106 | - **Remove** the part where Apollo hosts the web server
107 | - **Update Azure Function** with call to GraphQL
108 | - **Add** the needed libraries, i.e `GraphQL`
109 |
110 | **Move files**
111 |
112 | We simply move in our files into this structure, like so.
113 |
114 | ```
115 | .vscode/
116 | API/
117 | Data.cs
118 | Mutation.cs
119 | Query.cs
120 | Schema.cs
121 | .funcignore
122 | host.json
123 | local.settings.json
124 | ```
125 |
126 | Let's highlight our addition:
127 |
128 | ```
129 | API/
130 | Data.cs
131 | Mutation.cs
132 | Query.cs
133 | Schema.cs
134 | ```
135 |
136 | **Update Azure Function**
137 |
138 | Let's have a look at our Azure function:
139 |
140 | ```csharp
141 | // GraphQL.cs
142 |
143 | using System;
144 | using System.IO;
145 | using System.Threading.Tasks;
146 | using Microsoft.AspNetCore.Mvc;
147 | using Microsoft.Azure.WebJobs;
148 | using Microsoft.Azure.WebJobs.Extensions.Http;
149 | using Microsoft.AspNetCore.Http;
150 | using Microsoft.Extensions.Logging;
151 | using Newtonsoft.Json;
152 |
153 | namespace Microsoft
154 | {
155 | public static class GraphQL
156 | {
157 | [FunctionName("GraphQL")]
158 | public static async Task Run(
159 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
160 | ILogger log)
161 | {
162 | log.LogInformation("C# HTTP trigger function processed a request.");
163 |
164 | string name = req.Query["name"];
165 |
166 | string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
167 | dynamic data = JsonConvert.DeserializeObject(requestBody);
168 | name = name ?? data?.name;
169 |
170 | string responseMessage = string.IsNullOrEmpty(name)
171 | ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
172 | : $"Hello, {name}. This HTTP triggered function executed successfully.";
173 |
174 | return new OkObjectResult(responseMessage);
175 | }
176 | }
177 | }
178 |
179 | ```
180 |
181 | Let's update it to start using GraphQL. What we want to achieve is the following:
182 |
183 | - **read** query parameter `query`
184 | - **use** value of `query` to query our GraphQL server
185 | - **respond** with data from GraphQL Server
186 |
187 | Given the above agenda, let's change `GraphQL.cs` to the following:
188 |
189 | ```csharp
190 | // GraphQL.cs
191 |
192 | using System;
193 | using System.IO;
194 | using System.Threading.Tasks;
195 | using Microsoft.AspNetCore.Mvc;
196 | using Microsoft.Azure.WebJobs;
197 | using Microsoft.Azure.WebJobs.Extensions.Http;
198 | using Microsoft.AspNetCore.Http;
199 | using Microsoft.Extensions.Logging;
200 | using GraphQL;
201 |
202 | namespace Microsoft
203 | {
204 | public static class GraphQL
205 | {
206 | [FunctionName("GraphQL")]
207 | public static async Task Run(
208 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
209 | ILogger log)
210 | {
211 | log.LogInformation("C# HTTP trigger function processed a request.");
212 |
213 | string query = req.Query["query"];
214 | var schema = SchemaFactory.Create();
215 |
216 | var json = schema.Execute(_ =>
217 | {
218 | _.Query = query;
219 | });
220 | return new OkObjectResult(json);
221 | }
222 | }
223 | }
224 |
225 | ```
226 |
227 | **Install GraphQL NuGet**
228 |
229 | We need to update our project to use the `GraphQL` NuGet
230 |
231 | ```
232 | dotnet add package GraphQL
233 | ```
234 |
235 | ### Add external endpoints
236 |
237 | We need to realize the following. We have built two micro services that we can now use, so we no longer need to use the in-memory data. To use them we need to do the following:
238 |
239 | - Change code for `products` and `reviews` to do HTTP requests
240 | - Create a nice `HttpHelper` class that easily lets us fetch the JSON data we need
241 | - Get a JSON parsing lib
242 |
243 | **Install JSON lib**
244 |
245 | ```
246 | dotnet add package System.Text.Json
247 | ```
248 |
249 | **Create a HttpHelper**
250 |
251 | Add the following to `Query.cs`
252 |
253 | ```csharp
254 | // to the top
255 | using System.Net.Http;
256 | using System.Threading.Tasks;
257 | using System.Text.Json;
258 |
259 |
260 | // somewhere in the code
261 | public class HttpHelper
262 | {
263 | public static async ValueTask Get(string url)
264 | {
265 | var options = new JsonSerializerOptions
266 | {
267 | PropertyNameCaseInsensitive = true,
268 | };
269 |
270 | HttpClient client = new HttpClient();
271 | var streamTask = client.GetStreamAsync(url);
272 | var response = await System.Text.Json.JsonSerializer.DeserializeAsync(await streamTask, options);
273 | return response;
274 | }
275 | }
276 | ```
277 |
278 | The above will allow us to easily specify a URL and a return type. We will see later how this `HttpHelper` class will pay off.
279 |
280 | **Replace static data with HTTP requests**
281 |
282 | Keep working on `Query.cs`, this time ensure the method `GetProducts()` looks like this:
283 |
284 | ```csharp
285 | [GraphQLMetadata("products")]
286 | public async Task> GetProducts()
287 | {
288 | return await HttpHelper.Get>("http://localhost:8000");
289 | }
290 | ```
291 | and `GetReviews()` should look like this:
292 |
293 | ```csharp
294 | [GraphQLMetadata("reviews")]
295 | public async Task> GetReviews()
296 | {
297 | return await HttpHelper.Get>("http://localhost:8001");
298 | }
299 | ```
300 |
301 | lastly go into our `ReviewResolver` class and change the `Product()` method to say this:
302 |
303 | ```csharp
304 | public async Task Product(ResolveFieldContext context, Review review)
305 | {
306 | var products = await HttpHelper.Get>("http://localhost:8000");
307 | return products.SingleOrDefault(p => p.Id == review.Product);
308 | }
309 | ```
310 |
311 | ### Try it out
312 |
313 | Ensure you have started up the Micro services from part3 with
314 |
315 | ```
316 | docker-compose up
317 | ```
318 |
319 | Ensure you they respond to `http://localhost:8000` and `http://localhost:8001` respectively.
320 |
321 | Let's start the debugger for the Azure Function
322 |
323 | Now type the below `URL`
324 |
325 | ```
326 | http://localhost:7071/api/GraphQL?query={ reviews { title, grade, product { name } } }
327 | ```
328 |
329 |
330 | This nested query should now resolve by querying both our products endpoint and our reviews endpoint.
331 |
332 | ## Solution
333 |
334 | [SOLUTION workshop part 4](https://github.com/softchris/graphql-workshop-dotnet/tree/master/part4/solution)
--------------------------------------------------------------------------------
/docs/pt-br/workshop/4.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ☁️ Funções sem servidor
3 | ---
4 | # ☁️ Funções sem servidor
5 |
6 | Sem servidor significa gravar funções totalmente gerenciadas na nuvem. Ser totalmente gerenciado significa que todo o foco está no código. Não há sistema operacional, servidor de aplicativos nem qualquer outra preocupação, apenas a codificação. Isso é chamado de FaaS, funções como um serviço.
7 |
8 | Há dois conceitos importantes que precisamos conhecer para saber mais sobre o **[Azure Functions](https://docs.microsoft.com/azure/azure-functions/functions-overview?WT.mc_id=graphql_workshop-github-gllemos)**:
9 |
10 | - **Disparar**: é o que inicia a função. Há muitas opções que podem iniciar uma função, como uma solicitação HTTP, uma mensagem da fila, uma nova entrada de banco de dados e assim por diante
11 | - **Associações**: as associações são fornecidas em dois tipos: associações de entrada e associações de saída. A ideia é configurar uma conexão em uma fonte de dados, não havendo a necessidade de digitar nenhum código para fazer isso. Em vez disso, um arquivo JSON é usado para apontar itens como cadeia de conexão e exatamente quais dados você deseja.
12 | - **Associação de entrada**: uma associação de *entrada* significa que estamos lendo dados de uma fonte de dados
13 | - **Associação de saída**: significa que estamos gravando dados em uma fonte de dados
14 |
15 | ## Pré-requisitos
16 |
17 | Para começar a trabalhar com o Azure Functions precisamos do seguinte
18 |
19 | - **[Azure Functions Core Tools](https://docs.microsoft.com/azure/azure-functions/functions-run-local?WT.mc_id=graphql_workshop-github-gllemos)**: esta é a biblioteca principal
20 | - **[Azure Functions Extension para Vs Code](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions&WT.mc_id=graphql_workshop-github-gllemos)** (opcional): facilitará a criação e a implantação de um lote
21 |
22 | ### Azure Functions Core Tools
23 |
24 | A instalação desse componente é um pouco diferente no Linux, no Mac e no Windows.
25 |
26 | * **Mac**
27 |
28 | ```bash
29 | > brew tap azure/functions
30 | > brew install azure-functions-core-tools
31 | ```
32 |
33 | * **Windows**
34 |
35 | ```bash
36 | > npm i -g azure-functions-core-tools --unsafe-perm true
37 | ```
38 |
39 | * **Ubuntu**
40 |
41 | ```bash
42 | > wget -q https://packages.microsoft.com/config/ubuntu/19.04/packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.deb
43 | ```
44 |
45 | ### Extensão do VS Code
46 |
47 | Instale acessando este **[link](vscode:extension/ms-azuretools.vscode-azurefunctions
48 | )** ou abra o VS Code e digite `Azure Functions` na aba de `Extensions`
49 |
50 | 
51 |
52 |
53 | ## O que vamos criar?
54 |
55 | Faremos o seguinte:
56 |
57 | - **Criar** uma função do Azure
58 | - **Integrar** nossa API do GraphQL
59 | - **Adicionar** pontos de extremidade externos à nossa API do GraphQL
60 |
61 | ### Criando um Azure Function
62 |
63 | Usaremos a extensão do VS Code da função do Azure que instalamos. Ela conta com alguns comandos importantes que nos ajudarão a efetuar scaffold da função necessária.
64 |
65 | **Efetuar scaffold do aplicativo de funções do Azure + função**
66 |
67 | Um Azure Function precisa ser hospedada em um aplicativo de funções do Azure. Para criar uma, abra a paleta de comandos no VS Code (CTRL/CMD + SHIFT + P) ou a paleta de Exibição/Comando. Em seguida, digite `Azure Functions: Create New Project` e selecione-o.
68 |
69 | **Primeiro:** será solicitada a pasta que contém seu projeto. A escolha normal é a pasta que você está alterando.
70 |
71 | 
72 |
73 | **Segundo:** será solicitada a linguagem de programação que você deseja para o projeto. Selecione `C#`
74 |
75 | 
76 |
77 | **Terceiro:**, será solicitado o modelo a ser usado para sua primeira função em seu projeto do Azure Functions. Selecione `HttpTrigger`
78 |
79 | 
80 |
81 | **Quarta:**, será solicitado que você nomeie a função. Nós a nomeamos como `GraphQL`, mas você pode nomear como quiser
82 |
83 | 
84 |
85 | **Último:**, será solicitado o nível de autorização. Basicamente, as credenciais que você precisa ter para poder ter acesso. Selecione *anônimo*
86 |
87 | 
88 |
89 | Agora, devemos ter uma visão geral do projeto semelhante a esta:
90 |
91 | ```javascript
92 | .vscode/
93 | .funcignore
94 | host.json
95 | local.settings.json -- this contains app settings keys
96 | GraphQL.cs
97 | ```
98 |
99 | ### Integrar o GraphQL
100 |
101 | Como fazemos a integração do GraphQL?
102 |
103 | É necessário fazer o seguinte:
104 | - **Mover** os arquivos do GraphQL para nosso projeto do Azure
105 | - **Remover** a parte em que o Apollo hospeda o servidor Web
106 | - **Atualizar a função do Azure** com a chamada para o GraphQL
107 | - **Adicionar** as bibliotecas necessárias, por exemplo, `GraphQL`
108 |
109 | **Mover os arquivos**
110 |
111 | Simplesmente movemos os arquivos nessa estrutura.
112 |
113 | ```csharp
114 | .vscode/
115 | API/
116 | Data.cs
117 | Mutation.cs
118 | Query.cs
119 | Schema.cs
120 | .funcignore
121 | host.json
122 | local.settings.json
123 | ```
124 |
125 | Vamos destacar nossa adição:
126 |
127 | ```csharp
128 | API/
129 | Data.cs
130 | Mutation.cs
131 | Query.cs
132 | Schema.cs
133 | ```
134 |
135 | **Atualizando o Azure Function**
136 |
137 | Vamos dar uma olhada no nosso Azure Function:
138 |
139 | ```csharp
140 | // GraphQL.cs
141 |
142 | using System;
143 | using System.IO;
144 | using System.Threading.Tasks;
145 | using Microsoft.AspNetCore.Mvc;
146 | using Microsoft.Azure.WebJobs;
147 | using Microsoft.Azure.WebJobs.Extensions.Http;
148 | using Microsoft.AspNetCore.Http;
149 | using Microsoft.Extensions.Logging;
150 | using Newtonsoft.Json;
151 |
152 | namespace Microsoft
153 | {
154 | public static class GraphQL
155 | {
156 | [FunctionName("GraphQL")]
157 | public static async Task Run(
158 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
159 | ILogger log)
160 | {
161 | log.LogInformation("C# HTTP trigger function processed a request.");
162 |
163 | string name = req.Query["name"];
164 |
165 | string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
166 | dynamic data = JsonConvert.DeserializeObject(requestBody);
167 | name = name ?? data?.name;
168 |
169 | string responseMessage = string.IsNullOrEmpty(name)
170 | ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
171 | : $"Hello, {name}. This HTTP triggered function executed successfully.";
172 |
173 | return new OkObjectResult(responseMessage);
174 | }
175 | }
176 | }
177 | ```
178 |
179 | Vamos atualizá-la para começar a usar o GraphQL. O que queremos é o seguinte:
180 |
181 | - **ler** o parâmetro de consulta `query`
182 | - **usar** o valor de `query` para consultar nosso servidor GraphQL
183 | - **responder** usando dados do servidor GraphQL
184 |
185 | De acordo com o que vimos acima, vamos alterar `GraphQL.cs` para o seguinte:
186 |
187 | ```csharp
188 | // GraphQL.cs
189 |
190 | using System;
191 | using System.IO;
192 | using System.Threading.Tasks;
193 | using Microsoft.AspNetCore.Mvc;
194 | using Microsoft.Azure.WebJobs;
195 | using Microsoft.Azure.WebJobs.Extensions.Http;
196 | using Microsoft.AspNetCore.Http;
197 | using Microsoft.Extensions.Logging;
198 | using GraphQL;
199 |
200 | namespace Microsoft
201 | {
202 | public static class GraphQL
203 | {
204 | [FunctionName("GraphQL")]
205 | public static async Task Run(
206 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
207 | ILogger log)
208 | {
209 | log.LogInformation("C# HTTP trigger function processed a request.");
210 |
211 | string query = req.Query["query"];
212 | var schema = SchemaFactory.Create();
213 |
214 | var json = schema.Execute(_ =>
215 | {
216 | _.Query = query;
217 | });
218 | return new OkObjectResult(json);
219 | }
220 | }
221 | }
222 | ```
223 |
224 | **Instalando GraphQL via NuGet**
225 |
226 | Precisamos atualizar nosso projeto para usar o NuGet do `GraphQL`
227 |
228 | ```
229 | dotnet add package GraphQL
230 | ```
231 |
232 | ### Adicionar pontos de extremidade externos
233 |
234 | Precisamos observar o seguinte. Criamos dois microsserviços que podemos usar agora, portanto, não precisamos mais usar os dados na memória. Para usá-los, é preciso fazer o seguinte:
235 |
236 | - Alterar o código para `products` e `reviews` para fazer solicitações HTTP
237 | - Criar uma classe `HttpHelper` que nos permita buscar facilmente os dados JSON de que precisamos
238 | - Obter uma biblioteca de análise JSON
239 |
240 | **Instalar a biblioteca JSON**
241 |
242 | ```bash
243 | > dotnet add package System.Text.Json
244 | ```
245 |
246 | **Criando um HttpHelper**
247 |
248 | Adicione o seguinte ao `Query.cs`
249 |
250 | ```csharp
251 | // to the top
252 | using System.Net.Http;
253 | using System.Threading.Tasks;
254 | using System.Text.Json;
255 |
256 | // somewhere in the code
257 | public class HttpHelper
258 | {
259 | public static async ValueTask Get(string url)
260 | {
261 | var options = new JsonSerializerOptions
262 | {
263 | PropertyNameCaseInsensitive = true,
264 | };
265 |
266 | HttpClient client = new HttpClient();
267 | var streamTask = client.GetStreamAsync(url);
268 | var response = await System.Text.Json.JsonSerializer.DeserializeAsync(await streamTask, options);
269 | return response;
270 | }
271 | }
272 | ```
273 |
274 | Os itens acima nos permitirão especificar facilmente uma URL e um tipo de retorno. Veremos mais adiante como essa classe `HttpHelper` será usada.
275 |
276 | **Substituir dados estáticos por solicitações HTTP**
277 |
278 | Continue trabalhando em `Query.cs`. Dessa vez, verifique se o método `GetProducts()` tem a seguinte aparência:
279 |
280 | ```csharp
281 | [GraphQLMetadata("products")]
282 | public async Task> GetProducts()
283 | {
284 | return await HttpHelper.Get>("http://localhost:8000");
285 | }
286 | ```
287 | e `GetReviews()` deve ter esta aparência:
288 |
289 | ```csharp
290 | [GraphQLMetadata("reviews")]
291 | public async Task> GetReviews()
292 | {
293 | return await HttpHelper.Get>("http://localhost:8001");
294 | }
295 | ```
296 |
297 | Por fim, acesse a classe `ReviewResolver` e altere o método `Product()` para mostrar o seguinte:
298 |
299 | ```csharp
300 | public async Task Product(ResolveFieldContext context, Review review)
301 | {
302 | var products = await HttpHelper.Get>("http://localhost:8000");
303 | return products.SingleOrDefault(p => p.Id == review.Product);
304 | }
305 | ```
306 |
307 | ### Testando
308 |
309 | Verifique se você iniciou os microsserviços da parte 3 com
310 |
311 | ```bash
312 | > docker-compose up
313 | ```
314 |
315 | Verifique se eles respondem a `http://localhost:8000` e `http://localhost:8001`, respectivamente.
316 |
317 | Vamos iniciar o depurador para a função do Azure
318 |
319 | Agora, digite o seguinte `URL`
320 |
321 | ```bash
322 | http://localhost:7071/api/GraphQL?query={ reviews { title, grade, product { name } } }
323 | ```
324 |
325 |
326 | Essa consulta aninhada agora deve ser resolvida consultando nosso ponto de extremidade de produtos e nosso ponto de extremidade de revisões.
327 |
328 | ## Solução
329 |
330 | [SOLUÇÃO parte 4 do Workshop](https://github.com/softchris/graphql-workshop-dotnet/tree/master/part4/solution)
--------------------------------------------------------------------------------
/docs/workshop/2.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 🔎 The GraphQL API
3 | ---
4 |
5 | # 🔎 GraphQL
6 |
7 | Let's first give a little intro to GraphQL and the different parts it consist of.
8 |
9 | ## Concepts
10 |
11 | - **Schema**, this is where you define your data types and what things you can query for
12 | - **Resolvers**, these are functions that are being invoked when you ask for something from the schema. Your resolvers should respond with data or change the data depending on what they are meant for.
13 |
14 | ## Our first hello world API
15 |
16 | The easiest way to understand GraphQL is to build something with it and learn as we go.
17 |
18 | We will use something called `GQL` or **G**raphQL **Q**uery **L**anguage. We define our first simple schema like so:
19 |
20 | ### Schema
21 |
22 | ```
23 | type Query {
24 | hello: String
25 | }
26 | ```
27 |
28 | At this point we have defined the built in type `Query` and we are saying there is one thing we can query for, namely `hello`. Note how we after `:` is saying `string`. This is us saying what the return type is for `hello`;
29 |
30 | This means we can pose a query like so:
31 |
32 | ```
33 | {
34 | hello
35 | }
36 | ```
37 |
38 | We won't get an answer at this point because we haven't connected it to a resolver function that knows how to answer.
39 |
40 | Now, connecting a schema to a resolver is done differently depending on what library you use to build your GraphQL server.
41 |
42 | ### Resolver
43 |
44 | Let's define a resolver like it looks like in most implementations:
45 |
46 | ```csharp
47 | public class Query
48 | {
49 | [GraphQLMetadata("hello")]
50 | public string GetHello()
51 | {
52 | return "World";
53 | }
54 | }
55 | ```
56 |
57 | ### Custom type
58 |
59 | In GraphQL you have different primitives, also called scalar types, that can serve as inputs, return types. You can also combine several primitives to form a custom type. Lets first mention what those primitives are:
60 |
61 | - **String**, this is your typical string type, containing string characters
62 | - **ID**, represents a unique identifier, signifies that it is not intended to be human‐readable
63 | - **Float**, A signed double-precision floating-point value
64 | - **Int**. A signed 32‐bit integer
65 | - **Boolean**, has the value true or false
66 |
67 | So far we have only the queryable property `hello` and one resolver function. You are likely to want to build something more advanced than that and combining scalars in a group we can create a custom type like so:
68 |
69 | ```
70 | type Person {
71 | id: ID!
72 | name: String,
73 | address: String,
74 | speaker: Boolean
75 | }
76 | ```
77 |
78 | NOTE, we starting using exclamation character `!` on the `id` field, which means that this value cannot be *null*, i.e we can't leave it out.
79 |
80 | We can also, as mentioned use our custom type in our schema so we can extend it to look like so:
81 |
82 | ```
83 | type Person {
84 | id: ID!
85 | name: String,
86 | address: String,
87 | speaker: Boolean
88 | }
89 |
90 | type Query {
91 | hello: string,
92 | person: Person
93 | }
94 | ```
95 |
96 | which means we now need a new resolver function, like so:
97 |
98 | ```csharp
99 | public class Query
100 | {
101 | [GraphQLMetadata("hello")]
102 | public string GetHello()
103 | {
104 | return "World";
105 | }
106 |
107 | [GraphQLMetadata("person")]
108 | public Person GetPerson()
109 | {
110 | return new Person()
111 | {
112 | Id=1,
113 | Name="Jen",
114 | Address ="One Microsoft Way Redmond USA",
115 | Speaker = True
116 | };
117 | }
118 | }
119 |
120 | ```
121 |
122 | ### List type
123 |
124 | List type means that we can define in the schema that we get an array of whatever item back, like so:
125 |
126 | ```
127 | type Person {
128 | id: ID!
129 | name: String,
130 | address: String,
131 | speaker: Boolean
132 | }
133 |
134 | type Query {
135 | hello: string,
136 | person: Person,
137 | people: [Person]
138 | }
139 | ```
140 |
141 | We add the queryable type `people` which has the return type `[Person]` which simply means a list of the type `Person`. This of course means that we need to add a resolver function for this so we extend our resolver object with yet another function `people`:
142 |
143 | ```csharp
144 | public class Query
145 | {
146 | [GraphQLMetadata("hello")]
147 | public string GetHello()
148 | {
149 | return "World";
150 | }
151 |
152 | [GraphQLMetadata("person")]
153 | public Person GetPerson()
154 | {
155 | return new Person()
156 | {
157 | Id=1,
158 | Name="Jen",
159 | Address ="One Microsoft Way Redmond USA",
160 | Speaker = True
161 | };
162 | }
163 |
164 | [GraphQLMetadata("people")]
165 | public List GetPeople()
166 | {
167 | return new new List(){
168 | new Person()
169 | {
170 | Id=1,
171 | Name="Jen",
172 | Address ="One Microsoft Way Redmond USA",
173 | Speaker = True
174 | },
175 | new Person()
176 | {
177 | Id=2,
178 | Name="Chris",
179 | Address ="One Microsoft Way Redmond USA",
180 | Speaker = True
181 | }
182 | };
183 | }
184 | }
185 | ```
186 |
187 | ## Query with argument
188 |
189 | Now you of course if going to want to filter down your response at some point by being able to ask for a specific item. Imagine you don't want the full list of people back but a specific person. For that use case we can expose a queryable that takes a parameter like so:
190 |
191 | ```
192 | type Person {
193 | id: ID!
194 | name: String,
195 | address: String,
196 | speaker: Boolean
197 | }
198 |
199 | type Query {
200 | hello: string,
201 | person: Person,
202 | people: [Person],
203 | getPerson(id: ID!): Person
204 | }
205 | ```
206 |
207 | Now, to add this to our resolver object we just need to add a matching name property `getPerson` but we also need to dig out the parameter from the incoming request object:
208 |
209 | ```csharp
210 | var people = new List()
211 | {
212 | new Person()
213 | {
214 | Id=1,
215 | Name="Jen",
216 | Address ="One Microsoft Way Redmond USA",
217 | Speaker = True
218 | },
219 | new Person()
220 | {
221 | Id=2,
222 | Name="Chris",
223 | Address ="One Microsoft Way Redmond USA",
224 | Speaker = True
225 | }
226 | };
227 |
228 | public class Query
229 | {
230 | [GraphQLMetadata("hello")]
231 | public string GetHello()
232 | {
233 | return "World";
234 | }
235 |
236 | [GraphQLMetadata("person")]
237 | public Person GetPerson()
238 | {
239 | return new Person()
240 | {
241 | Id=1,
242 | Name="Jen",
243 | Address ="One Microsoft Way Redmond USA",
244 | Speaker = True
245 | };
246 | }
247 |
248 | [GraphQLMetadata("people")]
249 | public List GetPeople()
250 | {
251 | return people;
252 | }
253 |
254 | [GraphQLMetadata("getPerson")]
255 | public Person GetPersonById(int id)
256 | {
257 | return people.SingleOrDefault( p => p.Id == id);
258 | }
259 | }
260 | ```
261 |
262 | ## Mutation
263 |
264 | So far we have concentrated on learning scalars, custom types and how to build out our resolver object to be able to answer all of our queries and that really is the *bread and butter* of mastering GraphQL. But there is another construct we need to know about to be able to build a CRUD, **C**reate **R**ead **U**pdate **D**elete API, namely *mutations*. A *Mutation* is something that in GraphQL signals that we want to change something, either create something new, update it or remove it.
265 |
266 | Let's start with a very simple Mutation, adding an item to a list:
267 |
268 | ```
269 | type Mutation {
270 | addItem(item: String): String
271 | }
272 | ```
273 |
274 | Just like anything inside of a `Query` we can invoke anything inside it like a public API so we can call `addItem` like so:
275 |
276 | ```
277 | addItem(item: "my new item")
278 | ```
279 |
280 | Most of the time you might want a more complex input type than a scalar. At that we need to build such an input type, like so:
281 |
282 | ```
283 | input ProductInput {
284 | id: ID!
285 | name: String,
286 | address: String,
287 | speaker: Boolean
288 | }
289 | ```
290 |
291 | As you can see it's pretty much identical to our `type Person` and NO we can't use that one, we need to define something of type `input` like above. Let's add a `addPerson` property to our Schema:
292 |
293 | ```
294 | input PersonInput {
295 | id: ID!
296 | name: String,
297 | address: String,
298 | speaker: Boolean
299 | }
300 |
301 | type Person {
302 | id: ID!
303 | name: String,
304 | address: String,
305 | speaker: Boolean
306 | }
307 |
308 | type Mutation {
309 | addPerson(person: PersonInput): Person
310 | }
311 |
312 | type Query {
313 | hello: string,
314 | person: Person,
315 | people: [Person],
316 | getPerson(id: ID!): Person
317 | }
318 | ```
319 |
320 | To invoke this mutation `addPerson` we type:
321 |
322 | ```
323 | addPerson(person: { id: 3, name: 'Amy', address: `One Microsoft Way Redmond USA`, speaker: true }) {
324 | name
325 | }
326 | ```
327 |
328 | NOTE, we do get a return type of type `Person` so we need to select one or more columns as we are inserting a person but querying the response.
329 |
330 | lastly let's add our resolve function to our object:
331 |
332 | ```csharp
333 | var people = new List()
334 | {
335 | new Person()
336 | {
337 | Id=1,
338 | Name="Jen",
339 | Address ="One Microsoft Way Redmond USA",
340 | Speaker = True
341 | },
342 | new Person()
343 | {
344 | Id=2,
345 | Name="Chris",
346 | Address ="One Microsoft Way Redmond USA",
347 | Speaker = True
348 | }
349 | };
350 |
351 | public class Query
352 | {
353 | [GraphQLMetadata("hello")]
354 | public string GetHello()
355 | {
356 | return "World";
357 | }
358 |
359 | [GraphQLMetadata("person")]
360 | public Person GetPerson()
361 | {
362 | return new Person()
363 | {
364 | Id=1,
365 | Name="Jen",
366 | Address ="One Microsoft Way Redmond USA",
367 | Speaker = True
368 | };
369 | }
370 |
371 | [GraphQLMetadata("people")]
372 | public List GetPeople()
373 | {
374 | return people;
375 | }
376 |
377 | [GraphQLMetadata("getPerson")]
378 | public Person GetPersonById(int id)
379 | {
380 | return people.SingleOrDefault( p => p.Id == id);
381 | }
382 | }
383 |
384 | [GraphQLMetadata("Mutation")]
385 | public class Mutation {
386 | public String addPerson(Person person)
387 | {
388 | people.push(person);
389 | return "Success creating " + person;
390 | }
391 | }
392 | ```
393 |
394 | ## Nested type
395 |
396 | Quite often we want to do queries where we query at depth and we might have a schema looking like this:
397 |
398 | ```
399 | type Person {
400 | id: ID!
401 | name: String,
402 | address: String,
403 | speaker: Boolean,
404 | friend: Person
405 | }
406 | ```
407 |
408 | Especially note the property `friend`. There are two question we need an answer to here:
409 |
410 | 1. how do you store the data?
411 | 2. how do we resolve `friend`
412 |
413 | Two very good questions and the answer is simple.
414 | As for how we store the data the answer is that our `friend` property needs to point to a primitive that represents the unique identifier, which means the data needs to look like this:
415 |
416 | ```csharp
417 | var person = new Person()
418 | {
419 | Id = 1,
420 | Name = "Jen",
421 | Address = "One Microsoft Way Redmond USA",
422 | Speaker = True,
423 | Friend = 2
424 | };
425 | ```
426 |
427 | NOTE how `friend` has value `2`.
428 |
429 | Now to the next question, how to resolve this? What we mean by resolve is how to we support queries like this:
430 |
431 | ```
432 | {
433 | person {
434 | id,
435 | name,
436 | address,
437 | friend {
438 | name
439 | }
440 | }
441 | }
442 | ```
443 |
444 | As we said this is pretty simple once you know how. Let's go to our resolver object and update it like so:
445 |
446 | ```csharp
447 | var people = new List()
448 | {
449 | new Person()
450 | {
451 | Id=1,
452 | Name="Jen",
453 | Address ="One Microsoft Way Redmond USA",
454 | Speaker = True,
455 | Friend = 2
456 | },
457 | new Person()
458 | {
459 | Id=2,
460 | Name="Chris",
461 | Address ="One Microsoft Way Redmond USA",
462 | Speaker = True,
463 | Friend = 1
464 | }
465 | };
466 |
467 | public class Query
468 | {
469 | [GraphQLMetadata("hello")]
470 | public string GetHello()
471 | {
472 | return "World";
473 | }
474 |
475 | [GraphQLMetadata("person")]
476 | public Person GetPerson()
477 | {
478 | return new Person()
479 | {
480 | Id=1,
481 | Name="Jen",
482 | Address ="One Microsoft Way Redmond USA",
483 | Speaker = True
484 | };
485 | }
486 |
487 | [GraphQLMetadata("people")]
488 | public List GetPeople()
489 | {
490 | return people;
491 | }
492 |
493 | [GraphQLMetadata("getPerson")]
494 | public Person GetPersonById(int id)
495 | {
496 | return people.SingleOrDefault( p => p.Id == id);
497 | }
498 | }
499 |
500 | [GraphQLMetadata("Mutation")]
501 | public class Mutation {
502 | public String addPerson(Person person)
503 | {
504 | people.push(person);
505 | return "Success creating " + person;
506 | }
507 | }
508 |
509 | // this is used to resolve `friend`
510 | [GraphQLMetadata("Person", IsTypeOf=typeof(Person))]
511 | public class PersonResolver
512 | {
513 | public Person Friend(ResolveFieldContext context, Person source)
514 | {
515 | return people.SingleOrDefault(p => p.Id == source.Friend);
516 | }
517 | }
518 | ```
519 |
520 | NOTE, the addition of `Person` and its inner property `friend` that points to a resolver function that gets passed the value `2` which it is able to resolve by filtering that out of the list `people`.
521 |
522 |
523 | ## What we will build
524 |
525 | We will build a GraphQL consisting of two different resources `Products` and `Reviews`. The idea is two:
526 |
527 | 1. Demonstrate how to build a GraphQL API
528 | 2. Learn how to build your API to support nested queries
529 |
530 | ### Part I - Install and set up
531 |
532 | We assume you have installed .NET Core already. If you haven't go to the [install page](https://docs.microsoft.com/en-us/dotnet/core/install/sdk?pivots=os-windows)
533 |
534 | **Initialize a .NET Core project**
535 |
536 | ```
537 | dotnet new console -o api
538 | ```
539 |
540 | This will create a `console` project. You can definitely create a web project if you want to support things such `graphiql`, a visual environment to try out your queries.
541 |
542 | **Install dependencies**
543 | Now you are ready for the next step which is to install GraphQL. We will do that by the following command:
544 |
545 | NOTE, ensure you are standing in directory `API`.
546 |
547 | ```
548 | dotnet add package GraphQL
549 | ```
550 |
551 | > Currently I've set my .NET Core app to 2.2
552 |
553 | **Create our Server**
554 |
555 | We will do the following:
556 |
557 | - Define schema
558 | - Define a Query Class
559 | - Execute a Query
560 |
561 | Open up `Program.cs` and add the following at the top of the file:
562 |
563 | ```
564 | using GraphQL;
565 | using GraphQL.Types;
566 | ```
567 |
568 | in the `Main()` method:
569 |
570 | ```csharp
571 | var schema = Schema.For(@"
572 | type Query {
573 | hello: String
574 | }
575 | ", _ =>
576 | {
577 | _.Types.Include();
578 | });
579 | ```
580 |
581 | Next add the `Query` class in the `Program.cs` file, like so:
582 |
583 | ```csharp
584 | using GraphQL;
585 |
586 | public class Query
587 | {
588 | [GraphQLMetadata("hello")]
589 | public string GetHello()
590 | {
591 | return "World";
592 | }
593 | }
594 | ```
595 |
596 | Now to run a query we need the following code, still in `Main()` method:
597 |
598 | ```csharp
599 | var json = schema.Execute(_ =>
600 | {
601 | _.Query = "{ hello }";
602 | });
603 |
604 | Console.WriteLine(json);
605 | ```
606 |
607 | Next up, we want to run this. We do so by typing the following in the terminal:
608 |
609 | ```
610 | dotnet run
611 | ```
612 |
613 | You should now see something like this:
614 |
615 | ```json
616 | {
617 | "data": {
618 | "hello": "World"
619 | }
620 | }
621 | ```
622 |
623 | ### Part II - custom types
624 |
625 | In this next part we will take our existing app and give it some more types, namely `products`. We will show how to:
626 |
627 | - **query** for all products
628 | - **query** for a specific product
629 | - **create** a new product
630 |
631 | Lets begin.
632 |
633 | **Refactoring**
634 |
635 | The first thing we will do is to separate our `Program.cs` into several files:
636 |
637 | - **Program.cs** this will only be used to run queries on our schema
638 | - **Schema.cs**, this will contain our schema definition
639 | - **Query.cs** this will contain our resolver object
640 |
641 | The content of these files should now be:
642 |
643 | ```csharp
644 | // Query.cs
645 |
646 | using GraphQL.Types;
647 | using GraphQL;
648 |
649 | namespace app
650 | {
651 | public class Query
652 | {
653 | [GraphQLMetadata("hello")]
654 | public string GetHello()
655 | {
656 | return "World";
657 | }
658 | }
659 | }
660 | ```
661 |
662 | ```csharp
663 | // Schema.cs
664 |
665 | using GraphQL.Types;
666 | using GraphQL;
667 |
668 | namespace app
669 | {
670 | public class SchemaFactory
671 | {
672 | public static ISchema Create()
673 | {
674 | var schema = Schema.For(@"
675 | type Query {
676 | hello: String
677 | }
678 | ", _ =>
679 | {
680 | _.Types.Include();
681 | });
682 | return schema;
683 | }
684 | }
685 | }
686 | ```
687 |
688 | ```csharp
689 | // Program.cs
690 |
691 | using System;
692 | using GraphQL;
693 | using GraphQL.Types;
694 |
695 | namespace app
696 | {
697 | class Program
698 | {
699 | static void Main(string[] args)
700 | {
701 | var schema = SchemaFactory.Create();
702 | var json = schema.Execute(_ =>
703 | {
704 | _.Query = "{ hello }";
705 | });
706 |
707 | Console.WriteLine(json);
708 | }
709 | }
710 | }
711 | ```
712 |
713 | **Adding a custom type and some new queries**
714 |
715 | Ok, now that we added a custom type, lets open up `Schema.cs` and add the custom type `Product`:
716 |
717 | ```csharp
718 | // Schema.cs
719 |
720 | using GraphQL.Types;
721 | using GraphQL;
722 |
723 | namespace app
724 | {
725 | public class SchemaFactory
726 | {
727 | public static ISchema Create()
728 | {
729 | var schema = Schema.For(@"
730 | type Product {
731 | id: ID,
732 | name: String
733 | }
734 |
735 | type Query {
736 | hello: String,
737 | products: [Product],
738 | product(id: ID!): Product
739 | }
740 | ", _ =>
741 | {
742 | _.Types.Include();
743 | });
744 | return schema;
745 | }
746 | }
747 | }
748 | ```
749 |
750 | Above we have added the custom type `Product`, by doing this:
751 |
752 | ```
753 | type Product {
754 | id: ID,
755 | name: String
756 | }
757 | ```
758 |
759 | Then we have added the following to type `Schema.cs`:
760 |
761 | ```
762 | products: [Product],
763 | product(id: ID!): Product
764 | ```
765 |
766 | The first row means we can query for `products` and expect to get a product array back, as indicated by the array `[]` symbol.
767 |
768 | The second `product(id: ID!)`, means we added a query that made it possible to query for `product` but it needs a parameter `id`.
769 |
770 | Below is how you would invoke them:
771 |
772 | ```
773 | {
774 | products {
775 | name
776 | }
777 | product(id: 1) {
778 | name
779 | }
780 | }
781 | ```
782 |
783 | **Adding resolvers**
784 |
785 | We need to add resolver methods to be able to respond if the user types either `products` or `product`. For this we will open up `Query.cs` and ensure it looks like this:
786 |
787 | ```csharp
788 | // Query.cs
789 |
790 | using GraphQL.Types;
791 | using GraphQL;
792 | using System.Linq;
793 | using System.Collections.Generic;
794 |
795 | namespace app
796 | {
797 | public class Product
798 | {
799 | public int Id { get; set; }
800 | public string Name { get; set; }
801 | }
802 | public class Query
803 | {
804 | public static List Products = new List()
805 | {
806 | new Product()
807 | {
808 | Id = 1,
809 | Name = "Avengers - End Game"
810 | }
811 | };
812 |
813 | [GraphQLMetadata("hello")]
814 | public string GetHello()
815 | {
816 | return "World";
817 | }
818 |
819 | [GraphQLMetadata("products")]
820 | public List GetProducts()
821 | {
822 | return Data.Products;
823 | }
824 |
825 | [GraphQLMetadata("product")]
826 | public Product GetProductById(int id)
827 | {
828 | return Data.Products.SingleOrDefault( p => p.Id == id );
829 | }
830 | }
831 | }
832 | ```
833 |
834 | Above we add the methods `GetProducts()` and `GetProductById()` respectively.
835 |
836 | NOTE, to get input parameters we need to just specify those as an input params where they are used as with the method `GetProductById()`
837 |
838 | Secondly we will add the file `Data.cs`, a static data repository, that will contain all the data we want to query for and change.
839 |
840 | ```csharp
841 | // Data.cs
842 | using System.Collections.Generic;
843 |
844 | namespace app
845 | {
846 | public class Data
847 | {
848 | public static List Products = new List()
849 | {
850 | new Product()
851 | {
852 | Id = 1,
853 | Name = "Avengers - End Game"
854 | }
855 | };
856 | }
857 | }
858 | ```
859 |
860 | Lastly change `Program.cs` to look like this:
861 |
862 | ```csharp
863 | using System;
864 | using GraphQL;
865 | using GraphQL.Types;
866 |
867 | namespace app
868 | {
869 | class Program
870 | {
871 | static void Main(string[] args)
872 | {
873 | var schema = SchemaFactory.Create();
874 | var json = schema.Execute(_ =>
875 | {
876 | _.Query = "{ products { name } product(id: 1){ name } }";
877 | });
878 |
879 | Console.WriteLine(json);
880 | }
881 | }
882 | }
883 | ```
884 |
885 | Followed by `dotnet run`, that should give the following output in the terminal:
886 |
887 | ```json
888 | {
889 | "data": {
890 | "products": [
891 | {
892 | "name": "Avengers - End Game"
893 | }
894 | ],
895 | "product": {
896 | "name": "Avengers - End Game"
897 | }
898 | }
899 | }
900 | ```
901 |
902 | **Supporting 'create product' - adding mutations**
903 |
904 | Now we know how to support queries, lets look into supporting scenarios in which we add data. We will support creating a product. They way we do that is by adding a mutation. Lets start with updating the schema file `Schema.cs`:
905 |
906 | ```csharp
907 | using GraphQL.Types;
908 | using GraphQL;
909 |
910 | namespace app
911 | {
912 | public class SchemaFactory
913 | {
914 | public static ISchema Create()
915 | {
916 | var schema = Schema.For(@"
917 | type Product {
918 | id: ID,
919 | name: String
920 | }
921 |
922 | input ProductInput {
923 | name: String
924 | }
925 |
926 | type Mutation {
927 | createProduct(product: ProductInput): Product
928 | }
929 |
930 | type Query {
931 | hello: String,
932 | products: [Product],
933 | product(id: ID!): Product
934 | }
935 | ", _ =>
936 | {
937 | _.Types.Include();
938 | _.Types.Include();
939 | });
940 | return schema;
941 | }
942 | }
943 | }
944 | ```
945 |
946 | Above we added the type `Mutation` and in there we have added the `createProduct`, that takes a parameter `product` of type `ProductInput`. We can also see that we need to return something of type `Product`. `ProductInput` is of type `input` and is something we only use with mutations. We've also added this row ` _.Types.Include();` to support our `Mutation` class that we are about to create next.
947 |
948 |
949 | Ok, we got the contract explained to us, let's create the file `Mutation.cs` and give it the following content:
950 |
951 | ```csharp
952 | // Mutation
953 |
954 | using GraphQL.Types;
955 | using GraphQL;
956 | using System.Linq;
957 | using System.Collections.Generic;
958 |
959 | namespace app
960 | {
961 | public class Mutation
962 | {
963 | [GraphQLMetadata("createProduct")]
964 | public Product CreateProduct(Product product)
965 | {
966 | product.Id = Data.Products.Count() + 1;
967 | Data.Products.Add(product);
968 | return product;
969 | }
970 | }
971 | }
972 | ```
973 |
974 | Above we have added the method `CreateProduct()`. We have also added the `Mutation` property of our object that we end up exporting.
975 |
976 | To test this mutation, change `Program.cs` to the following:
977 |
978 | ```csharp
979 | using System;
980 | using GraphQL;
981 | using GraphQL.Types;
982 |
983 | namespace app
984 | {
985 | class Program
986 | {
987 | static void Main(string[] args)
988 | {
989 | var schema = SchemaFactory.Create();
990 | var json = schema.Execute(_ =>
991 | {
992 | _.Query = "mutation { createProduct(product: { name: \"Captain America - the first Avenger\" }) { name } }";
993 | });
994 |
995 | Console.WriteLine(json);
996 | }
997 | }
998 | }
999 | ```
1000 |
1001 | NOTE, how our query now says the following:
1002 |
1003 | ```csharp
1004 | _.Query = "mutation { createProduct(product: { name: \"Captain America - the first Avenger\" }) { name } }"
1005 | ```
1006 |
1007 | Above we use the keyword `mutation`. They keyword `mutation` optionally takes an argument if you want to have named mutation. If you want many of the them you need to name them. That's beyond the scope of this workshop.
1008 |
1009 | ### Part II - Adding reviews, supporting nested queries
1010 |
1011 | Now we will extend our schema with the custom type `Review` and also support nested queries.
1012 |
1013 | To accomplish this we need to do the following:
1014 |
1015 | 1. Add a custom type definition to `Review` to `Schema.cs`
1016 | 2. Add in memory data for reviews in `Data.cs`
1017 | 3. Add resolver function to `Query.cs`
1018 | 4. Try it out
1019 |
1020 | **Add custom type definition**
1021 |
1022 | The definition of `Review` should look like this:
1023 |
1024 | ```
1025 | type Review {
1026 | grade: Int,
1027 | title: String,
1028 | description: String,
1029 | product: Product
1030 | }
1031 | ```
1032 |
1033 | our `Schema.cs` should now look like this:
1034 |
1035 | ```csharp
1036 | using GraphQL.Types;
1037 | using GraphQL;
1038 |
1039 | namespace app
1040 | {
1041 | public class SchemaFactory
1042 | {
1043 | public static ISchema Create()
1044 | {
1045 | var schema = Schema.For(@"
1046 | type Product {
1047 | id: ID,
1048 | name: String
1049 | }
1050 |
1051 | type Review {
1052 | grade: Int,
1053 | title: String,
1054 | description: String,
1055 | product: Product
1056 | }
1057 |
1058 | input ProductInput {
1059 | name: String
1060 | }
1061 |
1062 | type Mutation {
1063 | createProduct(product: ProductInput): Product
1064 | }
1065 |
1066 | type Query {
1067 | hello: String,
1068 | products: [Product],
1069 | product(id: ID!): Product,
1070 | reviews: [Review]
1071 | }
1072 | ", _ =>
1073 | {
1074 | _.Types.Include();
1075 | _.Types.Include();
1076 | });
1077 | return schema;
1078 | }
1079 | }
1080 | }
1081 | ```
1082 |
1083 | **Add in memory data**
1084 |
1085 | Ensure `Data.cs` now looks like this:
1086 |
1087 | ```csharp
1088 | // Data.cs
1089 |
1090 | using System.Collections.Generic;
1091 |
1092 | namespace app
1093 | {
1094 |
1095 | public class Product
1096 | {
1097 | public int Id { get; set; }
1098 | public string Name { get; set; }
1099 | }
1100 |
1101 | public class Review
1102 | {
1103 | public int Grade { get; set; }
1104 | public string Title { get; set; }
1105 | public string Description { get; set; }
1106 | public int Product { get; set; }
1107 | }
1108 | public class Data
1109 | {
1110 | public static List Products = new List()
1111 | {
1112 | new Product()
1113 | {
1114 | Id = 1,
1115 | Name = "Avengers - End Game"
1116 | }
1117 | };
1118 |
1119 | public static List Reviews = new List()
1120 | {
1121 | new Review()
1122 | {
1123 | Grade = 5,
1124 | Title = "Great movie",
1125 | Description = "Great actor playing Thanos",
1126 | Product = 1
1127 | }
1128 | }
1129 | }
1130 | }
1131 | ```
1132 |
1133 | NOTE, we moved `Product` class into `Data.cs` and removed it from `Query.cs`.
1134 |
1135 | Let's now add a method `GetReviews()` to our `Query.cs` class like so:
1136 |
1137 | ```csharp
1138 | [GraphQLMetadata("reviews")]
1139 | public Product GetReviews()
1140 | {
1141 | return Data.Reviews;
1142 | }
1143 | ```
1144 |
1145 | `Query.cs` should look like this, at this point:
1146 |
1147 | ```csharp
1148 | // Query.cs
1149 |
1150 | using GraphQL.Types;
1151 | using GraphQL;
1152 | using System.Linq;
1153 | using System.Collections.Generic;
1154 |
1155 | namespace app
1156 | {
1157 | public class Query
1158 | {
1159 | [GraphQLMetadata("hello")]
1160 | public string GetHello()
1161 | {
1162 | return "World";
1163 | }
1164 |
1165 | [GraphQLMetadata("products")]
1166 | public List GetProducts()
1167 | {
1168 | return Data.Products;
1169 | }
1170 |
1171 | [GraphQLMetadata("product")]
1172 | public Product GetProductById(int id)
1173 | {
1174 | return Data.Products.SingleOrDefault( p => p.Id == id );
1175 | }
1176 |
1177 | [GraphQLMetadata("reviews")]
1178 | public Product GetReviews()
1179 | {
1180 | return Data.Reviews;
1181 | }
1182 | }
1183 | }
1184 | ```
1185 |
1186 | **Add nested resolver function**
1187 |
1188 | We are about to support a query like this:
1189 |
1190 | ```
1191 | {
1192 | reviews {
1193 | product { name }
1194 | }
1195 | }
1196 | ```
1197 |
1198 | For that to be possible we need a function with the capability to turn an integer into a `Product` object. Why am I saying that? Look at the review data one more time:
1199 |
1200 | ```csharp
1201 | new Review()
1202 | {
1203 | Grade = 5,
1204 | Title = "Great movie",
1205 | Description = "Great actor playing Thanos",
1206 | Product = 1
1207 | }
1208 | ```
1209 |
1210 | The `Product` field is pointing to `1` not a `Product` object. How to fix? Well, GraphQL will try to resolve this for us providing we give it a provider of Type `Review` that is a able to solve our `Product` field. The Provider should look something like this:
1211 |
1212 | ```csharp
1213 | [GraphQLMetadata("Review", IsTypeOf = typeof(Review))]
1214 | public class ReviewResolver
1215 | {
1216 | public string Title(Review review) => review.Title;
1217 | public string Description(Review review) => review.Description;
1218 | public int Grade(Review review) => review.Grade;
1219 | public Product Product(ResolveFieldContext context, Review review)
1220 | {
1221 | return Data.Products.SingleOrDefault(p => p.Id == review.Product);
1222 | }
1223 | }
1224 | ```
1225 |
1226 | What we are saying above is that simple fields simply are resolved like this:
1227 |
1228 | ```csharp
1229 | public string Title(Review review) => review.Title;
1230 | ```
1231 |
1232 | whereas fields that are integers, that we know point to real objects, according to our schema, will be resolved by methods instead like the `Product()` method that actively looks up our `1` and turns it into a `Product`.
1233 |
1234 | Our `Query.cs` should now look like this:
1235 |
1236 | ```csharp
1237 | using GraphQL.Types;
1238 | using GraphQL;
1239 | using System.Linq;
1240 | using System.Collections.Generic;
1241 |
1242 | namespace app
1243 | {
1244 | public class Query
1245 | {
1246 | [GraphQLMetadata("hello")]
1247 | public string GetHello()
1248 | {
1249 | return "World";
1250 | }
1251 |
1252 | [GraphQLMetadata("products")]
1253 | public List GetProducts()
1254 | {
1255 | return Data.Products;
1256 | }
1257 |
1258 | [GraphQLMetadata("product")]
1259 | public Product GetProductById(int id)
1260 | {
1261 | return Data.Products.SingleOrDefault( p => p.Id == id );
1262 | }
1263 |
1264 | [GraphQLMetadata("reviews")]
1265 | public List GetReviews()
1266 | {
1267 | return Data.Reviews;
1268 | }
1269 | }
1270 |
1271 | [GraphQLMetadata("Review", IsTypeOf = typeof(Review))]
1272 | public class ReviewResolver
1273 | {
1274 | public string Title(Review review) => review.Title;
1275 | public string Description(Review review) => review.Description;
1276 | public int Grade(Review review) => review.Grade;
1277 | public Product Product(ResolveFieldContext context, Review review)
1278 | {
1279 | return Data.Products.SingleOrDefault(p => p.Id == review.Product);
1280 | }
1281 | }
1282 | }
1283 | ```
1284 |
1285 | Don't forget to add this line `_.Types.Include();` to `Schema.cs` so that file now looks like this:
1286 |
1287 | ```csharp
1288 | // Schema.cs
1289 |
1290 | using GraphQL.Types;
1291 | using GraphQL;
1292 |
1293 | namespace app
1294 | {
1295 | public class SchemaFactory
1296 | {
1297 | public static ISchema Create()
1298 | {
1299 | var schema = Schema.For(@"
1300 | type Product {
1301 | id: ID,
1302 | name: String
1303 | }
1304 |
1305 | type Review {
1306 | grade: Int,
1307 | title: String,
1308 | description: String,
1309 | product: Product
1310 | }
1311 |
1312 | input ProductInput {
1313 | name: String
1314 | }
1315 |
1316 | type Mutation {
1317 | createProduct(product: ProductInput): Product
1318 | }
1319 |
1320 | type Query {
1321 | hello: String,
1322 | products: [Product],
1323 | product(id: ID!): Product,
1324 | reviews: [Review]
1325 | }
1326 | ", _ =>
1327 | {
1328 | _.Types.Include();
1329 | _.Types.Include();
1330 | _.Types.Include();
1331 | });
1332 | return schema;
1333 | }
1334 | }
1335 | }
1336 | ```
1337 |
1338 | Finally let's try this out by altering `Program.cs` to:
1339 |
1340 | ```csharp
1341 | // Program.cs
1342 |
1343 | using System;
1344 | using GraphQL;
1345 | using GraphQL.Types;
1346 |
1347 | namespace app
1348 | {
1349 | class Program
1350 | {
1351 | static void Main(string[] args)
1352 | {
1353 | var schema = SchemaFactory.Create();
1354 | var json = schema.Execute(_ =>
1355 | {
1356 | _.Query = "{ reviews { product { name } } }";
1357 | });
1358 |
1359 | Console.WriteLine(json);
1360 | }
1361 | }
1362 | }
1363 | ```
1364 |
1365 | NOTE, how this line `_.Query= "{ reviews { product { name } } }"` queries in a nested way and forces our code to resolve our `product` property. Executing this with `dotnet run` should give you the following output:
1366 |
1367 | ```json
1368 | {
1369 | "data": {
1370 | "reviews": [
1371 | {
1372 | "product": {
1373 | "name": "Avengers - End Game"
1374 | }
1375 | }
1376 | ]
1377 | }
1378 | }
1379 | ```
1380 |
1381 | ## Solution
1382 |
1383 | [SOLUTION intro](https://github.com/softchris/graphql-workshop-dotnet/tree/master/part2/dotnet/part1/app)
1384 |
1385 | [SOLUTION workshop part 2](https://github.com/softchris/graphql-workshop-dotnet/tree/master/part2/dotnet/part2/app)
1386 |
--------------------------------------------------------------------------------