Введение

Как и многие стартапы, наша компания начинала с монолитной серверной части. Это работало некоторое время, но по мере роста Mothership мы поняли, что в долгосрочной перспективе это невозможно поддерживать. Отгрузки, расценки, счета-фактуры и маршруты — это лишь некоторые из многих объектов, с которыми наша система имеет дело ежедневно, и их поддержка одним и тем же сервисом быстро привела к созданию массивного, хрупкого и трудно поддающегося изменению приложения. Итак, мы начали процесс разделения наших бэкендов на микросервисы, каждый из которых имеет свои собственные границы доменной области.

Однако наши внешние интерфейсы по-прежнему полагаются на доступ ко всем этим различным данным. Микросервисы прекрасны с точки зрения серверной части, поскольку им нужно иметь дело только со своим собственным доменом и не перегружать кодом, который им не нужен. Но предоставление всех этих данных внешним интерфейсам может быть довольно сложным. Мы не хотели генерировать значительно больше трафика от наших внешних клиентов, обращаясь ко всем этим микросервисам, и нам нужно было уйти от предыдущего монолита.

Создание GraphQL BFF

С GraphQL мы можем предоставить гибкое API-решение Backend-for-Frontend (BFF) для любого из наших интерфейсов, сохраняя при этом преимущества микросервисов. GraphQL возлагает на вызывающую сторону ответственность за предоставление желаемой схемы ответа, поэтому мы можем настроить наш BFF так, чтобы он вызывал только необходимые службы для запрошенных данных. Не нужна цитата, связанная с отгрузкой? Нашей конечной точке GraphQL не нужно запрашивать наш микросервис котировок. Нужны все возможные данные для данного счета? Удивительно, наш GraphQL API может предоставить вам единую конечную точку для получения этих данных!

Новые технологии всегда немного трудоемки для включения в существующий стек, но преимущества намного перевешивают усилия, которые нам пришлось приложить. Мы не планируем подробно рассказывать о преимуществах и недостатках GraphQL (если вы хотите узнать больше , нам очень нравится этот обзор по теме), но вместо этого поговорим об использовании GraphQL в Mothership и о знаниях, которые мы получили, пройдя этот путь.

Наш первоначальный подход

Здесь, в Mothership, большинство наших бэкэндов API размещаются с использованием Express. Мы не хотели отклоняться слишком далеко от этого, поэтому неудивительно, что мы остановились на express-graphql, чтобы начать наше путешествие по GraphQL.

Мы хотели, чтобы организация BFF на основе GraphQL отражала организацию наших микросервисов: маршрутизатор для предоставления основной конечной точки GraphQL, а затем логика, организованная для каждой сущности или микросервиса, которую можно было бы использовать во всем API. Поскольку несколько команд будут обновлять и поддерживать этот код, нам нужно было сделать его как можно более простым, но при этом обеспечить оптимальную производительность в наших запросах.

Первоначальный подход состоял из простого GraphQLRouter.ts:

Затем в наших сервисах мы создадим довольно простой файл схемы GraphQL в schema.gql и передаем его маршрутизатору:

Затем мы заполнили rootValue в Route именами наших методов, которые соответствовали нашей схеме GraphQL. Однако это означает, что вся логика должна быть в одном большом GraphQLRouter. Разделение нашей логики по типам сущностей казалось логичным следующим шагом. Для этого мы создали концепцию GraphQLManager.ts , которая будет предоставлять собственный экземпляр запросов, мутаций и схемы.

И наш обновленный GraphQLRouter.ts :

И теперь это позволяет нам разделить нашу логику на конкретные объекты.

Эта реализация работала какое-то время, но на этом пути было несколько заминок:

  • Что, если бы два разных менеджера реализовали запрос с одинаковым именем (т. е. два fetchShipment метода)? В итоге мы реализовали громоздкий код проверки в конструкторе GraphQLRouter, чтобы проверить это.
  • Еще одной неприятностью было управление схемой GraphQL, чтобы она соответствовала нашим библиотекам типов машинописных текстов.
    — Здесь, в Mothership, каждый микросервис предоставляет пакет -types npm, который содержит все интерфейсы TypeScript, которые могут использоваться другими сервисами. Всякий раз, когда требуется обновить тип, мы выпускаем новую версию пакета типов вместе с каждым обновлением микросервиса.
    — Поскольку мы вручную определяем схемы GraphQL в этом сервисе, нам нужно каждый раз обновлять эти файлы схем вручную. есть версия пакета типов.
  • Самая большая проблема возникла, когда мы захотели начать внедрять распознаватели полей. Как оказалось, функция по умолчанию graphql buildSchema не поддерживает это. У нас действительно была короткая вспышка, когда мы обновили наш GraphQLManager, чтобы принять свойство typeResolvers, и переключились на makeExecutableSchema из @graphql-tools/schema, но это все еще не казалось правильным решением.

Учитывая, что это не конец поста, вы, возможно, уже поняли, что эта реализация не задержалась.

Как это обычно делают программисты, когда им приходится выполнять утомительные задачи, мы задались вопросом, есть ли более чистое, простое и удобное в сопровождении решение наших проблем с GraphQL…

Введите TypeGraphQL

TypeGraphQL — это фреймворк, предназначенный для работы со стандартными практиками Typescript, и использует декораторы Typescript для применения конкретной логики GraphQL. Мы открыли для себя этот фреймворк из-за наших проблем с реализацией преобразователя полей, и он действительно помог нам устранить некоторые из первоначальных недостатков интеграции GraphQL в наши системы.

Больше никаких ручных схем GraphQL!

Прежде всего, TypeGraphQL полностью избавил нас от необходимости вручную определять наши схемы GraphQL. Вместо этого мы теперь полагаемся на эти декораторы, и он генерирует их для нас, полностью абстрагируя их. Давайте посмотрим, как мы печатаем Shipment.ts:

Раньше каждый раз, когда нам приходилось обновлять наш InternalShipment здесь, нам нужно было вручную вносить изменения в нашу схему GraphQL. Теперь нам просто нужно обновить это определение класса соответствующими декораторами!

Еще одна проблема, которую это помогает нам решить, — обеспечение актуальности наших схем GraphQL. Раньше мы могли повышать версии наших типов, но забывали обновлять схему GraphQL. Это означало бы, что библиотека GraphQL не будет обновляться для возврата этих данных возможному ожидающему клиенту.

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

С помощью TypeGraphQL мы можем гарантировать, что все поля в нашем InternalShipment всегда будут соответствовать версии установленных типов, поскольку мы реализуем этот интерфейс!

Более простые в обслуживании менеджеры!

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

Вот наш обновленный ShipmentResolver.ts (переименованный из ShipmentManager):

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

С TypeGraphQL его метод buildSchema передает реализацию схемы самой службе:

Это означает, что наш код GraphQLRouter обновляется, чтобы стать базовым:

Кроме того, TypeGraphQL также решает проблему дублирования методов, описанную выше!

Недостатки

Это кажется идеальным решением, но TypeGraphQL не лишен пары (хотя и небольших) недостатков.

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

Еще один небольшой недостаток для нас заключается в том, что TypeGraphQL на момент написания этой публикации опирается на предыдущую версию GraphQL 15.8.0. GraphQL 16 действительно предлагает некоторые интересные функции (например, улучшенные сообщения об ошибках), и, к счастью, TypeGraphQL работает над обновлением своей поддержки здесь.

Заключение

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

Вы можете обнаружить в своей компании, что, когда вы начинаете создавать микросервисы, вам нужно создать аналогичное решение. Надеемся, что это знакомство с решением Mothership GraphQL может дать вам новые идеи для изучения. Я уверен, что это не конец нашего знакомства с GraphQL, и мы с нетерпением ждем возможности поделиться другими знаниями в будущем.

Если вам нравится то, что вы видите, и вы хотите помочь Mothership расширить свою инфраструктуру GraphQL (или любой другой наш интересный проект), приходите к нам работать!