0
Обложка: Строим взаимодействие систем на PHP с помощью GraphQL

Строим взаимодействие систем на PHP с помощью GraphQL

Человечество уже давно выработало для себя множество различных систем коммуникации: речь, символы, жесты, письменность и т. д., которые позволяют представителям разных сообществ находить общий язык. Информационные системы в компаниях тоже нуждаются в возможностях для взаимодействия: приеме и передаче информации понятным для всех элементов образом. Сегодня поговорим о том, как построить архитектуру таких взаимодействий, используя GraphQL.

Сергей Тарасов
Сергей Тарасов
К. т. н., руководитель команд разработки, Группа НЛМК
Владимир Шершнев
Владимир Шершнев
Старший разработчик, Группа НЛМК

Небольшой дисклеймер

Статья в основном рассчитана на архитекторов/интеграторов/аналитиков/разработчиков, которые не просто рассматривают информационную систему с точки зрения «кода», а учитывают полный жизненный цикл, включая поддержку, развитие, систему управления знаниями и т. д.

Материал носит информационный характер, основанный на опыте и реальных системах, которые успешно введены в промышленную эксплуатацию, и не призывает слепо использовать те или иные технологии и методы.

Что такое или кто такой ЕКП?

ЕКП — это пример информационной системы Группы НЛМК. Расшифровывается как Единый корпоративный портал, единое окно предоставления цифровых услуг всем сотрудникам Группы.

Немого информации о портале:

  • >100 различных интерфейсов взаимодействия с другими системами;
  • >25 серверов с различными ландшафтами и функциями;
  • языки программирования: бэк — PHP, фронт — JS, HTML, CSS;
  • три языка: французский, датский, русский;более 80 сервисов из различных функциональных направлений, которые сами по себе являются полноценными ИС со своей бизнес-логикой, механизмами интеграций и дорожными картами (roadmap):
  1. новости,
  2. сообщества,
  3. блоги,
  4. лендинги,
  5. адресная книга и структура организации,
  6. прием делегаций,
  7. заказ переводческих услуг,
  8. расчетный лист,
  9. карта выявления опасностей,
  10. поведенческие диалоги безопасности,
  11. корпоративный университет,
  12. сервис оповещений,
  13. система управления знаниями — HR-гид и т. д.;
  • более 30 сайтов-визиток для различных ведомств и департаментов Группы;
  • >46 000 уникальных пользователей портала;
  • >4500 департаментов в организационной структуре.

Интеграционные взаимодействия

Изначально портал (~2017 год) был в основном потребителем информации, т. е. забирал данные из других систем и понемногу формировал свою базу знаний с помощью уникальных сервисов, которые были реализованы только на нем. Интеграционные взаимодействия, как правило, строились на базе REST-архитектуры с использованием curl или своих обработчиков на базе протокола https. Это был осознанный выбор, базировался он на следующих принципах.

  • Относительно невысокий порог вхождения в архитектуру REST и написание «простых» http(s) запросов. На рынке уже есть множество различных решений:
    1. стандартные библиотеки и функционал PHP (curl, http-запросы);
    2. библиотеки с подробными мануалами по использованию (Guzzle, Slim и т. д.).
  • Гибкость — под каждую интеграцию можно написать свою реализацию, например, обмен с помощью XML, json и других объектов.
  • Необходимость научить новую систему сначала «играть по текущим правилам», прежде чем их менять или диктовать свои. Портал только создавался и выступал в качестве «потребителя», а ряд систем с мастер-данными существовали уже давно со своими устоявшимися процессами и правилами (например, кадровый источник информации на базе SAP HCM).

По мере обогащения информацией и формирования собственной уникальной базы знаний стали появляться «потребители» — другие ИС. Информация была им нужна для различных целей: некоторым для популяризации своего сервиса путем вывода новостей/блогов/сообществ о компании, некоторым для расширения собственной базы знаний о сотрудниках и т. д. Таким образом, ЕКП стал не только «потребителем», но и «поставщиком».

Далее в качестве примера рассмотрим интеграцию с мобильным приложением Группы на базе получения/публикации новостей и сопроводительной информации по ним (рубрики, комментарии, просмотры и т. д.).

«Потребитель» VS «поставщик» с точки зрения ИС

Если раньше портал понимал, откуда забирать информацию и в каком виде, то в роли «поставщика» появилась большая неизвестность, стало неясно:

  • количество возможных «потребителей»;
  • платформа и технологический стек «потребителя», т. е. непонятно, как с ним взаимодействовать;
  • для чего и какая информация может потребоваться.

Исходя из вышеописанного, можно сделать вывод, что необходимо разработать собственные и единые «правила игры» для всех потенциальных «потребителей«.

Правила игры

Система-поставщик должна:

  1. Быть единообразной: работать одинаково для всех «потребителей» без исключения.
  2. Использовать один запрос на получение всей необходимой информации (one-step).

«Step-by-Step» VS «One-Step»

Объекты предоставляемой информации имеют связность с другими объектами. Например, у каждой новости есть рубрика, комментарии, просмотры и авторы (рис. 1).

Рис. 1. ERP-структура новостей

Информацию можно получать двумя различными способами (рис. 2).

  • Step-by-step — шаг за шагом, т. е. формируя несколько запросов, из которых складывается общая картина. Например, получить новость ->рубрики->просмотры->комментарии и т. д.
  • One-step — за один шаг, т. е. формируя один запрос. Например, получить новость сразу со всеми связанными элементами.

Рис. 2. Step-by-step & one-step

Множество HTTP-запросов по каждому объекту (step-by-step) вызовет:

  • снижение производительности — из-за высокой нагрузки на сеть растет время анализа запросов и формирования ответов, а вместе с этим снижается скорость работы для конечного пользователя и т. д.;
  • сложность поддержки — из-за множества методов под каждый объект и их кросc-функционального взаимодействия.

Поэтому необходим такой язык взаимодействия, который бы позволял однозначно сформулировать на входе все свои требования, а на выходе получать результат (one-step).

GraphQL

В ходе поиска инструмента под вышеописанные правила даже были попытки разработать собственный, но в конечном итоге выбор пал на GraphQL.

GraphQL — это язык запросов с открытым исходным кодом.

Три основные характеристики языка:

  1. Позволяет клиенту точно указать, какие данные ему нужны.
  2. Облегчает агрегацию данных из нескольких источников.
  3. Использует систему типов для описания данных.

В качестве основы мы стали использовать библиотеку GraphQL, которая соответствует изначально заложенной спецификации, имеет неплохую документацию и постоянно развивается.

Процесс установки зависимостей достаточно хорошо описан в документации, поэтому перейдем сразу к правилам.

Конечная точка GraphQL

Для того чтобы мы могли принимать запросы, определим единую точку входу graphql: mysite.ru/graphql/. Для этого создаем путь /graphql/index.php. Данный файл является единой точкой входа, который подключает библиотеку GraphQL и может содержать в себе следующую логику.

  • Создание схемы — используется класс GraphQLTypeSchema, в конструктор класса необходимо передать типы данных для запросов query и mutation.
  • Обработка запроса — используется метод GraphQLGraphQL:executeQuery, в конструктор класса необходимо передать созданную схему и полученный запрос.
  • Обработка ошибок — в случае ошибки выбрасывается исключение. Исключения являются экземплярами класса GrahpQlException. В случае отлова исключения в ответе возвращается статус ошибки, сообщение ошибки и место возникновения ошибки.
  • Вывод ответа — ответ отдается в формате json, в случае успеха ключом json-массива будет data, если было выброшено исключение, ключом массива будет errors.

Ниже еще рассмотрим некоторые сценарии более детально.

Для передачи запросов в наших примерах будем использовать приложение «Postman». Для работы с ним существует много документации, интерфейс интуитивно понятен, потому эту тему затрагивать не будем.

Для обработки запросов в /graphql/index.php добавим следующий код:

<?php
require_once realpath('../vendor/autoload.php');
use GraphQLTypeSchema;
use NLMKGraphQLAppTypeTypes;
try {
    $rawInput = file_get_contents('php://input');
    $input = json_decode($rawInput, true);
    $query = $input['query'];
  //формируем схему
    $schema = new Schema([
        'query' => Types::query() //определили класс для query запросов
    ]);
  //Выполняем сформированный запрос $query к нашей схеме $schema через GraphQL
  $result = GraphQL::executeQuery($schema, $query);
    $arException = $result->toArray()['errors'] ?: [];
} catch (Throwable $e) {
    $result = [
        'error' => [
            'message' => $e->getMessage()
        ]
    ];
    $arException = [
        'message' => $e->getMessage(),
        'code' => $e->getCode(),
        'file' => $e->getFile(),
        'line' => $e->getLine(),
        'trace' => $e->getTraceAsString(),
    ];
}
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($result);

Вначале мы подключаем автозагрузчик. Оборачиваем в try catch наш код для отлова ошибок и возвращаем результат в формате json.

Внутри try у нас добавлена инициализация схемы. Схема может иметь несколько типов запросов:

Query — запрос на получение;

Mutation — запрос на изменение.

Для начала реализуем запрос на получение данных.

Типы запросов GraphQL — Query

Для удобства создаем необходимую структуру файлов и директорий:

Рис. 3. Рекомендуемая структура

QueryType.php — список Query-запросов.

Создадим тестовую базу данных (БД) и наполним ее данными. Получилась следующая схема:

Рис. 4. Схема базы данных

Добавим следующий код в файл AppTypeQueryType.php :

<?php
//файл AppTypeQueryType.php
namespace NLMKGraphQLAppType;
use GraphQLTypeDefinitionObjectType;
use NLMKGraphQLAppEntityNews;
/**
 * Class QueryType
 *
 * Тип Query для GraphQL
 *
 * @package NLMKGraphQLAppType
 */
class QueryType extends ObjectType
{
    /**
     * QueryType constructor.
     */
    public function __construct()
    {
        $config = [
            'fields' => function () {
                return [
                    'news' => [
                        'type' => Types::listOf(Types::news()), // новый тип - новость, возвращаем в виде списка
                        'args' => [
                            'id' => Types::int(), //аргумент для отбора элементов из БД
                        ],
                        'resolve' => function ($root, $args, $context, $info) {
                            return News::get($args); //Реализуем класс сущности "Новость" и метод для получения данных из таблицы "news"
                        }
                    ]
                ];
            }
        ];
        parent::__construct($config);
    }
}

Конструктор класса «QueryType» будет содержать в себе все подтипы «query»-запросов, он должен быть наследован от «ObjectType», что позволяет нам создавать свои составные типы данных.

В данном примере у нас возвращается запрос «news», который имеет тип «Types::listOf(Types::news())» — т.е. список новостей. В качестве принимаемых параметров фильтра «args» у нас передается «id», тип «Types::int()».

Ключ «resolve» возвращает результат «News::get($args)».

Создадим класс для работы с сущностью «News» и опишем возвращаемые поля запроса NewsType.

Создадим файл AppTypeNewsType.php и добавим в него код:

<?php
//файл AppTypeNewsType.php
namespace NLMKGraphQLAppType;
use GraphQLTypeDefinitionObjectType;
/**
 * Class NewsType
 *
 * Тип News для GraphQL
 *
 * @package NLMKGraphQLAppType
 */
class NewsType extends ObjectType
{
    /**
     * NewsType constructor.
     */
    public function __construct()
    {
        $config = [ //описываем допустимые поля запроса для нового типа данных news
            'fields' => function () {
                return [
                    'id' => [
                        'type' => Types::int(),
                    ],
                    'name' => [
                        'type' => Types::string(),
                    ],
                    'text' => [
                        'type' => Types::string(),
                    ],
                    'create_date' => [
                        'type' => Types::string(),
                    ],
                    'rubric' => [
                        'type' => Types::int(),
                    ],
                    'publish_date' => [
                        'type' => Types::string(),
                    ],
                    'preview_text' => [
                        'type' => Types::string(),
                    ],
                    'author' => [
                        'type' => Types::int(),
                    ],
                ];
            }
        ];
        parent::__construct($config);
    }
}

Теперь нам необходимо реализовать метод «News::get($args)» для возврата результата.

Создадим файл AppEntityNews.php и добавим в него код:

<?php
//файл AppEntityNews.php
namespace NLMKGraphQLAppEntity;
use NLMKGraphQLAppDBConnectDB;
/**
 * Класс для работы с сущностью Новость
 *
 * Class News
 * @package NLMKGraphQLAppEntity
 */
class News
{
    /**
     * Запрос на получение значений из таблицы news
     * с фильтром по id
     *
     * @param $args
     * @return mixed
     */
    public static function get($args)
    {
        $DB = new ConnectDB();
        return $DB->query("SELECT * from news WHERE id = {$args['id']}");
    }
}

Ключи возвращаемого массива должны быть в нижнем регистре, чтобы произошел корректный маппинг, так как в таблице мы добавили ключи в БД в верхнем регистре, а описали в нижнем.

В данном примере конвертация из верхнего регистра в нижний производится в методе $DB->query и детально не рассматривается.

Отправим тестовый запрос на получение новостей из приложения:

Рис. 5. Запрос на получение новости с ID: 4 и возврат полей: id, name, text

В результате запроса мы получили ответ в виде json-объекта. Мы запросили возврат полей id, name, text (эти поля мы описали в файле «AppTypeNewsType.php«).

Составной запрос (step-by-step & one-step)

Если мы посмотрим на рисунок 4, где изображена наша схема базы данных, то увидим, что, зная ID новости, мы можем получить рубрику.

Добавим еще один запрос на получение рубрик в файл AppTypeQueryType.php :

<?php
//файл AppTypeQueryType.php
namespace NLMKGraphQLAppType;
use GraphQLTypeDefinitionObjectType;
use NLMKGraphQLAppEntityNews;
use NLMKGraphQLAppEntityRubric;
/**
 * Class QueryType
 *
 * Тип Query для GraphQL
 *
 * @package NLMKGraphQLAppType
 */
class QueryType extends ObjectType
{
    /**
     * QueryType constructor.
     */
    public function __construct()
    {
        $config = [
            'fields' => function () {
                return [
                    'news' => [
                        'type' => Types::listOf(Types::news()),
                        'args' => [
                            'id' => Types::int(),
                        ],
                        'resolve' => function ($root, $args, $context, $info) {
                            return News::get($args);
                        }
                    ],
                    'rubric' => [ //Добавили еще один запрос
                        'type' => Types::rubric(), // Создаем новый составной тип данных - рубрика
                        'args' => [
                            'id' => Types::int(), // Добавили аргумент, по которому мы можем получить рубрику
                        ],
                        'resolve' => function ($root, $args, $context, $info) {
                            return Rubric::get($args); // Добавляем метод get для получения результата запроса
                        }
                    ]
                ];
            }
        ];
        parent::__construct($config);
    }
}

Затем регистрируем наш новый тип данных в файле AppTypeTypes.php, и он принимает уже следующий вид:

<?php
//файл AppTypeTypes.php
namespace NLMKGraphQLAppType;
use GraphQLTypeDefinitionType;
class Types extends Type
{
    private static $query;
    private static $news;
    private static $rubric;
    public static function query()
    {
        return self::$query ?: (self::$query = new QueryType());
    }
    public static function news()
    {
        return self::$news ?: (self::$news = new NewsType());
    }
    public static function rubric() // Зарегистрировали новый тип
    {
        return self::$rubric ?: (self::$rubric = new RubricType()); // new RubricType() - необходимо описать поля этого типа
    }
}

Так как при добавлении типа rubric мы вызвали конструктор «new RubricType()», необходимо описать поля этого типа данных.

В файле AppTypeRubricType.php опишем возвращаемые поля согласно таблице «rubrics»:

<?php
//файл AppTypeRubricType.php
namespace NLMKGraphQLAppType;
use GraphQLTypeDefinitionObjectType;
/**
 * Class RubricType
 *
 * Тип Rubric для GraphQL
 *
 * @package NLMKGraphQLAppType
 */
class RubricType extends ObjectType
{
    /**
     * RubricType constructor.
     */
    public function __construct()
    {
        $config = [
            'fields' => function () {
                return [ //описали поля таблицы и указали их типы
                    'id' => [
                        'type' => Types::int(),
                    ],
                    'name' => [
                        'type' => Types::string(),
                    ],
                    'code' => [
                        'type' => Types::string(),
                    ],
                ];
            }
        ];
        parent::__construct($config);
    }
}

Добавляем, как уже делали ранее, класс для работы с сущностью Rubric и реализуем метод Rubric::get($args) в файле AppEntityRubric.php:

<?php
//файл AppEntityRubric.php
namespace NLMKGraphQLAppType;
use NLMKGraphQLAppDBConnectDB;
class Rubric
{
    /**
     * Метод возвращает результат из таблицы рубрик
     *
     * @param $args
     * @return mixed
     */
    public static function get($args)
    {
        $DB = new ConnectDB();
        return $DB->query("SELECT * from rubrics WHERE id = {$args['id']}")[0];
    }
}

Для проверки отправим запрос на получение рубрик:

Рис. 6. Запрос на прямое получение рубрики с ID: 2

Нам вернулся корректный ответ, но сейчас, чтобы получить рубрику новости, необходимо послать два запроса: сначала получить поля новости, затем послать запрос на получение рубрики, зная ее ID (step-by-step).

Реализуем составной запрос, чтобы мы могли получить эти же данные при одном запросе (one-step).

Поменяем код в файле AppTypeNewsType.php там, где у нас были описаны возвращаемые поля типа «rubric»:

<?php
//файл AppTypeNewsType.php
namespace NLMKGraphQLAppType;
use GraphQLTypeDefinitionObjectType;
use NLMKGraphQLAppEntityRubric;
/**
 * Class NewsType
 *
 * Тип News для GraphQL
 *
 * @package NLMKGraphQLAppType
 */
class NewsType extends ObjectType
{
    /**
     * NewsType constructor.
     */
    public function __construct()
    {
        $config = [
            'fields' => function () {
                return [
                    'id' => [
                        'type' => Types::int(),
                    ],
                    'name' => [
                        'type' => Types::string(),
                    ],
                    'text' => [
                        'type' => Types::string(),
                    ],
                    'create_date' => [
                        'type' => Types::string(),
                    ],
                    'rubric' => [  //описали вложенный запрос на получение рубрики новости
                        'type' => Types::rubric(),
                        'resolve' => function ($root, $args, $context, $info) {
                            //передаем в метод параметры для отбора рубрик по родительским возвращаемым полям, согласно нашей схеме БД
                            return Rubric::get(['id' => $root['rubric']]);
                        }
                    ],
                    'publish_date' => [
                        'type' => Types::string(),
                    ],
                    'preview_text' => [
                        'type' => Types::string(),
                    ],
                    'author' => [
                        'type' => Types::int(),
                    ],
                ];
            }
        ];
        parent::__construct($config);
    }
}

Отправляем запрос на получение новости и указываем поля рубрики, которые хотим вернуть:

Рис. 7. Запрос на получение новости с ID: 4 и связанной рубрики

В результате запроса мы получили объект новости, у которого был выполнен составной запрос на получение рубрики.

Теперь мы можем получать как отдельно рубрику, указав ее ID в параметрах запроса (рис. 6), так и составным запросом (рис. 7).

По аналогии мы можем реализовать получение автора новости, просмотры и комментарии.

Типы запросов GraphQL — Mutation

Запросы типа Mutation позволяют менять данные в нашей БД.

Добавим возможность обрабатывать запросы на изменение в нашем файле /graphql/index.php, изменив схему:

//файл graphqlindex.php
try {
    $rawInput = file_get_contents('php://input');
    $input = json_decode($rawInput, true);
    $query = $input['query'];
    $schema = new Schema([
        'query' => Types::query(),
        'mutation' => Types::mutation() // добавим возможность обрабатывать запросы на изменение
    ]);
    $result = GraphQL::executeQuery($schema, $query);
    $arException = $result->toArray()['errors'] ?: [];
} catch (Throwable $e) {

По аналогии c Query-типом зарегистрируем наш тип в файле AppTypeTypes.php:

//файл AppTypeTypes.php

private static $mutation;
public static function mutation() //Зарегистрировали новый тип данных
{
     return self::$mutation ?: (self::$mutation = new MutationType());
}

После этого создаем файл AppTypeMutationType.php. В нем опишем тип запроса «add_comment» для добавления комментария к новости:

<?php
//файл AppTypeMutationType.php
namespace NLMKGraphQLAppType;
use GraphQLTypeDefinitionObjectType;
use NLMKGraphQLAppEntityComments;

/**
 * Class MutationType
 *
 * Тип Mutation для GraphQL
 *
 * @package NLMKGraphQLAppType
 */
class MutationType extends ObjectType
{
    /**
     * MutationType constructor.
     */
    public function __construct()
    {
        $config = [
            'fields' => function () {
                return [
                    'add_comment' => [
                        'type' => Types::comments(), //необходимо зарегистрировать тип и описать поля
                        'args' => [
                            'news_id' => Types::int(),
                            'text' => Types::string(),
                            'author' => Types::int(),
                            'date_create' => Types::string(),
                        ],
                        'resolve' => function ($root, $args, $context, $info) {
                            return Comments::set($args); // Реализуем метод Comments::set для добавления комментария
                        }
                    ]
                ];
            }
        ];
        parent::__construct($config);
    }
}

Зарегистрируем тип «comments» в файле AppTypeTypes.php:

//файл AppTypeTypes.php
private static $comment;
public static function comments()
{
    return self::$comment ?: (self::$comment = new CommentType());
}

Опишем возвращаемые поля для комментариев в файле AppTypeCommentType.php:

<?php
//файл AppTypeCommentType.php
namespace NLMKGraphQLAppType;
use GraphQLTypeDefinitionObjectType;
/**
 * Class CommentType
 *
 * Тип comment для GraphQL
 *
 * @package NLMKGraphQLAppType
 */
class CommentType extends ObjectType
{
    /**
     * CommentType constructor.
     */
    public function __construct()
    {
        $config = [
            'fields' => function () {
                return [
                    'id' => [
                        'type' => Types::int(),
                    ],
                    'text' => [
                        'type' => Types::string(),
                    ],
                    'date_create' => [
                        'type' => Types::string(),
                    ],
                    'source_id' => [
                        'type' => Types::int(),
                    ],
                    'author' => [
                        'type' => Types::int(),
                    ]
                ];
            }
        ];
        parent::__construct($config);
    }
}

Добавляем класс для работы с сущностью «комментарий» AppEntityComments.php и реализуем в нем метод «Comments::set»:

<?php
//файл AppEntityComments.php
namespace NLMKGraphQLAppEntity;
use NLMKGraphQLAppDBConnectDB;
/**
 * Class Comments
 * @package NLMKGraphQLAppEntity
 */
class Comments
{
    /**
     * @param $args
     * @return mixed
     */
    public static function set($args)
    {
        $DB = new ConnectDB();
        $newCommentID = $DB->query("INSERT INTO comments (TEXT, AUTHOR, DATE_CREATE, SOURCE_ID) VALUES
            ('{$args['text']}', {$args['author']}, '{$args['date_create']}', {$args['news_id']})", false, true); //добавили новый комментарий и вернули его ID
        return $DB->query("SELECT * from comments WHERE id = {$newCommentID}")[0]; //возвращаем поля добавленного комментария
    }
}

Отправляем запрос на добавление комментария:

Рис. 8. Запрос на добавление комментария

В результате запроса мы получили поля добавленного комментария. Таким образом мы реализовали запрос типа «Mutation».

Типы данных (Input & Output)

В GraphQL типы данных делятся на:

  • Output type — типы для вывода данных (возвращаемых полей);
  • Input type — типы для ввода данных (передаваемых аргументов).

Простые типы данных относятся к обоим видам одновременно (Scalar, Enum, List, NonNull).

Составной тип Object, который мы использовали в наших примерах, относится к Output. Но есть тип InputObject, который является типом для передачи аргументов. InputObject отличается от Object-типа тем, что его поля не могут иметь «args» и «resolver».

Создадим тип данных CommentInputType в файле AppTypeCommentInputType.php для передачи аргументов:

<?php
//файл AppTypeCommentInputType.php
namespace NLMKGraphQLAppType;
use GraphQLTypeDefinitionInputObjectType;
/**
 * Класс для входящих аргументов сущности "Комментарий"
 *
 * Class CommentInputType
 * @package NLMKGraphQLAppType
 */
class CommentInputType extends InputObjectType
{
    /**
     * CommentInputType constructor.
     */
    public function __construct()
    {
        $config = [
            'description' => 'Добавление комментария',
            'fields' => function() {
                return [
                    'news_id' => [
                        'type' => Types::int(),
                        'description' => 'ID новости' //ключи "description" можно не добавлять
                    ],
                    'text' => [
                        'type' => Types::string(),
                        'description' => 'Комментарий пользователя'
                    ],
                    'author' => [
                        'type' => Types::int(),
                        'description' => 'ID пользователя'
                    ],
                    'date_create' => [
                        'type' => Types::string(),
                        'description' => 'Datetime добавления комментария'
                    ]
                ];
            }
        ];
        parent::__construct($config);
    }
}

Зарегистрируем наш тип данных AppTypeTypes.php:

<?php
//файл AppTypeTypes.php
private static $commentInput;
public static function commentInput() //Зарегистрировали новый тип вида Input
{
    return self::$commentInput ?: (self::$commentInput = new CommentInputType());
}

Поменяем код в нашем файле мутаций AppTypeMutationType.php:

<?php
//файл AppTypeMutationType.php
namespace NLMKGraphQLAppType;
use GraphQLTypeDefinitionObjectType;
use NLMKGraphQLAppEntityComments;
/**
 * Class MutationType
 *
 * Тип Mutation для GraphQL
 *
 * @package NLMKGraphQLAppType
 */
class MutationType extends ObjectType
{
    /**
     * MutationType constructor.
     */
    public function __construct()
    {
        $config = [
            'fields' => function () {
                return [
                    'add_comment' => [
                        'type' => Types::comments(),
                        'args' => [
                            'comment' => Types::commentInput() //Добавили тип вида Input
                        ],
                        'resolve' => function ($root, $args, $context, $info) {
                            return Comments::set($args['comment']); //передали аргументы
                        }
                    ]
                ];
            }
        ];
        parent::__construct($config);
    }
}

Отправляем запрос:

Рис. 9. Запрос на добавление комментария через созданный тип вида InputType

Теперь в запросе мы можем передавать полноценный объект комментария, поля и значения которого будут отправлены в обработку.

Выводы и рекомендации

  • Определите назначение системы в разрезе поставщик/потребитель.
  • Не ищите «волшебную пилюлю», которая сможет решить все проблемы. Каждая технология по-своему хороша.
  • Анализируйте рынок, возможно, вы найдете уже готовое решение или базовую основу для достижения какой-либо цели.
  • Отдавайте собственную информацию. Собственная информация — данные, которые хранятся в самой системе, при этом неважно, как они в нее попали. Например, «поставщик» получает из кадровой системы информацию по пользователям с полями ФИО, должность, орг. структура и сохраняет их в системе, но «потребителю» нужен еще табельный номер, который есть у «кадров», но его нет у «поставщика». В этом случае, если нужна дополнительная информация из других ИС. Лучше интегрироваться напрямую с ними без «посредников», которые являются дополнительными точками отказа и возникновения проблем; используйте отдельные прокси-сервера/мосты/шины данных (брокеры сообщений) для построения взаимодействий.
  • Старайтесь отдавать «сырую» информацию, т. е. забирать ее напрямую из хранилища без модификаций. Все модификации должен делать «потребитель» на своей стороне. Почему? Как было написано выше, мы не знаем, как и для чего «потребитель» может использовать нашу информацию.