著者は、 Write for DOnations プログラムの一環として、 Girls WhoCodeを選択して寄付を受け取りました。

序章

GraphQL は、 REST APIの代替として、Facebookによって作成およびオープンソース化されたAPI標準です。 REST APIとは対照的に、GraphQLは型付きシステムを使用してデータ構造を定義します。この場合、送受信されるすべての情報は、事前定義されたスキーマに準拠している必要があります。 また、さまざまなリソースの複数のURLではなく、すべての通信に対して単一のエンドポイントを公開し、クライアントから要求されたデータのみを返すことで overfetching の問題を解決し、より小さく簡潔な応答を生成します。

このチュートリアルでは、 URL短縮サービスのバックエンドを作成します。これは、クエリやミューテーションなどのGraphQLの概念を掘り下げながら、任意のURLを取得してより短く読みやすいバージョンを生成するサービスです。 、およびGraphiQLインターフェースなどのツール。 bit.ly のように、以前にそのようなサービスを使用したことがあるかもしれません。

GraphQLは言語に依存しないテクノロジーであるため、さまざまな言語やフレームワークの上に実装されています。 ここでは、汎用の Pythonプログラミング言語 Django Webフレームワーク、および Graphene-Django ライブラリを、特定の統合を備えたGraphQLPython実装として使用します。ジャンゴ。

前提条件

  • このチュートリアルを続行するには、開発マシンにPythonバージョン3.5以降がインストールされている必要があります。 Pythonをインストールするには、お使いのOS用のPython3のローカルプログラミング環境をインストールしてセットアップする方法に関するチュートリアルに従ってください。 仮想環境も作成して開始してください。 このチュートリアルの先導に従うために、プロジェクトディレクトリに名前を付けることができます shorty.

  • Djangoの初級レベルの知識が必要ですが、必須ではありません。 興味があれば、DigitalOceanコミュニティによって作成されたこのDjango開発シリーズをフォローできます。

ステップ1—Djangoプロジェクトのセットアップ

このステップでは、アプリケーションに必要なすべてのツールをインストールし、Djangoプロジェクトをセットアップします。

前提条件で説明されているように、プロジェクトディレクトリを作成して仮想環境を開始したら、次を使用して必要なパッケージをインストールします。 pip、Pythonパッケージマネージャー。 このチュートリアルでは、Djangoバージョン2.1.7とGraphene-Djangoバージョン2.2.0以降をインストールします。

  1. pip install "django==2.1.7" "graphene-django>==2.2.0"

これで、ツールベルトに必要なすべてのツールが揃いました。 次に、を使用してDjangoプロジェクトを作成します django-admin 指図。 プロジェクトは、デフォルトのDjangoボイラープレートであり、Webアプリケーションの開発を開始するために必要なすべてのものを含むフォルダーとファイルのセットです。 この場合、プロジェクトを呼び出します shorty を指定して、現在のフォルダ内に作成します . 最後に:

  1. django-admin startproject shorty .

プロジェクトを作成したら、Django移行を実行します。 これらのファイルには、Djangoによって生成されたPythonコードが含まれており、Djangoモデルに従ってアプリケーションの構造を変更する役割を果たします。 たとえば、変更にはテーブルの作成が含まれる場合があります。 デフォルトでは、Djangoには Django Authentication などのサブシステムを担当する独自の移行セットが付属しているため、次のコマンドでそれらを実行する必要があります。

  1. python manage.py migrate

このコマンドは、Pythonインタープリターを使用して、次のDjangoスクリプトを呼び出します。 manage.py、アプリの作成や移行の実行など、プロジェクトのさまざまな側面を管理する責任があります。

これにより、次のような出力が得られます。

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

Djangoのデータベースの準備ができたら、ローカル開発サーバーを起動します。

  1. python manage.py runserver

これにより、次のようになります。

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.

このコマンドは、ターミナルのプロンプトを取り除き、サーバーを起動します。

訪問 http://127.0.0.1:8000 ローカルブラウザのページ。 このページが表示されます:

サーバーを停止してターミナルに戻るには、を押します。 CTRL+C. ブラウザにアクセスする必要があるときはいつでも、前のコマンドが実行されていることを確認してください。

次に、プロジェクトでDjango-Grapheneライブラリを有効にして、この手順を完了します。 Djangoには次の概念があります app、特定の責任を持つWebアプリケーション。 プロジェクトは、1つまたは複数のアプリで構成されます。 今のところ、 shorty/settings.py 選択したテキストエディタでファイルを作成します。 このチュートリアルでは、vimを使用します。

  1. vim shorty/settings.py

The settings.py ファイルは、プロジェクトのすべての設定を管理します。 その中を検索します INSTALLED_APPS エントリを追加し、 'graphene_django' ライン:

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',
]
...

この追加により、Djangoは次のアプリを使用することになります。 graphene_django、ステップ1でインストールしました。

ファイルの最後に、次の変数を追加します。

shorty / shorty / settings.py
...
GRAPHENE = {
    'SCHEMA': 'shorty.schema.schema',
}

この最後の変数は、後で作成するメインのSchemaを指します。 GraphQLでは、スキーマには、リソース、クエリ、ミューテーションなどのすべてのオブジェクトタイプが含まれます。 これは、システムで使用可能なすべてのデータと機能を表すドキュメントと考えてください。

変更後、ファイルを保存して閉じます。

これで、Djangoプロジェクトが構成されました。 次のステップでは、Djangoアプリとそのモデルを作成します。

ステップ2—Djangoアプリとモデルのセットアップ

Djangoプラットフォームは通常、1つのプロジェクトと多くのアプリケーションまたはアプリで構成されています。 アプリはプロジェクト内の一連の機能を記述し、適切に設計されていれば、Djangoプロジェクト全体で再利用できます。

このステップでは、というアプリを作成します shortener、実際のURL短縮機能を担当します。 基本的なスケルトンを作成するには、ターミナルで次のコマンドを入力します。

  1. python manage.py startapp shortener

ここではパラメータを使用しました startapp app_name、指示 manage.py 名前の付いたアプリを作成するには shortener.

アプリの作成を完了するには、 shorty/settings.py ファイル

  1. vim shorty/settings.py

同じにアプリの名前を追加します INSTALLED_APPS 以前に変更したエントリ:

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',
]
...

ファイルを保存して閉じます。

あなたと shortener に追加 shorty/settings.py、プロジェクトのモデルの作成に進むことができます。 モデルはDjangoの重要な機能の1つです。 これらは「Pythonic」方式でデータベースを表すために使用され、Pythonコードを使用してデータを管理、クエリ、および保存できるようにします。

開く前に models.py 変更のファイル。このチュートリアルでは、行う変更の概要を説明します。

モデルファイル—shortener/models.py-既存のコードを置き換えると、次のコンテンツが含まれます。

shorty / shortener / models.py
from hashlib import md5

from django.db import models

ここでは、コードに必要なパッケージをインポートします。 行を追加します from hashlib import md5 上部に、URLのhashを作成するために使用されるPython標準ライブラリをインポートします。 The from django.db import models lineは、モデルを作成するためのDjangoヘルパーです。

警告:このチュートリアルでは、入力を受け取り、常に同じ出力を返す関数の結果として、hashを参照します。 このチュートリアルでは、デモンストレーションの目的でMD5ハッシュ関数を使用します。

MD5には衝突の問題があり、本番環境では回避する必要があることに注意してください。

次に、という名前のモデルを追加します URL 次のフィールドを使用します。

  • full_url:短縮するURL。
  • url_hash:完全なURLを表す短いハッシュ。
  • clicks:短縮URLにアクセスした回数。
  • created_at:URLが作成された日時。
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)

を生成します url_hash MD5ハッシュアルゴリズムをに適用することによって full_url フィールドとモデルの間に返された最初の10文字だけを使用 save() Djangoがエントリをデータベースに保存するたびに実行されるメソッド。 さらに、URL短縮サービスは通常、リンクがクリックされた回数を追跡します。 これは、メソッドを呼び出すことで実現できます clicked() ユーザーがURLにアクセスしたとき。

上記の操作は、 URL このコードのモデル:

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)

コードを確認したので、 shortener/models.py ファイル:

  1. vim shortener/models.py

コードを次のコンテンツに置き換えます。

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)

必ずファイルを保存して閉じてください。

これらの変更をデータベースに適用するには、次のコマンドを実行して移行を作成する必要があります。

  1. python manage.py makemigrations

これにより、次の出力が得られます。

Output
Migrations for 'shortener': shortener/migrations/0001_initial.py - Create model URL

次に、移行を実行します。

  1. python manage.py migrate

端末に次の出力が表示されます。

Output
Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, shortener Running migrations: Applying shortener.0001_initial... OK

モデルを設定したので、次のステップでGraphQLエンドポイントとクエリを作成します。

ステップ3—クエリの作成

RESTアーキテクチャは、さまざまなエンドポイントでさまざまなリソースを公開し、各エンドポイントには明確に定義されたデータ構造が含まれています。 たとえば、次の場所でユーザーのリストを取得できます。 /api/users、常に同じフィールドを期待します。 一方、GraphQLは、すべての対話に対して単一のエンドポイントを持ち、クエリを使用してデータにアクセスします。 主な(そして最も価値のある)違いは、クエリを使用して1つのリクエスト内ですべてのユーザーを取得できることです。

すべてのURLをフェッチするクエリを作成することから始めます。 いくつか必要なものがあります。

  • 以前に定義したモデルにリンクされたURLタイプ。
  • 名前の付いたQueryステートメント urls.
  • クエリを解決するメソッド。つまり、データベースからすべてのURLをフェッチし、それらをクライアントに返します。

と呼ばれる新しいファイルを作成します shortener/schema.py:

  1. vim shortener/schema.py

Pythonを追加することから始めます import ステートメント:

shorty / shortener / schema.py
import graphene
from graphene_django import DjangoObjectType

from .models import URL

最初の行はメインをインポートします graphene ライブラリ。ベースのGraphQLタイプが含まれています。 List. The DjangoObjectType は任意のDjangoモデルからスキーマ定義を作成するためのヘルパーであり、3行目は以前に作成したものをインポートします URL モデル。

その後、新しいGraphQLタイプを作成します。 URL 次の行を追加してモデル化します。

shorty / shortener / schema.py
...
class URLType(DjangoObjectType):
    class Meta:
        model = URL

最後に、これらの行を追加して、 URL モデル:

shorty / shortener / schema.py
...
class Query(graphene.ObjectType):
    urls = graphene.List(URLType)

    def resolve_urls(self, info, **kwargs):
        return URL.objects.all()

このコードは Query 1つのフィールドという名前のクラス urls、これは以前に定義されたリストです URLType. を介してクエリを解決する場合 resolve_urls メソッドでは、データベースに保存されているすべてのURLを返します。

フル shortener/schema.py ファイルはここに表示されます:

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()

ファイルを保存して閉じます。

これで、すべてのクエリをメインスキーマに追加する必要があります。 それをあなたのすべてのリソースの保有者と考えてください。

に新しいファイルを作成します shorty/schema.py パスを作成し、エディターで開きます。

  1. vim shorty/schema

次の行を追加して、次のPythonパッケージをインポートします。 最初のものは、すでに述べたように、基本のGraphQLタイプを含んでいます。 2行目は、以前に作成したスキーマファイルをインポートします。

shorty / shorty / schema.py
import graphene

import shortener.schema

次に、メインを追加します Query クラス。 継承を介して、作成されたすべてのクエリと将来の操作を保持します。

shorty / shorty / schema.py
...
class Query(shortener.schema.Query, graphene.ObjectType):
    pass

最後に、を作成します schema 変数:

shorty / shorty / schema.py
...
schema = graphene.Schema(query=Query)

The SCHEMA 手順2で定義した設定は、 schema 作成した変数。

フル shorty/schema.py ファイルはここに表示されます:

shorty / shorty / schema.py
import graphene

import shortener.schema


class Query(shortener.schema.Query, graphene.ObjectType):
    pass

schema = graphene.Schema(query=Query)

ファイルを保存して閉じます。

次に、GraphQLエンドポイントと GraphiQL インターフェースを有効にします。これは、GraphQLシステムとの対話に使用されるグラフィカルWebインターフェースです。

を開きます shorty/urls.py ファイル:

  1. vim shorty/urls.py

学習のために、ファイルの内容を削除して保存し、最初から始められるようにします。

追加する最初の行は、Pythonインポートステートメントです。

shorty / shorty / urls.py
from django.urls import path
from django.views.decorators.csrf import csrf_exempt

from graphene_django.views import GraphQLView

The path 関数は、GraphiQLインターフェースのアクセス可能なURLを作成するためにDjangoによって使用されます。 その後、インポートします csrf_exempt、これにより、クライアントはサーバーにデータを送信できます。 完全な説明は、グラフェンドキュメントにあります。 最後の行では、インターフェースを担当する実際のコードを次の方法でインポートしました。 GraphQLView.

次に、という名前の変数を作成します urlpatterns.

shorty / shorty / urls.py
...
urlpatterns = [
    path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
]

これにより、GraphiQLインターフェースをで利用できるようにするために必要なすべてのコードがつなぎ合わされます。 graphql/ 道:

フル shortener/urls.py ファイルはここに表示されます:

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))),
]

ファイルを保存して閉じます。

ターミナルに戻り、 python manage.py runserver コマンド(まだ実行されていない場合):

  1. python manage.py runserver

でWebブラウザを開きます http://localhost:8000/graphql 住所。 次の画面が表示されます。

GraphiQLは、GraphQLステートメントを実行して結果を確認できるインターフェースです。 1つの機能は Docs 右上のセクション。 GraphQLのすべてが型指定されているため、すべての型、クエリ、ミューテーションなどに関する無料のドキュメントを入手できます。

ページを探索した後、メインテキスト領域に最初のクエリを挿入します。

query {
  urls {
    id
    fullUrl
    urlHash
    clicks
    createdAt
  }
}

このコンテンツは、GraphQLクエリの構造を示しています。まず、キーワードを使用します query 一部のデータのみを戻したいことをサーバーに通知します。 次に、 urls で定義されたフィールド shortener/schema.py 内部のファイル Query クラス。 それから、で定義されたすべてのフィールドを明示的に要求します URL GraphQLのデフォルトであるキャメルケーススタイルを使用したモデル。

次に、左上の再生矢印ボタンをクリックします。

まだURLがないことを示す次の応答が表示されます。

Output
{ "data": { "urls": [] } }

これは、GraphQLが機能していることを示しています。 ターミナルで、 CTRL+C サーバーを停止します。

このステップでは、GraphQLエンドポイントを作成し、すべてのURLをフェッチするクエリを作成し、GraphiQLインターフェースを有効にすることで多くのことを達成しました。 次に、データベースを変更するためのミューテーションを作成します。

ステップ4—ミューテーションの作成

ほとんどのアプリケーションには、データを追加、更新、または削除することでデータベースの状態を変更する方法があります。 GraphQLでは、これらの操作はMutationsと呼ばれます。 これらはクエリのように見えますが、引数を使用してサーバーにデータを送信します。

最初のミューテーションを作成するには、 shortener/schema.py:

  1. vim shortener/schema.py

ファイルの最後に、名前の付いた新しいクラスを追加することから始めます CreateURL:

shorty / shortener / schema.py
...
class CreateURL(graphene.Mutation):
    url = graphene.Field(URLType)

このクラスは、 graphene.Mutation GraphQLミューテーションの機能を持つヘルパー。 プロパティ名もあります url、ミューテーションの完了後にサーバーから返されるコンテンツを定義します。 この場合、 URLType データ構造。

次に、という名前のサブクラスを追加します Arguments すでに定義されているクラスへ:

shorty / shortener / schema.py
...
    class Arguments:
        full_url = graphene.String()

これは、サーバーが受け入れるデータを定義します。 ここでは、という名前のパラメータを期待しています full_url とともに String コンテンツ:

次に、次の行を追加して、 mutate 方法:

shorty / shortener / schema.py
...

    def mutate(self, info, full_url):
        url = URL(full_url=full_url)
        url.save()

これ mutate メソッドは、クライアントからデータを受信してデータベースに保存することにより、多くの作業を実行します。 最後に、新しく作成されたアイテムを含むクラス自体を返します。

最後に、 Mutation 次の行を追加して、アプリのすべてのミューテーションを保持するクラス:

shorty / shortener / schema.py
...

class Mutation(graphene.ObjectType):
    create_url = CreateURL.Field()

これまでのところ、名前が付けられたミューテーションは1つだけです。 create_url.

フル shortener/schema.py ファイルはここに表示されます:

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()

ファイルを閉じて保存します。

ミューテーションの追加を完了するには、 shorty/schema.py ファイル:

  1. vim shorty/schema.py

次の強調表示されたコードを含むようにファイルを変更します。

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)

ファイルを保存して閉じます。 ローカルサーバーを実行していない場合は、次のように起動します。

  1. python manage.py runserver

案内する http://localhost:8000/graphql Webブラウザで。 次のステートメントを実行して、GraphiQLWebインターフェースで最初のミューテーションを実行します。

mutation {
  createUrl(fullUrl:"https://www.digitalocean.com/community") {
    url {
      id
      fullUrl
      urlHash
      clicks
      createdAt
    }
  }
}

あなたはミューテーションを createURL 名前、 fullUrl 引数、および内部で定義された応答に必要なデータ url 分野。

出力には、GraphQL内で作成したURL情報が含まれます。 data ここに示すように、フィールド:

Output
{ "data": { "createUrl": { "url": { "id": "1", "fullUrl": "https://www.digitalocean.com/community", "urlHash": "077880af78", "clicks": 0, "createdAt": "2020-01-30T19:15:10.820062+00:00" } } } }

これにより、ハッシュ化されたバージョンのURLがデータベースに追加されました。 urlHash 分野。 最後のステップで作成したクエリを実行して、結果を確認してください。

query {
  urls {
    id
    fullUrl
    urlHash
    clicks
    createdAt
  }
}

出力には、保存されているURLが表示されます。

Output
{ "data": { "urls": [ { "id": "1", "fullUrl": "https://www.digitalocean.com/community", "urlHash": "077880af78", "clicks": 0, "createdAt": "2020-03-18T21:03:24.664934+00:00" } ] } }

同じクエリを実行してみることもできますが、必要なフィールドのみを要求します。

次に、別のURLでもう一度試してください。

mutation {
  createUrl(fullUrl:"https://www.digitalocean.com/write-for-donations/") {
    url {
      id
      fullUrl
      urlHash
      clicks
      createdAt
    }
  }
}

出力は次のようになります。

Output
{ "data": { "createUrl": { "url": { "id": "2", "fullUrl": "https://www.digitalocean.com/write-for-donations/", "urlHash": "703562669b", "clicks": 0, "createdAt": "2020-01-30T19:31:10.820062+00:00" } } } }

これで、システムは短いURLを作成して一覧表示できるようになりました。 次のステップでは、ユーザーが短いバージョンでURLにアクセスできるようにし、正しいページにリダイレクトします。

ステップ5—アクセスエンドポイントを作成する

このステップでは、 Django Views (リクエストを受け取り、レスポンスを返すメソッド)を使用して、にアクセスするすべてのユーザーをリダイレクトします。 http://localhost:8000/url_hash 完全なURLへのエンドポイント。

を開きます shortener/views.py エディターにファイルする:

  1. vim shortener/views.py

まず、内容を次の行に置き換えて2つのパッケージをインポートします。

shorty / shortener / views.py
from django.shortcuts import get_object_or_404, redirect

from .models import URL

これらについては、後で詳しく説明します。

次に、という名前のDjangoビューを作成します root. ファイルの最後に、ビューを担当する次のコードスニペットを追加します。

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)

これはと呼ばれる引数を受け取ります url_hash ユーザーが要求したURLから。 関数内で、最初の行はデータベースからURLを取得しようとします。 url_hash 口論。 見つからない場合は、HTTP 404エラーをクライアントに返します。これは、リソースが欠落していることを意味します。 その後、それは増分します clicked URLエントリのプロパティ。URLがアクセスされた回数を追跡するようにしてください。 最後に、クライアントを要求されたURLにリダイレクトします。

フル shortener/views.py ファイルはここに表示されます:

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)

ファイルを保存して閉じます。

次に、開く shorty/urls.py:

  1. vim shorty/urls.py

次の強調表示されたコードを追加して、 root 意見。

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'),
]

The root ビューはでアクセス可能になります / サーバーのパス、受け入れます url_hash 文字列パラメータとして。

ファイルを保存して閉じます。 ローカルサーバーを実行していない場合は、ローカルサーバーを実行して起動します。 python manage.py runserver 指図。

新しい追加をテストするには、Webブラウザを開いて、 http://localhost:8000/077880af78 URL。 URLの最後の部分は、ステップ5のミューテーションによって作成されたハッシュであることに注意してください。 ハッシュのURLページ(この場合はDigitalOcean Community Webサイト)にリダイレクトされます。

URLリダイレクトが機能するようになったので、Mutationの実行時にエラー処理を実装することで、アプリケーションをより安全にします。

ステップ6—エラー処理の実装

開発者は通常、サーバーに送信される内容を制御しないため、エラーの処理はすべてのアプリケーションのベストプラクティスです。 この場合、障害を予測し、その影響を最小限に抑えることができます。 GraphQLのような複雑なシステムでは、クライアントが間違ったデータを要求したり、サーバーがデータベースにアクセスできなくなったりするなど、多くのことがうまくいかない可能性があります。

型付きシステムとして、GraphQLは、 SchemaValidationと呼ばれる操作でクライアントが要求および受信するすべてのものを検証できます。 存在しないフィールドを使用してクエリを作成することで、これが実際に動作していることを確認できます。

案内する http://localhost:8000/graphql ブラウザでもう一度、GraphiQLインターフェイス内で次のクエリを実行します。 iDontExist 分野:

query {
  urls {
    id
    fullUrl
    urlHash
    clicks
    createdAt
    iDontExist
  }
}

ないので iDontExist クエリで定義されたフィールド、GraphQLはエラーメッセージを返します:

Output
{ "errors": [ { "message": "Cannot query field \"iDontExist\" on type \"URLType\".", "locations": [ { "line": 8, "column": 5 } ] } ] }

GraphQLタイプのシステムでは、スキーマですでに定義されている情報だけを送受信することが目的であるため、これは重要です。

現在のアプリケーションは、任意の文字列を受け入れます full_url 分野。 問題は、誰かが不適切に構成されたURLを送信した場合、保存された情報を試すときにユーザーをどこにもリダイレクトしないことです。 この場合、次のことを確認する必要があります full_url データベースに保存する前に適切にフォーマットされており、エラーが発生した場合は、 GraphQLError カスタムメッセージの例外。

この機能を2つのステップで実装しましょう。 まず、 shortener/models.py ファイル:

  1. vim shortener/models.py

インポートセクションに強調表示された行を追加します。

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
...

The URLValidator URL文字列とを検証するためのDjangoヘルパーです GraphQLError グラフェンは、カスタムメッセージで例外を発生させるために使用します。

次に、データベースに保存する前に、ユーザーが受信したURLを必ず検証してください。 強調表示されたコードをに追加して、この操作を有効にします shortener/models.py ファイル:

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)

まず、このコードはインスタンス化します URLValidator の中に validate 変数。 内部 try/except ブロック、あなた validate() 受信したURLを上げて GraphQLError とともに invalid url 何か問題が発生した場合のカスタムメッセージ。

フル shortener/models.py ファイルはここに表示されます:

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)

ファイルを保存して閉じます。 ローカルサーバーを実行していない場合は、 python manage.py runserver 指図。

次に、で新しいエラー処理をテストします。 http://localhost:8000/graphql. 無効なURLで新しいURLを作成してみてください full_url GraphiQLインターフェースの場合:

mutation {
  createUrl(fullUrl:"not_valid_url"){
    url {
      id
      fullUrl
      urlHash
      clicks
      createdAt
    }
  }
}

無効なURLを送信すると、カスタムメッセージで例外が発生します。

Output
{ "errors": [ { "message": "invalid url", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createUrl" ] } ], "data": { "createUrl": null } }

あなたがあなたのターミナルを見れば python manage.py runserver コマンドが実行されている場合、エラーが表示されます:

Output
... graphql.error.located_error.GraphQLLocatedError: invalid url [30/Jan/2020 19:46:32] "POST /graphql/ HTTP/1.1" 200 121

GraphQLエンドポイントは常にHTTP200ステータスコードで失敗します。これは通常、成功を意味します。 GraphQLはHTTPの上に構築されていますが、RESTのようにHTTPステータスコードやHTTPメソッドの概念を使用していないことに注意してください。

エラー処理が実装されたので、クエリをフィルタリングするメカニズムを導入して、サーバーから返される情報を最小限に抑えることができます。

ステップ7—フィルターの実装

URL短縮サービスを使用して独自のリンクを追加し始めたと想像してください。 しばらくすると、エントリが非常に多くなり、適切なエントリを見つけるのが難しくなります。 この問題は、フィルターを使用して解決できます。

フィルタリングはRESTAPIの一般的な概念であり、通常、フィールドと値を持つクエリパラメーターがURLに追加されます。 例として、 jojo という名前のすべてのユーザーをフィルタリングするには、次のように使用できます。 GET /api/users?name=jojo.

GraphQLでは、クエリ引数をフィルターとして使用します。 それらは、すてきでクリーンなインターフェースを作成します。

クライアントがURLを名前でフィルタリングできるようにすることで、「URLが見つからない」問題を解決できます。 full_url 分野。 これを実装するには、 shortener/schema.py お気に入りのエディタでファイルします。

  1. vim shortener/schema.py

まず、インポートします Q 強調表示された行のメソッド:

shorty / shortener / schema.py
import graphene
from graphene_django import DjangoObjectType
from django.db.models import Q

from .models import URL
...

これは、データベースクエリをフィルタリングするために使用されます。

次に、全体を書き直します Query 次の内容のクラス:

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
...

行っている変更は次のとおりです。

  • 追加する url 内部のフィルターパラメーター urls 変数と resolve_url 方法。
  • 内部 resolve_urls、という名前のパラメータの場合 url が指定されている場合、データベースの結果をフィルタリングして、指定された値を含むURLのみを返します。 Q(full_url__icontains=url) 方法。

フル shortener/schema.py ファイルはここに表示されます:

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() 

ファイルを保存して閉じます。 ローカルサーバーを実行していない場合は、 python manage.py runserver.

で最新の変更をテストします http://localhost:8000/graphql. GraphiQLインターフェースで、次のステートメントを記述します。 communityという単語ですべてのURLをフィルタリングします。

query {
  urls(url:"community") {
    id
    fullUrl
    urlHash
    clicks
    createdAt
  }
}

1つのURLを追加しただけなので、出力は1つのエントリのみです。 community その中の文字列。 以前にURLを追加した場合、出力は異なる場合があります。

Output
{ "data": { "urls": [ { "id": "1", "fullUrl": "https://www.digitalocean.com/community", "urlHash": "077880af78", "clicks": 1, "createdAt": "2020-01-30T19:27:36.243900+00:00" } ] } }

これで、URLを検索できるようになりました。 ただし、リンクが多すぎると、クライアントは、URLリストがアプリで処理できるよりも多くのデータを返していると不満を言う可能性があります。 これを解決するには、ページネーションを実装します。

ステップ8—ページネーションの実装

バックエンドを使用しているクライアントは、応答時間が長すぎる、またはURLエントリが多すぎると、応答時間が大きすぎると不満を言う可能性があります。 あなたのデータベースでさえ、膨大な情報のセットをまとめるのに苦労するかもしれません。 この問題を解決するために、 pagination と呼ばれる手法を使用して、クライアントが各リクエスト内で必要なアイテムの数を指定できるようにすることができます。

この機能を実装するデフォルトの方法はありません。 REST APIでも、名前と動作が異なるHTTPヘッダーまたはクエリパラメーターに表示される場合があります。

このアプリケーションでは、URLクエリに対してさらに2つの引数を有効にすることで、ページ付けを実装します。 firstskip. first 要素の最初の可変数を選択し、 skip 最初からスキップする要素の数を指定します。 たとえば、 first == 10skip == 5 最初の10個のURLを取得しますが、そのうち5個をスキップして、残りの5個だけを返します。

このソリューションの実装は、フィルターの追加に似ています。

を開きます shortener/schema.py ファイル:

  1. vim shortener/schema.py

ファイルで、 Query 2つの新しいパラメータをに追加してクラスを作成します urls 変数と resolve_urls 次のコードで強調表示されているメソッド:

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
...

このコードは新しく作成されたものを使用します firstskip 内部のパラメータ resolve_urls データベースクエリをフィルタリングするメソッド。

ファイルを保存して閉じます。 ローカルサーバーを実行していない場合は、 python manage.py runserver.

ページネーションをテストするには、次のGraphiQLインターフェイスで次のクエリを発行します。 http://localhost:8000/graphql:

query {
  urls(first: 2, skip: 1) {
    id
    fullUrl
    urlHash
    clicks
    createdAt
  }
}

URL短縮サービスは、データベースに作成された2番目のURLを返します。

Output
{ "data": { "urls": [ { "id": "2", "fullUrl": "https://www.digitalocean.com/write-for-donations/", "urlHash": "703562669b", "clicks": 0, "createdAt": "2020-01-30T19:31:10.820062+00:00" } ] } }

これは、ページネーション機能が機能していることを示しています。 URLを追加し、さまざまなセットをテストして、自由に試してみてください。 firstskip.

結論

GraphQLエコシステム全体が日々成長しており、その背後には活発なコミュニティがあります。 GitHubやFacebookなどの企業によって本番環境に対応していることが証明されており、このテクノロジーを独自のプロジェクトに適用できるようになりました。

このチュートリアルでは、クエリやミューテーションなどの概念を使用して、GraphQL、Python、Djangoを使用してURL短縮サービスを作成しました。 しかしそれ以上に、DjangoWebフレームワークを使用してWebアプリケーションを構築するためにこれらのテクノロジーに依存する方法を理解できました。

ここで使用されているGraphQLとツールの詳細については、GraphQLWebサイトおよびGrapheneドキュメントWebサイトを参照してください。 また、DigitalOceanには、PythonおよびDjangoの追加のチュートリアルがあり、どちらかについて詳しく知りたい場合に使用できます。