Пример реализации асинхронных запросов API

Примечание

Этот материал находится в состоянии наполнения информацией.

Общие сведения

Асинхронные запросы - это запросы с отложенным ответом от сервера. Т.е. можно совершить несколько запросов друг за другом не дожидаясь ответов на них сразу после совершения запроса. В Web для реализации такого способа обмена обычно используется технология websocket. Для реализации асинхронных запросов API по протоколу websocket у нас применён компонент Django-channels-graphql-ws.

Программная реализация

Реализация асинхронности через websocket совершенно идентична реализации для синхронных запросов.

Программная реализация подписки

Подписка (subscription - запрос с отложенным ответом при возникновении какого-либо события) очень похожа на реализацию схем и мутаций для GraphQL API. Рассмотрим для примера создание подписки на действия в чате. Для начала создадим файл mesagxilo/api/[subscription.py](http://subscription.py) с описанием объектов подписки с подобным содержимым:

import channels_graphql_ws
import graphene

from graphql import GraphQLError
from uzantoj.api.schema import UzantoNode
from .schema import MesagxiloBabilejoNode, MesagxiloMesagxoNode, MesagxiloInvestojNode
from ..models import MesagxiloBabilejo, MesagxiloMesagxo, MesagxiloPartoprenanto
from uzantoj.models import Uzanto

# Реализация подписки для чатов
# Объединение возвращаемых объектов действий
class MesagxiloEventojUnion(graphene.Union):
    class Meta:
        # Типы объединения
        types = (
            UzantoNode,
            MesagxiloMesagxoNode,
            MesagxiloInvestojNode,
        )

# Подписки для WebSocket
class MesagxiloEventoj(channels_graphql_ws.Subscription):
    """Подписка на действия в чатах"""
    evento = graphene.String(required=True)
    babilejo = graphene.relay.node.Field(MesagxiloBabilejoNode)
    objekto = graphene.Field(MesagxiloEventojUnion)

    class Arguments:
        """Тут аргументы, передающиеся для подписки на события чатов"""
        # Список ID чатов
        babilejoj = graphene.List(graphene.Int, required=True)

    def subscribe(self, info, babilejoj):
        """В этом методе определятся список чатов для подписки на их действия"""
        if info.context.user.is_authenticated:
            babilejoj_res = MesagxiloPartoprenanto.objects.filter(
                partoprenanto=info.context.user, forigo=False, arkivo=False, publikigo=True,
                babilejo__id__in=babilejoj
            )

            babilej_set = set(
                babilejoj_res.values_list('babilejo__id', flat=True)
            )

            if not babilejoj_res:
                raise GraphQLError(
                    'Невозможно отслеживать чаты c ID {}, они не сущствуют или не доступны'.format(
                        set(babilejoj) - babilej_set
                    )
                )

            # Тут можно сделать проверку на количество заявленных и фактически найденных чатов,
            # в которых пользователь участвует
            return list(map(str, babilej_set)) or None

        raise GraphQLError('Необходима авторизация')

    [@staticmethod](profile/staticmethod)
    def publish(payload, info, babilejoj):
        evento = payload.get('evento')

        try:
            babilejo = MesagxiloBabilejo.objects.get(
                uuid=payload.get('babilejo'),
                publikigo=True,
                forigo=False,
                arkivo=False
            )
            if evento in ('nova_mesagxo', 'redaktita_mesagxo'):
                objekto = MesagxiloMesagxo.objects.get(
                    uuid=payload.get('mesagxo'),
                    publikigo=True,
                    arkivo=False,
                    forigo=False
                )
                autoro = objekto.posedanto
            else:
                objekto = Uzanto.objects.get(
                    id=payload.get('uzanto')
                )
                autoro = objekto

            if autoro.id == info.context.user.id:
                # Пропускаем публикацию события для автора события :)
                return MesagxiloEventoj.SKIP

            return MesagxiloEventoj(evento=evento, babilejo=babilejo, objekto=objekto)
        except (MesagxiloBabilejo.DoesNotExist, Uzanto.DoesNotExist, MesagxiloMesagxo.DoesNotExist):
            pass

        return MesagxiloEventoj.SKIP

    [@classmethod](profile/classmethod)
    def publikigi(cls, babilejo, mesagxo):
        """Этот метод должен вызываться при публикации нового сообщения в чате"""
        cls.broadcast(
            group='{}'.format(babilejo.id),
            payload={
                'evento': 'nova_mesagxo',
                'babilejo': str(babilejo.uuid),
                'mesagxo': str(mesagxo.uuid)
            }
        )

    [@classmethod](profile/classmethod)
    def redakti(cls, babilejo, mesagxo):
        """Этот метод должен вызываться при редактировании сообщения в чате"""
        cls.broadcast(
            group=babilejo.id,
            payload={
                'evento': 'redaktita_mesagxo',
                'babilejo': babilejo.uuid,
                'mesagxo': mesagxo.uuid
            }
        )

    [@classmethod](profile/classmethod)
    def presajoj(cls, babilejo, uzanto):
        """Этот метод должен вызываться при редактировании сообщения в чате"""
        cls.broadcast(
            group=babilejo.id,
            payload={
                'evento': 'presajoj',
                'babilejo': babilejo.uuid,
                'uzanto': uzanto.id
            }
        )

class MesagxiloSubscription(graphene.ObjectType):
    mesagxilo_eventoj = MesagxiloEventoj.Field()