GraphQL o que, como e quando QCon SP 2018
Introdução pessoal Problemas de APIs REST Soluções GraphQL Caso: GitHub Caso: Cartão Elo Agenda
- Desenvolvedor desde os 9 anos - Web desde 1998 (Perl, CGI ) - UNICAMP 2001-2005 Gustavo Sverzut Barbieri Engenheiro de Computação ProFUSION - Serviços de desenvolvimento de sw - Apaixonado por eficiência - Sistemas embarcados e IoT - Sistemas Complexos - P&D
Problemas REST Por que criar o GraphQL?
REST: Representational State Transfer - Modelo de Arquitetura - Vários pontos de acesso (URL), um por recurso - Cada operação (GET, POST, PUT ) retorna um conjunto de dados fixo - Adicionar ou remover dados ou parâmetros quebra a API (nova versão)
REST: ilustrado Dado portador CPF 12345678900, pegue os 4 últimos dígitos (last4) e o BIN do primeiro token do primeiro cartão. Card Holder Card Card Token GET /v1/card-holder/12345678900 {, "name": "José da Silva", "cards": [ "xyz", "xpto", ], GET /v1/card/xyz {, "last4": "1234", "bin": "5035", "cardholder": "1234", "tokens": ["abc", "def", ], GET /v1/card-token/abc {, "last4": "1234", "bin": "6046", "card": "xyz", "cardholder": "1234",
REST: ilustrado Dado portador CPF 12345678900, pegue os 4 últimos dígitos (last4) e o BIN do primeiro token do primeiro cartão. Card Holder Card Card Token GET /v1/card-holder/12345678900 {, "name": "José da Silva", "cards": [ "xyz", "xpto", ], GET /v1/card/xyz {, "last4": "1234", "bin": "5035", "cardholder": "1234", "tokens": ["abc", "def", ], GET /v1/card-token/abc {, "last4": "1234", "bin": "6046", "card": "xyz", "cardholder": "1234", estado transferido (Portador) estado transferido (Cartão) estado transferido (Token)
REST: ilustrado Dado portador CPF 12345678900, pegue os 4 últimos dígitos (last4) e o BIN do primeiro token do primeiro cartão. Card Holder Card Card Token GET /v1/card-holder/12345678900 {, "name": "José da Silva", "cards": [ "xyz", "xpto", ], dados inúteis! GET /v1/card/xyz {, "last4": "1234", "bin": "5035", "cardholder": "1234", "tokens": ["abc", "def", ], GET /v1/card-token/abc {, "last4": "1234", "bin": "6046", "card": "xyz", "cardholder": "1234",
REST: problemas - API bem normalizada resulta em muitas conexões HTTP; - Tráfego de dados inúteis ao aplicativo; - Endpoints otimizados para aplicativos - Mudanças de requisitos no front-end costumam precisar de adaptações no backend; - Falta Documentação; - Falta Validação e Garantias; - Falta ambiente de testes/playground.
REST: resolvendo problemas de ambiente - Documentação via Swagger - Validação com JSON Schema - Testes com Postman ou curl
REST: resolvendo problemas de execução Extensões: - Parâmetros para controlar campos a retornar; - Parâmetros para controlar paginação, ordenação...; - Desnormalização para reduzir número de consultas; mas são específicas de fornecedores! GET /v2/card/xyz?fields=last4,bin,tokens {, "last4": "1234", "bin": "5035", "cardholder": "1234", "tokens": ["abc", "def", ], GET /v2/card/xyz? fields=last4,bin,tokens.limit(1) { "last4": "1234", "bin": "5035", "tokens": ["abc", "def", ], GET /v2/card-holder/1234? fields=name,cards.limit(1){last4,bin { "name": "José da Silva", "cards": [{"last4": "1234, "bin": "5035"], Facebook Graph API
REST: problemas com extensões GET /v2/card-holder/1234? fields=name,cards.limit(1){last4,bin - Linguagem de domínio específica (DSL) - Validação - Documentação
GraphQL Resolvendo problemas REST
GraphQL - Linguagem de Consulta de Grafos - Nós: dados - Arestas: relacionamentos - Não é: - Protocolo de Rede - Descrição de Banco de Dados - Descrição de Classes em OOP - Origem: Facebook após tratar diversos problemas com REST ID Name Card Holder ID City cursor Card cursor Token Addr. Zip
GraphQL - Declaração de tipos e consultas via Schema # Portador de cartão type CardHolder implements Node { # Identificador Global Único id: ID! # Nome completo do portador name: String - Tipagem forte - Sempre verificado - Documentação embutida no schema, com consulta/introspecção - Interfaces # Cartões em posse cards( # Limita a lista às primeiras entradas first: Int, # Inicia após o cursor opaco after: String, # outros argumentos ): CardsConnection type Query { node(id: ID!): Node cardholders( ): CardHoldersConnection
GraphQL comentários são armazenados como descrição, convenção por formatação Markdown # Portador de cartão type CardHolder implements Node { # Identificador Global Único id: ID! # Nome completo do portador name: String declaração de Objetos e interfaces: validação e documentação! Todos os campos também são consultas, com tipos de retorno Todas consultas podem ter argumentos, documentados e com tipos # Cartões em posse cards( # Limita a lista às primeiras entradas first: Int, # Inicia após o cursor opaco after: String, # outros argumentos ): CardsConnection type Query { node(id: ID!): Node cardholders( ): CardHoldersConnection
GraphQL - Consultas aninhadas - Linguagem de consultas bem definida - Argumentos - Variáveis - Resultados espelham estrutura da consulta - Fragmentos de consulta query Nome($holderId: node(id: $holderid)... on CardHolder name cards(first: 1) edges { node { last4 bin ID!) { { { {"data": { "node": { { "name": "João da Silva", "cards": { "edges": [ "node": { "last4": "1234", "bin": "5035" ]
GraphQL operação: query variáveis query Nome($holderId: node(id: $holderid)... on CardHolder name cards(first: 1) edges { node { last4 bin ID!) { { { {"data": { "node": { { "name": "João da Silva", "cards": { "edges": [ "node": { "last4": "1234", "bin": "5035" ]
GraphQL resultado espelhado consulta raiz consultas aninhadas query Nome($holderId: node(id: $holderid)... on CardHolder name cards(first: 1) edges { node { last4 bin ID!) { { { {"data": { "node": { { argumentos "name": "João da Silva", "cards": { "edges": [ "node": { "last4": "1234", "bin": "5035" ]
GraphQL fragmentos: seleção de tipo ("Cast") query Nome($holderId: node(id: $holderid)... on CardHolder name cards(first: 1) edges { node { last4 bin ID!) { { { {"data": { "node": { { "name": "João da Silva", "cards": { "edges": [ "node": { "last4": "1234", "bin": "5035" ]
GraphQL: múltiplas consultas raiz - Consultas executadas em paralelo - Apelidos para diferenciar consultas com argumentos diferentes query { n0: node(id: "id0") {... on CardInterface { last4 holder { name n1: node(id: "id0") {... on CardInterface { last4 holder { name bin(number: "509069") { issuer { name {"data": { "n0": { "last4": "1234", "holder": { "João...", "n1": { "last4": "2468", "holder": { "José...", "bin": { "issuer": { "name": "Banco...",
GraphQL: múltiplas consultas raiz apelidos renomeiam retorno em execução em paralelo em query { n0: node(id: "id0") {... on CardInterface { last4 paralelo holder { name n1: node(id: "id1") {... on CardInterface { last4 paralelo holder { name bin(number: "509069") { issuer { name {"data": { "n0": { "last4": "1234", "holder": { "João...", "n1": { "last4": "2468", "holder": { "José...", "bin": { "issuer": { "name": "Banco...",
GraphQL: mutações - Mutações alteram e retornam o estado - Executadas em série - Consulta de retorno em paralelo mutation { a0: associatepspmerchant( ) { pspid legalid a1: associatepspmerchant( ) { pspid legalid {"data": { "a0": { "pspid": "1234", "legalid": "1234...",, "a1": { "pspid": "222", "legalid": "2345...",
GraphQL: mutações executado primeiro em executado depois em mutation { a0: associatepspmerchant( ) { pspid paralelo legalid a1: associatepspmerchant( ) { paralelo pspid legalid {"data": { "a0": { "pspid": "1234", "legalid": "1234...",, "a1": { "pspid": "222", "legalid": "2345...",
GraphQL: erros - Consultas retornam: null - Até o primeiro elemento null-able - Motor sempre garante retorno correto - Listagem de erros - bin() pode retornar null - BIN { issuer é não-nulo - CardIssuer { name é não-nulo - CardIssuer { url é null-able query { a: bin(number: "invalid") { issuer { name b: bin(number: "509069") { issuer { name # server bug! c: bin(number: "509069") { issuer { url # server bug! {"data": { "a": null, "b": null, "b": { "issuer": { "url": null, "errors": [ {"message": "Bin inválido", "path": ["a"], {"message": "Campo null!", "path": ["b", "issuer", "name"] {"message": "Server Bug", "path": ["c", "issuer", "url"] ]
Relay - Relay: adiciona GraphQL a clientes React.JS; - Foco em desempenho e facilidade de uso; - Uso extensivo de fragmentos; - Convenções adicionais: - Identificação de Objetos: cache e atualização - Conexões: paginação - Mutações: previsibilidade e idempotência adotados por todos os frameworks, cliente e servidor: Apollo, Graphene... https://facebook.github.io/relay/docs/en/graphql-server-specification.html
Relay: Identificação de Objetos interface Node { id: ID! - Interface declara objeto com identificação global; - Permite cache global do aplicativo; type Query { node(id: ID!): Node - Fragmentos obtém dados, populando cache; - Relay mantém o cache e informa utilizadores sobre atualizações cache React Components
Relay: Conexões - Conexões de Nós do Grafo; - Arestas podem conter mais informações, ex: data da associação, custo - Parâmetros de paginação; - Informações de paginação; - Cursores opacos. type Query { cards( first: Int # limita elementos no retorno after: String # cursor de início last: Int # limita elementos no retorno before: String # cursor de término filter: CardFilterInput # filtros ): CardsConnection type CardsConnection { edges: [CardsEdge] pageinfo: PageInfo! # outros campos que achar conveniente totalcount: Int type CardsEdge { node: Card cursor: String! # opaco # outros campos que achar conveniente type PageInfo { haspreviouspage: Boolean! hasnextpage: Boolean! startcursor: String endcursor: String
Relay: Mutações - Assinatura com nomenclatura padrão; - clientmutationid para reconciliação e idempotência; type ActivateCardTokenInput { clientmutationid: String # opaco # outros campos que achar conveniente cardtokenid: String sensitive: String type ActivateCardTokenPayload { clientmutationid: String # opaco # outros campos que achar conveniente cardtoken: CardToken # null se não existe type Mutation { activatecardtoken( input: ActivateCardTokenInput ): ActivateCardTokenPayload cliente servidor activatecardtoken(input: {clientmutationid: "x", ) executa tenta novamente activatecardtoken(input: {clientmutationid: "x", ) detecta réplica
GraphQL: Benefícios - Schema - Linguagem de Domínio Específico (DSL) fácil e bem documentada - Tipagem forte e garantida pelo motor - Consultas de introspeção built-in ( schema, type) - Documentação faz parte do Schema - Múltiplas consultas ou mutações por requisição
GraphQL: Playground - GraphiQL https://github.com/graphql/graphiql
GraphQL: não... GraphQL não especifica: - Transporte, em geral HTTP; - Segurança, em geral TLS (HTTPS); - Serialização de dados, em geral JSON; - Autenticação e Autorização, em geral OAuth v2 via HTTP + Headers.
- Escalabilidade GitHub Caso de Sucesso API v4, Setembro de 2016 - Flexibilidade - Paginação - Tipagem - Documentação
GitHub: motivos da mudança - API REST responsável por 60% dos acessos ao DB - Resultados continham muitos dados inúteis, "*_url" para navegação - Integradores reclamavam de falta de dados úteis - 2-3 requisições para visualização completa de um recurso - Complicado manter documentação correta, com tipos e garantias
GitHub: a mudança - Primeiro objetivo: reações Emoji nos comentários - Foi necessário modelar uma grande parte do sistema: - Usuário - Repositório - Issues / Pull Requests - Comentários User Repository Issues PR - Ajuda: aliados no time de frontend (uso de React & Relay) Comments - Em produção concomitantemente com REST - Bons resultados = v4! Reactions
GitHub: o que disseram no anúncio GraphQL represents a massive leap forward for API development. Schema! Type safety, introspection, generated documentation, and predictable responses benefit both the maintainers and consumers of our platform. We re looking forward to our new era of a GraphQL-backed platform, and we hope that you do, too! https://githubengineering.com/the-github-graphql-api/
GitHub: justificativa da mudança Eficiência & Flexibilidade GitHub chose GraphQL for our API v4 because it offers significantly more flexibility for our integrators. The ability to define precisely the data you want and only the data you want is a powerful advantage over the REST API v3 endpoints. GraphQL lets you replace multiple REST requests with a single call to fetch the data you specify. https://developer.github.com/v4/#why-is-github-using-graphql
Cartão Elo Caso de Sucesso - Brasil - GraphQL desde o início - Várias unidades de negócios - Uniformidade - Experiência do Desenvolvedor Outubro de 2017 https://dev.elo.com.br/
Experiência do Desenvolvedor Design API Portal Dev Funcionamento API Gateway Backend
Cartão Elo - Moto: API First - Consumo interno e externo - Diversas Áreas de Negócios: - Cadastro de Portadores Seguros Tabela de Bins Precificação Tokenização Histórico de Transações... Experiência de Usuário: - Suporte Nacional Segurança Documentação Facilidade de Uso Extensível GraphQL!
Cartão Elo Documentação gerada via Schema Playground similar ao GraphiQL https://dev.elo.com.br/documentacao/tabela-de-bins#tabela-de-bins/consultas
Cartão Elo Address Card Token Card Card Holder BIN Card Issuer Card Network Merchant Card Brand Card Usage
o que? dados como grafo como? navegando informação quando? eficiência e facilidade
Obrigado! Perguntas? Gustavo Sverzut Barbieri <barbieri@profusion.mobi>