├── .editorconfig ├── .gitignore ├── CONTRIBUTING ├── LICENSE ├── README.md ├── docs ├── .vuepress │ ├── config.js │ ├── public │ │ ├── Bit_Learning.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── site.webmanifest │ │ └── workshop-apollo-web.png │ └── styles │ │ └── index.styl ├── README.md ├── pt-br │ ├── CODE_OF_CONDUCT.md │ ├── README.md │ └── workshop │ │ ├── 1.md │ │ ├── 2.md │ │ ├── 3.md │ │ ├── 4.md │ │ ├── 5.md │ │ └── README.md └── workshop │ ├── 1.md │ ├── 2.md │ ├── 3.md │ ├── 4.md │ ├── 5.md │ ├── 6.md │ ├── CODE_OF_CONDUCT.md │ └── README.md ├── package-lock.json ├── package.json ├── part1 ├── app.js ├── package-lock.json └── package.json ├── part2 └── dotnet │ ├── part1 │ └── app │ │ ├── Program.cs │ │ ├── Query.cs │ │ └── app.csproj │ └── part2 │ └── app │ ├── Data.cs │ ├── Mutation.cs │ ├── Program.cs │ ├── Query.cs │ ├── Schema.cs │ └── app.csproj ├── part3 ├── docker-compose.yaml ├── products │ ├── Controllers │ │ ├── DefaultController.cs │ │ └── WeatherForecastController.cs │ ├── Dockerfile │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── WeatherForecast.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── products.csproj └── reviews │ ├── Controllers │ ├── ReviewsController.cs │ └── WeatherForecastController.cs │ ├── Dockerfile │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── WeatherForecast.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── reviews.csproj ├── part4 ├── .funcignore ├── .gitignore ├── GraphQL │ ├── API │ │ ├── app.js │ │ ├── resolvers.js │ │ └── schema.js │ ├── function.json │ ├── index.js │ └── sample.dat ├── host.json ├── package-lock.json ├── package.json ├── proxies.json └── solution │ ├── .gitignore │ ├── API │ ├── Data.cs │ ├── Mutation.cs │ ├── Query.cs │ └── Schema.cs │ ├── GraphQL.cs │ ├── host.json │ └── solution.csproj └── part5 ├── .funcignore ├── .gitignore ├── host.json ├── package-lock.json ├── package.json ├── proxies.json └── solution ├── .gitignore ├── API ├── Data.cs ├── Mutation.cs ├── Query.cs └── Schema.cs ├── GraphQL.cs ├── host.json └── solution.csproj /.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 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | .DS_Store 4 | bin 5 | obj 6 | .vscode -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Workshop - .NET Core & GraphQL 2 | 3 | [![Screen-Shot-07-23-20-at-11-38-PM.png](https://i.postimg.cc/y6cmmZ7M/Screen-Shot-07-23-20-at-11-38-PM.png)](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 | -------------------------------------------------------------------------------- /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/.vuepress/public/Bit_Learning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/f076dda48618d4118b4d3fda3870f8edf44caaf4/docs/.vuepress/public/Bit_Learning.png -------------------------------------------------------------------------------- /docs/.vuepress/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/f076dda48618d4118b4d3fda3870f8edf44caaf4/docs/.vuepress/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/.vuepress/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/f076dda48618d4118b4d3fda3870f8edf44caaf4/docs/.vuepress/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/f076dda48618d4118b4d3fda3870f8edf44caaf4/docs/.vuepress/public/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/f076dda48618d4118b4d3fda3870f8edf44caaf4/docs/.vuepress/public/favicon-16x16.png -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/f076dda48618d4118b4d3fda3870f8edf44caaf4/docs/.vuepress/public/favicon-32x32.png -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/f076dda48618d4118b4d3fda3870f8edf44caaf4/docs/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/.vuepress/public/workshop-apollo-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softchris/graphql-workshop-dotnet/f076dda48618d4118b4d3fda3870f8edf44caaf4/docs/.vuepress/public/workshop-apollo-web.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --- -------------------------------------------------------------------------------- /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/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 | --- -------------------------------------------------------------------------------- /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/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/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 | ![](https://res.cloudinary.com/practicaldev/image/fetch/s--6RVfGE9v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/psz5ihp5kjyujpvq1qrq.png) 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 | ![](https://cdn-images-1.medium.com/max/1024/1%2AyCAYfl2xSaU1XMs4nox__g.png) 72 | 73 | **Segundo:** será solicitada a linguagem de programação que você deseja para o projeto. Selecione `C#` 74 | 75 | ![](https://cdn-images-1.medium.com/max/1024/1%2Ao0DqKyhGpjPYG6Owd_hAQQ.png) 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 | ![](https://cdn-images-1.medium.com/max/1024/1%2AtOj9evR2VzUHN3769auafg.png) 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 | ![](https://cdn-images-1.medium.com/max/1024/1%2AByRKpClyIg18VocVthLlIw.png) 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 | ![](https://cdn-images-1.medium.com/max/1024/1%2AwE6QFcXxhHWn928Tf90gPQ.png) 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/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 | ![](https://thepracticaldev.s3.amazonaws.com/i/m3u7ox70e1hnfcvfl269.png) 114 | 115 | Em seguida, selecione o modelo correto digitando `Web App for Containers`: 116 | 117 | ![](https://thepracticaldev.s3.amazonaws.com/i/tbho3zfew4auk5789dot.png) 118 | 119 | Depois, preencha alguns campos obrigatórios: 120 | 121 | ![](https://thepracticaldev.s3.amazonaws.com/i/hv4tjrud8dhybgav21ld.png) 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 | ![](https://thepracticaldev.s3.amazonaws.com/i/0485qfd86jawp4c9blx3.png) 213 | 214 | Clique no símbolo implantar 215 | 216 | ![](https://thepracticaldev.s3.amazonaws.com/i/p07g5173lrpvuwuusvi0.png) 217 | 218 | Selecione `Create a new Function App` 219 | 220 | ![](https://thepracticaldev.s3.amazonaws.com/i/feovtte58st3x1l1byez.png) 221 | 222 | Isso deve começar a mostrar algo como isto: 223 | 224 | ![](https://thepracticaldev.s3.amazonaws.com/i/sx350ke8d6u820s8kycn.png) 225 | 226 | Quando terminar, você verá algo como isto: 227 | 228 | ![](https://thepracticaldev.s3.amazonaws.com/i/7nis5z0xk4fcyv2y6dnm.png) 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 | ![](https://thepracticaldev.s3.amazonaws.com/i/yw4d7m5d02nneh1a4ro3.png) 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/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 | [![grapql-image.png](https://i.postimg.cc/pV7jjzcH/grapql-image.png)](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/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/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 | -------------------------------------------------------------------------------- /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/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 | ![](https://res.cloudinary.com/practicaldev/image/fetch/s--6RVfGE9v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/psz5ihp5kjyujpvq1qrq.png) 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 | ![](https://cdn-images-1.medium.com/max/1024/1%2AyCAYfl2xSaU1XMs4nox__g.png) 73 | 74 | **Secondly**, you are asked for what programming language you want the project to be, select `C#` 75 | 76 | ![](https://cdn-images-1.medium.com/max/1024/1%2Ao0DqKyhGpjPYG6Owd_hAQQ.png) 77 | 78 | **Thirdly**, you are asked for what template to use for your first function in your Azure Functions project. Select `HttpTrigger` 79 | 80 | ![](https://cdn-images-1.medium.com/max/1024/1%2AtOj9evR2VzUHN3769auafg.png) 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 | ![](https://cdn-images-1.medium.com/max/1024/1%2AByRKpClyIg18VocVthLlIw.png) 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 | ![](https://cdn-images-1.medium.com/max/1024/1%2AwE6QFcXxhHWn928Tf90gPQ.png) 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/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 | ![](https://thepracticaldev.s3.amazonaws.com/i/m3u7ox70e1hnfcvfl269.png) 116 | 117 | Next select the correct template by typing `Web App for Containers`: 118 | 119 | ![](https://thepracticaldev.s3.amazonaws.com/i/tbho3zfew4auk5789dot.png) 120 | 121 | Thereafter fill in some mandatory fields: 122 | 123 | ![](https://thepracticaldev.s3.amazonaws.com/i/hv4tjrud8dhybgav21ld.png) 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 | ![](https://thepracticaldev.s3.amazonaws.com/i/0485qfd86jawp4c9blx3.png) 215 | 216 | Click the deploy symbol 217 | 218 | ![](https://thepracticaldev.s3.amazonaws.com/i/p07g5173lrpvuwuusvi0.png) 219 | 220 | Select to `Create a new Function App` 221 | 222 | ![](https://thepracticaldev.s3.amazonaws.com/i/feovtte58st3x1l1byez.png) 223 | 224 | It should start showing something like this: 225 | 226 | ![](https://thepracticaldev.s3.amazonaws.com/i/sx350ke8d6u820s8kycn.png) 227 | 228 | When it's done it should say something like this: 229 | 230 | ![](https://thepracticaldev.s3.amazonaws.com/i/7nis5z0xk4fcyv2y6dnm.png) 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 | ![](https://thepracticaldev.s3.amazonaws.com/i/yw4d7m5d02nneh1a4ro3.png) 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/workshop/6.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 🍻 Conclusion and Challenges 3 | --- 4 | 5 | # 🍻 Conclusion and Challenges 6 | 7 | ## What we will build 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | [![grapql-image.png](https://i.postimg.cc/pV7jjzcH/grapql-image.png)](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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | }); -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /part2/dotnet/part2/app/app.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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: -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /part3/products/products.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/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"] -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /part3/reviews/reviews.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /part4/.funcignore: -------------------------------------------------------------------------------- 1 | *.js.map 2 | *.ts 3 | .git* 4 | .vscode 5 | local.settings.json 6 | test 7 | tsconfig.json -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /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/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; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /part4/GraphQL/sample.dat: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Azure" 3 | } -------------------------------------------------------------------------------- /part4/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /part4/proxies.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/proxies", 3 | "proxies": {} 4 | } 5 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/.funcignore: -------------------------------------------------------------------------------- 1 | *.js.map 2 | *.ts 3 | .git* 4 | .vscode 5 | local.settings.json 6 | test 7 | tsconfig.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 -------------------------------------------------------------------------------- /part5/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /part5/proxies.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/proxies", 3 | "proxies": {} 4 | } 5 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------