A esta altura, espero que você tenha tido a chance de ver o meu tutorial anterior intitulado, Uso do GraphQL com Golang e um banco de dados NoSQLque é mais ou menos um início rápido para usar o GraphQL com Couchbase. Lá, vimos como criar um esquema GraphQL que permitia consultas somente leitura e mutações nos dados. No entanto, as consultas não foram projetadas de forma eficiente em torno de possíveis relacionamentos de dados.
Veremos como lidar e definir relacionamentos entre modelos de dados usando GraphQLCouchbase e a linguagem de programação Go.
No exemplo anterior, criamos dois objetos GraphQL relacionados a contas e blogs que se pareciam com o seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
accountType := graphql.Novo objeto(graphql.ObjectConfig{ Nome: "Conta", Campos: graphql.Campos{ "id": &graphql.Campo{ Tipo: graphql.Cordas, }, "firstname": &graphql.Campo{ Tipo: graphql.Cordas, }, "lastname" (sobrenome): &graphql.Campo{ Tipo: graphql.Cordas, }, }, }) blogType := graphql.Novo objeto(graphql.ObjectConfig{ Nome: "Blog", Campos: graphql.Campos{ "id": &graphql.Campo{ Tipo: graphql.Cordas, }, "account" (conta): &graphql.Campo{ Tipo: graphql.Cordas, }, "título": &graphql.Campo{ Tipo: graphql.Cordas, }, "content" (conteúdo): &graphql.Campo{ Tipo: graphql.Cordas, }, }, }) |
No exemplo acima, presumimos que o conta
no campo blogType
era uma chave para o objeto accountType
objeto. Ao fazer isso, conseguimos executar consultas como as seguintes:
1 2 3 4 5 6 7 8 9 10 |
{ conta(id: "12345") { primeiro nome, sobrenome } blogs(conta: "12345") { título, conteúdo } } |
A abordagem acima funciona, mas temos que continuar passando variáveis em nosso Consulta GraphQL.
Muito bem, o esquema não tem nada a ver com a consulta acima. O objeto de consulta é responsável pelo desempenho da consulta, não o esquema em si.
Lembre-se de que definimos consultas que utilizaram o Couchbase da seguinte forma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
"contas": &graphql.Campo{ Tipo: graphql.Nova lista(accountType), Resolver: func(p graphql.ResolveParams) (interface{}, erro) { consulta := gocb.NewN1qlQuery("SELECT META(account).id, account.* FROM example AS account WHERE account.type = 'account'") linhas, erro := balde.ExecutarN1qlQuery(consulta, nulo) se erro != nulo { retorno nulo, erro } var contas []Conta var fila Conta para linhas.Próximo(&fila) { contas = anexar(contas, fila) } retorno contas, nulo }, }, |
Onde quero chegar com isso?
Em vez de criar consultas para cada interação que desejamos realizar, é mais fácil incorporá-las ao modelo GraphQL. Em outras palavras, em vez de fazer referência a modelos por uma chave de cadeia de caracteres, por que não fazer referência a outro modelo?
Faça a seguinte modificação no blogType
objeto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
blogType := graphql.Novo objeto(graphql.ObjectConfig{ Nome: "Blog", Campos: graphql.Campos{ "id": &graphql.Campo{ Tipo: graphql.Cordas, }, "account" (conta): &graphql.Campo{ Tipo: accountType, Resolver: func(parâmetros graphql.ResolveParams) (interface{}, erro) { blog := parâmetros.Fonte.(Blog) var conta Conta conta.ID = blog.Conta _, erro := balde.Obter(blog.Conta, &conta) se erro != nulo { retorno nulo, erro } retorno conta, nulo }, }, "título": &graphql.Campo{ Tipo: graphql.Cordas, }, "content" (conteúdo): &graphql.Campo{ Tipo: graphql.Cordas, }, }, }) |
Ainda estamos definindo cada um de nossos possíveis campos de API, mas também estamos definindo um Resolver
para a função conta
campo. Dentro do Resolver
podemos obter os dados do objeto pai, sendo a função blogType
e usá-lo para consultar os dados da conta quando solicitado.
A consulta GraphQL que vimos anteriormente pode se tornar a seguinte:
1 2 3 4 5 6 7 8 9 10 |
{ blog(conta: "12345") { conta { primeiro nome, sobrenome }, título, conteúdo } } |
A consulta acima pode não ter exatamente o mesmo significado que a anterior, mas, neste exemplo, ela tem. Estamos dizendo que queremos obter um blog específico por conta. Em vez de fornecer o ID da conta, podemos simplesmente obter as informações por meio da consulta Resolver
que havíamos criado.
Essa abordagem torna as consultas desnecessárias? Não, não torna, porque pode haver necessidade de fazer consultas de diferentes maneiras. No entanto, ela nos permitiu fazer um tipo de JUNTAR
com o GraphQL.
O Couchbase tem N1QL, portanto, um JUNTAR
Seria melhor que a operação do GraphQL fosse feita por meio do banco de dados e não no backend? Quero dizer, mesmo que tenhamos criado relacionamentos NoSQL com o GraphQL, ainda estamos fazendo várias solicitações ao banco de dados. Eu diria que seria melhor para o GraphQL fazer o JOIN no nível do banco de dados, não no nível do aplicativo. Quanto menos solicitações, mais rápido será o aplicativo, certo?
Vamos modificar o blogs
em nossa consulta rootQuery
objeto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
"blogs": &graphql.Campo{ Tipo: graphql.Nova lista(blogType), Resolver: func(parâmetros graphql.ResolveParams) (interface{}, erro) { consulta := gocb.NewN1qlQuery(` SELECIONAR META(blog).id, blog.título, blog.conteúdo, { META(conta).id, conta.primeiro nome, conta.sobrenome } AS conta DE exemplo AS blog JUNTAR exemplo AS conta ON CHAVES blog.conta ONDE blog.tipo = 'blog' `) linhas, erro := balde.ExecutarN1qlQuery(consulta, nulo) se erro != nulo { retorno nulo, erro } var blogs []Blog var fila Blog para linhas.Próximo(&fila) { blogs = anexar(blogs, fila) } retorno blogs, nulo }, }, |
Em vez de adicionar um Resolver
para a função contas
no campo blogType
estamos fazendo tudo isso por meio da consulta. Nosso blogType
seria parecido com o seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
blogType := graphql.Novo objeto(graphql.ObjectConfig{ Nome: "Blog", Campos: graphql.Campos{ "id": &graphql.Campo{ Tipo: graphql.Cordas, }, "account" (conta): &graphql.Campo{ Tipo: accountType, }, "título": &graphql.Campo{ Tipo: graphql.Cordas, }, "content" (conteúdo): &graphql.Campo{ Tipo: graphql.Cordas, }, }, }) |
Se quiséssemos escrever uma consulta para isso, poderíamos fazer isso facilmente, como a seguir:
1 2 3 4 5 6 7 8 9 10 |
{ blogs { título, conteúdo, conta { primeiro nome, sobrenome } } } |
A consulta acima retornaria todos os blogs e os dados da conta associados a eles. Em vez de fazer uma consulta para cada campo, estamos fazendo isso em uma única solicitação com o N1QL.
O Couchbase N1QL torna essa abordagem mais plausível do que um banco de dados relacional, pois o esquema flexível pode ser facilmente modelado com o GraphQL.
Conclusão
Você acabou de ver como trabalhar com tipos de dados relacionados em um aplicativo Golang que usa GraphQL e o banco de dados NoSQL, Couchbase. Com as relações GraphQL, você pode criar Resolver
para cada campo do modelo ou você pode criar consultas mais complicadas com o N1QL e deixar que o banco de dados faça o trabalho pesado. Em ambos os cenários, o usuário pode solicitar exatamente o que deseja sem ter que se preocupar com uma quantidade potencialmente excessiva de endpoints de API.
Eu o encorajo a dar uma olhada no meu exemplo anterior pois ele é mais aprofundado no que diz respeito ao GraphQL com tipos de dados Golang e Couchbase. Você pode até mesmo ir além e ver outro exemplo que escrevi intitulado, Primeiros passos com o GraphQL usando Golang.
Para saber mais sobre relacionamentos de dados e sobre como usar o Couchbase com Go, confira o artigo Portal do desenvolvedor do Couchbase.