Table of Contents
*O autor selecionou Girls Who Code para receber uma doação como parte do programa Write for DOnations.*
Introdução
O GraphQL é um padrão de API criado pelo Facebook e de código aberto, como uma alternativa às APIs REST. Diferente das APIs REST, o GraphQL utiliza um sistema de tipos para definir sua estrutura de dados, onde todas as informações enviadas e recebidas devem estar em conformidade com um esquema pré-definido. Ele também expõe um único ponto de extremidade para todas as comunicações, em vez de várias URLs para diferentes recursos, e resolve a questão de _overfetching_ (excesso de dados retornados), retornando somente os dados solicitados pelo cliente, gerando respostas menores e mais concisas.
Neste tutorial, você criará um back-end para um *encurtador de URL* (um serviço que recebe qualquer URL e gera uma versão mais curta e de mais fácil leitura), ao passo que você se aprofunda nos conceitos do GraphQL, como consultas e mutações, e ferramentas, como a interface GraphiQL. Você já deve ter usado esses serviços antes, como, por exemplo, o bit.ly.
Como o GraphQL é uma tecnologia de linguagem agnóstica, ele é implementado em diferentes linguagens e frameworks. Aqui, você usará a linguagem de programação Python de uso geral, o framework Web Django e a biblioteca Graphene-Django para a implementação do GraphQL em Python com integrações específicas para o Django.
Pré-requisitos
- Para continuar o tutorial, você precisará da versão 3.5 do Python ou mais recente instalada em sua máquina de desenvolvimento. Para instalar o Python, siga nosso tutorial sobre Como instalar e configurar um ambiente de programação local para o Python 3 para o seu sistema operacional. Além disso, lembre-se de criar e iniciar um ambiente virtual; para seguir este tutorial, você pode nomear seu diretório de projeto como
<^>shorty<^>.
- É desejável ter um conhecimento básico de Django, mas não é obrigatório. Se estiver curioso, siga esta série de desenvolvimento do Django, criada pela comunidade da the cloud provider.
Passo 1 — Configurando o projeto Django
Neste passo, você instalará todas as ferramentas necessárias para o aplicativo e configurará seu projeto Django.
Assim que tiver criado seu diretório de projeto e iniciado seu ambiente virtual, conforme previsto nos pré-requisitos, instale os pacotes necessários usando o pip, o gerenciador de pacotes do Python. Este tutorial instalará a versão 2.1.7 do Django e a versão do 2.2.0 do Graphene-Django, ou uma versão mais recente:
pip install "django==<^>2.1.7<^>" "graphene-django>==<^>2.2.0<^>"
Agora, você tem todas as ferramentas necessárias em seu cinto de utilidades. Em seguida, crie um projeto Django usando o comando django-admin. Um projeto é o boilerplate Django padrão: um conjunto de pastas com tudo que é necessário para começar o desenvolvimento de um aplicativo Web. Neste caso, você chamará seu projeto de <^>shorty<^> e o criará dentro de sua pasta atual, especificando o ., no final:
django-admin startproject <^>shorty<^> .
Após criar seu projeto, execute as migrações Django. Estes arquivos contém o código Python gerado pelo Django e eles são responsáveis por alterar a estrutura do aplicativo, de acordo com os modelos Django. As alterações podem incluir a criação de uma tabela, por exemplo. Por padrão, o Django vem com seu próprio conjunto de migrações, responsável por subsistemas, como a autenticação do Django. Assim, é necessário executá-los com o seguinte comando:
python manage.py migrate
Este comando utiliza o intérprete Python para invocar um script Django chamado manage.py, responsável por gerenciar diferentes aspectos do seu projeto, como, por exemplo, a criação de aplicativos ou a execução de migrações.
Isso dará um resultado parecido com este:
[secondary_label Output]
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying sessions.0001_initial... OK
Assim que o banco de dados do Django estiver pronto, inicie seu servidor de desenvolvimento local:
python manage.py runserver
Isso dará:
[secondary_label Output]
Performing system checks...
System check identified no issues (0 silenced).
March 18, 2020 - 15:46:15
Django version 2.1.7, using settings 'shorty.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Este comando removerá o prompt em seu terminal e iniciará o servidor.
Visite a página http://127.0.0.1:8000 em seu navegador local. Você verá esta página:
Para parar o servidor e retornar ao seu terminal, pressione CTRL+C. Sempre que precisar acessar o navegador, confirme se o comando anterior está funcionando.
Em seguida, termine este passo habilitando a biblioteca Django-Graphene no projeto. O Django tem o conceito de app, um aplicativo Web com uma responsabilidade específica. Um projeto é composto por um ou vários apps. Por enquanto, abra o arquivo <^>shorty<^>/settings.py no editor de texto de sua escolha. Este tutorial utilizará o vim:
vim <^>shorty<^>/settings.py
O arquivo settings.py gerencia todas as configurações em seu projeto. Dentro dele, procure pela entrada INSTALLED_APPS e adicione a linha 'graphene_django':
[label shorty/shorty/settings.py]
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
<^>'graphene_django',<^>
]
...
Esta adição informa o Django que você usará um aplicativo chamado graphene-django, que foi instalado no Passo 1.
No final do arquivo, adicione a seguinte variável:
[label shorty/shorty/settings.py]
...
<^>GRAPHENE = {<^>
<^>'SCHEMA': 'shorty.schema.schema',<^>
<^>}<^>
Esta última variável aponta para seu _Schema_ principal, que você criará mais tarde. No GraphQL, um Esquema contém todos os tipos de objeto, como recursos, consultas e mutações. Pense nele como uma documentação representando todos os dados e funcionalidades disponíveis em seu sistema.
Após as alterações, salve e feche o arquivo.
Agora, seu projeto Django está configurado. No próximo passo, você criará um aplicativo Django e os Modelos dele.
Passo 2 — Configurando um aplicativo e modelos Django
Normalmente, uma plataforma Django é composta por um projeto e muitos aplicativos, ou _apps_. Um aplicativo descreve um conjunto de recursos dentro de um projeto e, se for bem projetado, pode ser reutilizado por vários projetos Django.
Neste passo, você criará um aplicativo chamado <^>shortener<^>, responsável pela funcionalidade de encurtamento da URL em questão. Para criar a estrutura básica, digite o próximo comando em seu terminal:
python manage.py startapp <^>shortener<^>
Aqui, você usou os parâmetros startapp <^>app_name<^>, instruindo o manage.py a criar um aplicativo chamado shortener.
Para terminar a criação do app, abra o arquivo <^>shorty<^>/settings.py.
vim <^>shorty<^>/settings.py
Adicione o nome do aplicativo à mesma entrada INSTALLED_APPS, que você modificou anteriormente:
[label shorty/shorty/settings.py]
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'graphene_django'
<^>'shortener',<^>
]
...
Salve e feche o arquivo.
Com seu shortener adicionado ao <^>shorty<^>/settings.py, prossiga para a criação de modelos para seu projeto. Os Modelos constituem uma das principais funcionalidades no Django. Eles são usados para representar um banco de dados de maneira "Pythonizada", permitindo que você gerencie, consulte e armazene dados usando o código Python.
Antes de abrir o arquivo models.py para fazer alterações, este tutorial dará uma visão geral das mudanças que você fará.
Seu arquivo modelo (shortener/models.py) terá o seguinte conteúdo, assim que tiver substituído o código existente:
[label shorty/shortener/models.py]
from hashlib import md5
from django.db import models
Aqui, você importará os pacotes necessários para seu código. Você adicionará a linha from hashlib import md5 no topo do modelo para importar a biblioteca padrão do Python, que será usada para criar uma _hash_ da URL. A linha from django.db import models é um auxiliar do Django para a criação de modelos.
[warning]
Aviso: este tutorial refere-se ao *hash* como o resultado de uma função que recebe uma entrada e sempre retorna a mesma saída. Este tutorial utilizará a função hash MD5 para fins demonstrativos.
Note que a MD5 possui problemas de colisão e deve ser evitada em ambientes de produção.
Em seguida, adicione um Modelo chamado URL com os seguintes campos:
full_url: a URL a ser encurtada.
url_hash: um hash curto, representando a URL completa.
clicks: quantas vezes a URL encurtada foi acessada.
created_at: a data e hora em que a URL foi criada.
[label shorty/shortener/models.py]
...
class URL(models.Model):
full_url = models.URLField(unique=True)
url_hash = models.URLField(unique=True)
clicks = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
Você gerará a url_hash aplicando o algoritmo hash MD5 ao campo full_url, usando apenas os primeiros 10 caracteres retornados pelo método save() do Modelo, executado toda vez que o Django armazena uma entrada ao banco de dados. Além disso, encurtadores de URL geralmente contam a quantidade de vezes que um link foi clicado. Você conseguirá fazer isso chamando o método clicked(), quando a URL for visitada por um usuário.
As operações mencionadas serão adicionadas em seu modelo URL com este código:
[label shorty/shortener/models.py]
...
def clicked(self):
self.clicks += 1
self.save()
def save(self, *args, **kwargs):
if not self.id:
self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]
return super().save(*args, **kwargs)
Agora que você revisou o código, abra o arquivo shortener/models.py:
vim shortener/models.py
Substitua o código pelo seguinte conteúdo:
[label shorty/shortener/models.py]
from hashlib import md5
from django.db import models
class URL(models.Model):
full_url = models.URLField(unique=True)
url_hash = models.URLField(unique=True)
clicks = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
def clicked(self):
self.clicks += 1
self.save()
def save(self, *args, **kwargs):
if not self.id:
self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]
return super().save(*args, **kwargs)
Lembre-se de salvar e fechar o arquivo.
Para aplicar estas alterações no banco de dados, será necessário criar as migrações, executando o comando a seguir:
python manage.py makemigrations
Isso gerará o seguinte resultado:
[secondary_label Output]
Migrations for 'shortener':
shortener/migrations/0001_initial.py
- Create model URL
Depois, execute as migrações:
python manage.py migrate
Você verá o seguinte resultado em seu terminal:
[secondary_label Output]
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, shortener
Running migrations:
Applying shortener.0001_initial... OK
Agora que os modelos estão configurados, no próximo passo você criará o ponto de extremidade do GraphQL e uma consulta.
Passo 3 — Criando consultas
A arquitetura REST mostra recursos diferentes em pontos de extremidades distintos, cada um contendo uma estrutura de dados bem definida. Por exemplo, você pode obter uma lista de usuários em /api/users, sempre contendo os mesmos campos. O GraphQL, por outro lado, tem um único ponto de extremidade para todas as interações, e utiliza as _Queries_ para acessar os dados. A principal (e mais valiosa) diferença é que você pode usar uma consulta para recuperar todos os seus usuários dentro de uma única solicitação.
Comece criando uma consulta para obter todas as URLs. Você precisará de algumas coisas:
- Um tipo de URL, vinculado ao seu modelo previamente definido.
- Uma instrução de consulta chamada
urls.
- Um método para _resolver_ sua consulta, que significa obter todas as URLs do banco de dados e retorná-las ao cliente.
Crie um arquivo novo chamado shortener/schema.py:
vim shortener/schema.py
Comece adicionando as instruções de import do Python:
[label shorty/shortener/schema.py]
import graphene
from graphene_django import DjangoObjectType
from .models import URL
A primeira linha importa a biblioteca graphene principal, que contém os tipos base do GraphQL, como o List. O DjangoObjectType é um auxiliar para criar uma definição de Schema de qualquer modelo Django, e a terceira linha importa seu modelo de URL criado anteriormente.
Depois disso, crie um novo tipo do GraphQL para o modelo URL, adicionando as linhas seguintes:
[label shorty/shortener/schema.py]
...
class URLType(DjangoObjectType):
class Meta:
model = URL
Por fim, adicione estas linhas para criar um tipo de consulta para o modelo URL:
[label shorty/shortener/schema.py]
...
class Query(graphene.ObjectType):
urls = graphene.List(URLType)
def resolve_urls(self, info, **kwargs):
return URL.objects.all()
Este código cria uma classe Query com um campo chamado urls, que é uma lista de URLType previamente definida. Ao resolver a consulta através do método resolve_urls, você retorna todas as URLs armazenadas no banco de dados.
O arquivo shortener/schema.py completo é mostrado aqui:
[label shorty/shortener/schema.py]
import graphene
from graphene_django import DjangoObjectType
from .models import URL
class URLType(DjangoObjectType):
class Meta:
model = URL
class Query(graphene.ObjectType):
urls = graphene.List(URLType)
def resolve_urls(self, info, **kwargs):
return URL.objects.all()
Salve e feche o arquivo.
Todas as consultas devem ser adicionadas ao esquema principal. Pense nele como o detentor de todos os seus recursos.
Crie um novo arquivo no caminho <^>shorty<^>/schema.py e abra-o em seu editor:
vim <^>shorty<^>/schema
Importe os pacotes Python seguintes, adicionando as linha a seguir. A primeira linha, como já foi referida, contém os tipos base do GraphQL. A segunda linha importa o arquivo de esquema criado anteriormente.
[label shorty/shorty/schema.py]
import graphene
import shortener.schema
Em seguida, adicione a classe Query principal. Ela terá, através de herança, todas as consultas e operações futuras criadas:
[label shorty/shorty/schema.py]
...
class Query(shortener.schema.Query, graphene.ObjectType):
pass
Por último, crie a variável schema:
[label shorty/shorty/schema.py]
...
schema = graphene.Schema(query=Query)
A configuração SCHEMA que você definiu no Passo 2 aponta para a variável schema, que acabou de criar.
O arquivo <^>shorty<^>/schema.py completo é mostrado aqui:
[label shorty/shorty/schema.py]
import graphene
import shortener.schema
class Query(shortener.schema.Query, graphene.ObjectType):
pass
schema = graphene.Schema(query=Query)
Salve e feche o arquivo.
Em seguida, habilite o ponto de extremidade do GraphQL e a interface GraphiQL, que é uma interface Web gráfica usada para interagir com o sistema GraphQL.
Abra o arquivo <^>shorty<^>/urls.py:
vim <^>shorty<^>/urls.py
Para fins de aprendizagem, exclua o conteúdo do arquivo e salve-o, de forma que você possa começar do zero.
As primeiras linhas que você adicionará são as instruções de importação do Python:
[label shorty/shorty/urls.py]
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
A função path é usada pelo Django para criar uma URL acessível para a interface GraphiQL. Em seguida, importe o csrf_exempt, que permite que os clientes enviem dados ao servidor. Você pode encontrar uma explicação mais completa na Documentação do Graphene. Na última linha, você importou o código atualmente responsável pela interface via GraphQLView.
Em seguida, crie uma variável chamada urlpatterns.
[label shorty/shorty/urls.py]
...
urlpatterns = [
path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
]
Isto reunirá todo o código necessário para criar a interface GraphiQL, disponível no caminho graphql/:
O arquivo shortener/urls.py completo é mostrado aqui:
[label shorty/shorty/urls.py]
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
urlpatterns = [
path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
]
Salve o arquivo e feche-o.
De volta no terminal, execute o comando python manage.py runserver (se ainda não estiver em execução):
python manage.py runserver
Abra seu navegador Web no endereço http://localhost:8000/graphql. Você verá esta tela:
O GraphiQL é uma interface onde você pode executar instruções do GraphQL e ver os resultados. Uma das características dela é a seção Docs, no canto superior direito. Como tudo no GraphQL funciona por tipos, você receberá uma documentação grátis sobre todos seus tipos, consultas, mutações, etc.
Após explorar a página, insira sua primeira consulta na área de texto principal:
query {
urls {
id
fullUrl
urlHash
clicks
createdAt
}
}
Este conteúdo mostra como uma consulta GraphQL é estruturada: primeiro, utilize a palavra-chave query para avisar o servidor que você deseja apenas alguns dados de volta. Em seguida, utilize o campo urls, definido no arquivo shortener/schema.py, dentro da classe Query. A partir daí, solicite explicitamente todos os campos definidos no modelo URL usando o estilo camel case, que é o padrão do GraphQL.
Agora, clique no botão de play no canto superior esquerdo.
Você receberá a seguinte resposta, informando que ainda não possui URLs:
[secondary_label Output]
{
"data": {
"urls": []
}
}
Isso mostra que o GraphQL está funcionando. Em seu terminal, pressione CTRL+C para interromper o servidor.
Você fez muita coisa neste passo, criou o ponto de extremidade GraphQL, fez uma consulta para obter todas as URLs e habilitou a interface GraphiQL. Agora, você criará as mutações para mudar o banco de dados.
Passo 4 — Criando mutações
A maioria dos aplicativos tem uma maneira de alterar o estado de um banco de dados adicionando, atualizando ou excluindo dados. No GraphQL, essas operações são chamadas de *Mutações*. Elas se parecem com consultas, mas usam argumentos para enviar dados para o servidor.
Para criar sua primeira mutação, abra o shortener/schema.py:
vim shortener/schema.py
No final do arquivo, adicione uma nova classe chamada CreateURL:
[label shorty/shortener/schema.py]
...
class CreateURL(graphene.Mutation):
url = graphene.Field(URLType)
Esta classe herda o auxiliar graphene.Mutation, para ter as capacidades de uma mutação GraphQL. Ela também tem uma url de nome da propriedade, definindo o conteúdo retornado pelo servidor após a mutação terminar. Neste caso, ela será a estrutura de dados URLType.
Em seguida,adicione uma subclasse chamada Arguments à classe já definida:
[label shorty/shortener/schema.py]
...
class Arguments:
full_url = graphene.String()
Isto define quais dados serão aceitos pelo servidor. Aqui, espera-se um parâmetro chamado full_url com um conteúdo String:
Adicione agora as linhas seguintes para criar o método mutate:
[label shorty/shortener/schema.py]
...
def mutate(self, info, full_url):
url = URL(full_url=full_url)
url.save()
Este método mutate faz grande parte do trabalho,pois recebe os dados do cliente e os armazena no banco de dados. No final, ele retorna a classe propriamente dita, contendo o item recém-criado.
Por último, crie uma classe Mutation para armazenar todas as mutações ao seu aplicativo, adicionando essas linhas:
[label shorty/shortener/schema.py]
...
class Mutation(graphene.ObjectType):
create_url = CreateURL.Field()
Até agora, você tem apenas uma mutação, chamada create_url.
O arquivo shortener/schema.py completo é mostrado aqui:
[label shorty/shortener/schema.py]
import graphene
from graphene_django import DjangoObjectType
from .models import URL
class URLType(DjangoObjectType):
class Meta:
model = URL
class Query(graphene.ObjectType):
urls = graphene.List(URLType)
def resolve_urls(self, info, **kwargs):
return URL.objects.all()
class CreateURL(graphene.Mutation):
url = graphene.Field(URLType)
class Arguments:
full_url = graphene.String()
def mutate(self, info, full_url):
url = URL(full_url=full_url)
url.save()
return CreateURL(url=url)
class Mutation(graphene.ObjectType):
create_url = CreateURL.Field()
Feche e salve o arquivo.
Para terminar de adicionar a mutação, mude o arquivo <^>shorty<^>/schema.py:
vim <^>shorty<^>/schema.py
Adicione o arquivo para incluir o código destacado seguinte:
[label shorty/shorty/schema.py]
import graphene
import shortener.schema
class Query(shortener.schema.Query, graphene.ObjectType):
pass
<^>class Mutation(shortener.schema.Mutation, graphene.ObjectType):<^>
<^>pass<^>
schema = graphene.Schema(query=Query, <^>mutation=Mutation<^>)
Salve e feche o arquivo. Caso não esteja executando o servidor local,inicie-o:
python manage.py runserver
Vá para http://localhost:8000/graphql em seu navegador Web. Execute sua primeira mutação na interface Web do GraphQL, executando a instrução:
mutation {
createUrl(fullUrl:"https://www.progressiverobot.com/") {
url {
id
fullUrl
urlHash
clicks
createdAt
}
}
}
Você redigiu a mutação com o nome createURL, o argumento fullUrl e os dados que deseja na resposta definida no campo url.
O resultado terá as informações da URL que acabou de criar dentro do campo data do GraphQL, como mostrado aqui:
[secondary_label Output]
{
"data": {
"createUrl": {
"url": {
"id": "1",
"fullUrl": "https://www.progressiverobot.com/",
"urlHash": "077880af78",
"clicks": 0,
"createdAt": "<^>2020-01-30T19:15:10.820062+00:00<^>"
}
}
}
}
Com isso, foi adicionada uma URL ao banco de dados com a versão com hash dela,como você pode ver no campo urlHash. Tente executar a consulta que você criou no último Passo para ver seu resultado:
query {
urls {
id
fullUrl
urlHash
clicks
createdAt
}
}
O resultado mostrará a URL armazenada:
[secondary_label Output]
{
"data": {
"urls": [
{
"id": "1",
"fullUrl": "https://www.progressiverobot.com/",
"urlHash": "077880af78",
"clicks": 0,
"createdAt": "<^>2020-03-18T21:03:24.664934+00:00<^>"
}
]
}
}
Você pode também tentar executar a mesma consulta mas, dessa vez, pedindo apenas os campo que deseja.
Em seguida, tente fazer isso mais uma vez com uma URL diferente:
mutation {
createUrl(fullUrl:"https://www.progressiverobot.com/") {
url {
id
fullUrl
urlHash
clicks
createdAt
}
}
}
O resultado será:
[secondary_label Output]
{
"data": {
"createUrl": {
"url": {
"id": "2",
"fullUrl": "https://www.progressiverobot.com/",
"urlHash": "703562669b",
"clicks": 0,
"createdAt": "<^>2020-01-30T19:31:10.820062+00:00<^>"
}
}
}
}
O sistema agora consegue criar URLs curtas e listá-las. No próximo passo, você habilitará o acesso dos usuários auma URL pela versão curta, redirecionando-os para a página correta.
Passo 5 — Criando o ponto de extremidade de acesso
Neste passo, você usará o Django Views, um método que recebe uma solicitação e retorna uma resposta, para redirecionar qualquer um que acesse o ponto de extremidade http://localhost:8000/<^>url_hash<^> para sua URL completa.
Abra o arquivo shortener/views.py com seu editor:
vim shortener/views.py
Para começar, importe dois pacotes, substituindo seus conteúdos com as linhas a seguir:
[label shorty/shortener/views.py]
from django.shortcuts import get_object_or_404, redirect
from .models import URL
Essas linhas serão explicadas mais tarde.
Em seguida, crie um Django View chamado root. Adicione este snippet de código responsável pela visualização no final de seu arquivo:
[label shorty/shortener/views.py]
...
def root(request, url_hash):
url = get_object_or_404(URL, url_hash=url_hash)
url.clicked()
return redirect(url.full_url)
Esta linha recebe um argumento chamado url_hash da URL solicitada por um usuário. Dentro da função, a primeira linha tenta obter a URL do banco de dados usando o argumento url_hash. Caso não encontre uma URL, ela retorna ao cliente o erro HTTP 404, que significa que o recurso está indisponível. Depois disso, ele incrementa a propriedade clicked da entrada URL, certificando-se de quantificar o número de vezes que a URL foi acessada. No final, ele redireciona o cliente para a URL solicitada.
O arquivo shortener/views.py completo é mostrado aqui:
[label shorty/shortener/views.py]
from django.shortcuts import get_object_or_404, redirect
from .models import URL
def root(request, url_hash):
url = get_object_or_404(URL, url_hash=url_hash)
url.clicked()
return redirect(url.full_url)
Salve e feche o arquivo.
Em seguida, abra o <^>shorty/urls.py:
vim <^>shorty<^>/urls.py
Adicione o código destacado a seguir para habilitar a visualização root.
[label shorty/shorty/urls.py]
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
<^>from shortener.views import root<^>
urlpatterns = [
path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
<^>path('<str:url_hash>/', root, name='root'),<^>
]
A visualização root ficará acessível no caminho / de seu servidor, aceitando uma url_hash como parâmetro string.
Salve e feche o arquivo. Se não estiver executando o servidor local, inicie-o executando o comando python manage.py runserver.
Para testar sua nova adição, abra seu navegador Web e acesse a URL http://localhost:8000/077880af78. Note que a última parte da URL é o hash criado pela mutação do Passo 5. Você será redirecionado para a página URL do hash, neste caso, o site da comunidade the cloud provider.
Agora, com o redirecionamento da URL funcionando, você tornará o aplicativo mais seguro implementando o tratamento de erros quando a mutação for executada.
Passo 6 — Implementando o tratamento de erros
O tratamento de erros é a melhor prática em todos os aplicativos, pois os desenvolvedores geralmente não controlam o que será enviado para o servidor. Neste caso, você pode tentar prever falhas e minimizar os impactos delas. Em um sistema complexo como o GraphQL, muitas coisas podem dar errado, desde o cliente pedindo dados incorretos até o servidor perdendo o acesso ao banco de dados.
Por ser um sistema de tipos, o GraphQL pode verificar tudo aquilo que o cliente solicita e recebe, através de uma operação chamada _Schema Validation_. Você pode ver esta operação em ação ao fazer uma consulta com um campo não existente.
Vá para http://localhost:8000/graphql em seu navegador mais uma vez e execute a próxima consulta dentro da interface GraphiQL, com o campo iDontExist:
query {
urls {
id
fullUrl
urlHash
clicks
createdAt
iDontExist
}
}
Como não existe o campo iDontExist definido em sua consulta, o GraphQL retorna uma mensagem de erro:
[secondary_label Output]
{
"errors": [
{
"message": "Cannot query field \"iDontExist\" on type \"URLType\".",
"locations": [
{
"line": 8,
"column": 5
}
]
}
]
}
Isto é importante, pois no sistema de tipos GraphQL, o objetivo é enviar e receber apenas as informações já definidas no esquema.
O aplicativo, do jeito que se encontra, aceita qualquer string arbitrária no campo full_url. O problema disso é que se alguém enviar uma URL mal construída, você não estaria redirecionando o usuário para um endereço válido ao tentar acessar a informação armazenada. Neste caso, será necessário verificar se a full_url está bem formatada antes de salvá-la no banco de dados. Caso haja algum erro, acione a exceção GraphQLError com uma mensagem personalizada.
Vamos implementar esta funcionalidade em dois passos. Primeiro, abra o arquivo shortener/models.py:
vim shortener/models.py
Adicione as linhas destacadas na seção de importação:
[label shorty/shortener/models.py]
from hashlib import md5
from django.db import models
<^>from django.core.validators import URLValidator<^>
<^>from django.core.exceptions import ValidationError<^>
<^>from graphql import GraphQLError<^>
...
O URLValidator é um auxiliar do Django, criado para validar uma String URL, enquanto o GraphQLError é utilizado pelo Graphene para acionar exceções com uma mensagem personalizada.
Em seguida, lembre-se de validar a URL recebida pelo usuário antes de salvá-la no banco de dados. Habilite esta operação adicionando o código destacado no arquivo shortener/models.py:
[label shorty/shortener/models.py]
class URL(models.Model):
full_url = models.URLField(unique=True)
url_hash = models.URLField(unique=True)
clicks = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
def clicked(self):
self.clicks += 1
self.save()
def save(self, *args, **kwargs):
if not self.id:
self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]
<^>validate = URLValidator()<^>
<^>try:<^>
<^>validate(self.full_url)<^>
<^>except ValidationError as e:<^>
<^>raise GraphQLError('invalid url')<^>
return super().save(*args, **kwargs)
Primeiro, este código instancia o URLValidator na variável validate. Dentro do bloco try/except, você faz o validate() da URL recebida e aciona um GraphQLError com a mensagem personalizada invalid url, caso algo dê errado.
O arquivo shortener/models.py completo é mostrado aqui:
[label shorty/shortener/models.py]
from hashlib import md5
from django.db import models
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError
from graphql import GraphQLError
class URL(models.Model):
full_url = models.URLField(unique=True)
url_hash = models.URLField(unique=True)
clicks = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
def clicked(self):
self.clicks += 1
self.save()
def save(self, *args, **kwargs):
if not self.id:
self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]
validate = URLValidator()
try:
validate(self.full_url)
except ValidationError as e:
raise GraphQLError('invalid url')
return super().save(*args, **kwargs)
Salve e feche o arquivo. Caso não esteja executando o servidor local, inicie-o com o comando python manage.py runserver.
Em seguida, teste seu novo tratamento de erro em http://localhost:8000/graphql. Tente criar uma nova URL com uma full_url inválida na interface GraphQL:
mutation {
createUrl(fullUrl:"not_valid_url"){
url {
id
fullUrl
urlHash
clicks
createdAt
}
}
}
Ao enviar uma URL inválida, sua exceção será acionada com a mensagem personalizada:
[secondary_label Output]
{
"errors": [
{
"message": "invalid url",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"createUrl"
]
}
],
"data": {
"createUrl": null
}
}
Se procurar em seu terminal onde o comando python manage.py runserver está sendo executado, um erro aparecerá:
[secondary_label Output]
...
graphql.error.located_error.GraphQLLocatedError: invalid url
[<^>30/Jan/2020 19:46:32<^>] "POST /graphql/ HTTP/1.1" 200 121
Um ponto de extremidade do GraphQL sempre falhará com um código de status HTTP 200, que geralmente significa êxito. Lembre-se de que, embora o GraphQL seja construído no topo do HTTP, ele não utiliza os conceitos do código de status do HTTP ou os métodos HTTP, como o REST faz.
Com o tratamento de erro implementado, coloque um mecanismo para filtrar suas consultas, reduzindo as informações retornadas pelo servidor.
Passo 7 — Filtros de implementação
Imagine que você começou a usar o encurtador de URL para adicionar seus próprios links. Após um tempo, existirão tantas entradas que será difícil encontrar a correta. Esse problema pode ser resolvido utilizando _filtros_.
O processo de filtragem é um conceito comum em APIs do REST, onde normalmente há anexado à URL um _parâmetro de consulta_ com um campo e valor. Por exemplo, para filtrar todos os usuários chamado jojo, você poderia usar GET /api/users?name=jojo.
No GraphQL, você usará argumentos de consulta como filtros. Eles criam uma interface agradável e limpa.
Você pode resolver o problema "difícil encontrar uma URL", permitindo que o cliente filtre as URLs pelo nome, usando o campo full_url. Para implementar isso, abra o arquivo shortener/schema.py em seu editor favorito.
vim shortener/schema.py
Primeiro, importe o método Q na linha destacada:
[label shorty/shortener/schema.py]
import graphene
from graphene_django import DjangoObjectType
<^>from django.db.models import Q<^>
from .models import URL
...
Isto será usado para filtrar sua consulta do banco de dados.
Em seguida, reescreva a classe Query inteira com o seguinte conteúdo:
[label shorty/shortener/schema.py]
...
class Query(graphene.ObjectType):
urls = graphene.List(URLType, url=graphene.String())
def resolve_urls(self, info, url=None, **kwargs):
queryset = URL.objects.all()
if url:
_filter = Q(full_url__icontains=url)
queryset = queryset.filter(_filter)
return queryset
...
As modificações que estão sendo feitas são:
- Adicione o parâmetro do filtro
urldentro da variávelurlse do métodoresolve_url.
- Dentro de
resolve_urls, se um parâmetro chamadourlfor dado, a filtragem do banco de dados retornará apenas as URLs que contenham o valor definido, usando o métodoQ(full_url__icontains=url).
O arquivo shortener/schema.py completo é mostrado aqui:
[label shorty/shortener/schema.py]
import graphene
from graphene_django import DjangoObjectType
from django.db.models import Q
from .models import URL
class URLType(DjangoObjectType):
class Meta:
model = URL
class Query(graphene.ObjectType):
urls = graphene.List(URLType, url=graphene.String())
def resolve_urls(self, info, url=None, **kwargs):
queryset = URL.objects.all()
if url:
_filter = Q(full_url__icontains=url)
queryset = queryset.filter(_filter)
return queryset
class CreateURL(graphene.Mutation):
url = graphene.Field(URLType)
class Arguments:
full_url = graphene.String()
def mutate(self, info, full_url)
url = URL(full_url=full_url)
url.save()
return CreateURL(url=url)
class Mutation(graphene.ObjectType):
create_url = CreateURL.Field()
Salve e feche o arquivo. Caso não esteja executando o servidor local, inicie-o com python manage.py runserver.
Teste suas últimas alterações em http://localhost:8000/graphql. Na interface GraphiQL, escreva a seguinte instrução. Ela filtrará todas as URLs com a palavra community:
query {
urls(url:"community") {
id
fullUrl
urlHash
clicks
createdAt
}
}
O resultado será apenas um registro, já que você adicionou somente uma URL com a string community nela. Se você tivesse adicionado mais URLs antes, o resultado poderia ter sido diferente.
[secondary_label Output]
{
"data": {
"urls": [
{
"id": "1",
"fullUrl": "https://www.progressiverobot.com/",
"urlHash": "077880af78",
"clicks": 1,
"createdAt": "<^>2020-01-30T19:27:36.243900+00:00<^>"
}
]
}
}
Agora, você pode pesquisar suas URLs. No entanto, se houver links demais, seus clientes podem reclamar que a lista de URL está retornando mais dados do que os aplicativos deles conseguem processar. Para resolver isso, você implementará a paginação.
Passo 8 — Implementando a paginação
Se houver entradas de URL em excesso, os clientes que utilizam seu back-end podem reclamar que o tempo de resposta está muito longo ou que o tamanho das URLs está grande demais. Até mesmo seu banco de dados pode encontrar dificuldade para reunir um enorme conjunto de informações. Para resolver este problema, permita que o cliente especifique quantos itens ele deseja dentro de cada pedido, usando uma técnica chamada _paginação_.
Não há modo padrão de implementar esta funcionalidade. Mesmo em APIs do REST, você pode vê-la em cabeçalhos HTTP ou em parâmetros de consulta, com diferentes nomes e comportamentos.
Neste aplicativo, você implementará a paginação, habilitando mais dois argumentos para consulta das URLs: first e skip. O first selecionará um número variável de elementos entre os primeiros da lista, e o skip especificará quantos elementos devem ser ignorados partindo do início da lista. Por exemplo, ao utilizar first == 10 e skip == 5, pegamos as primeiras 10 URLs, mas ignoramos 5 delas, retornando apenas as 5 restantes.
Implementar esta solução é parecido com a adição de um filtro.
Abra o arquivo shortener/schema.py:
vim shortener/schema.py
No arquivo, mude a classe Query, adicionando os dois novos parâmetros à variável urls e ao método resolve_urls, destacados no código a seguir:
[label shorty/shortener/schema.py]
import graphene
from graphene_django import DjangoObjectType
from django.db.models import Q
from .models import URL
class Query(graphene.ObjectType):
urls = graphene.List(URLType, url=graphene.String(), <^>first=graphene.Int(), skip=graphene.Int()<^>)
def resolve_urls(self, info, url=None, <^>first=None, skip=None,<^> **kwargs):
queryset = URL.objects.all()
if url:
_filter = Q(full_url__icontains=url)
queryset = queryset.filter(_filter)
<^>if first:<^>
<^>queryset = queryset[:first]<^>
<^>if skip:<^>
<^>queryset = queryset[skip:]<^>
return queryset
...
Este código usa os parâmetros recém-criados first e skip, dentro do método resolve_urls, para filtrar a consulta do banco de dados.
Salve e feche o arquivo. Caso não esteja executando o servidor local, inicie-o com python manage.py runserver.
Para testar a paginação, emita a seguinte consulta na interface GraphQL em http://localhost:8000/graphql:
query {
urls(first: 2, skip: 1) {
id
fullUrl
urlHash
clicks
createdAt
}
}
Seu encurtador de URL retornará a segunda URL criada em seu banco de dados:
[secondary_label Output]
{
"data": {
"urls": [
{
"id": "2",
"fullUrl": "https://www.progressiverobot.com/",
"urlHash": "703562669b",
"clicks": 0,
"createdAt": "<^>2020-01-30T19:31:10.820062+00:00<^>"
}
]
}
}
Isto mostra que a funcionalidade de paginação funciona. Sinta-se à vontade para brincar, adicionando mais URLs e testando diferentes combinações de first e skip.
Conclusão
O ecossistema da GraphQL cresce a cada dia, com uma comunidade ativa por trás dele. A GraphQL foi testada por empresas como a GitHub e Facebook e provou-se pronta para a produção. Agora, você também pode aplicar esta tecnologia em seus próprios projetos.
Neste tutorial, você criou um serviço encurtador de URL usando o GraphQL, Python e Django, usando conceitos como consultas e mutações. Além do mais, você agora entende como se basear nessas tecnologias para desenvolver aplicativos Web, usando o framework Web Django.
Explore mais sobre o GraphQL e as ferramentas usadas aqui no site do GraphQL e os sites de documentação do Graphene. Além disso, a the cloud provider tem tutoriais adicionais para Python e Django que você pode usar se quiser aprender mais sobre eles.