Опыт внедрения GraphQL в Netflix

Spread the love

В этом посте мы расскажем о нашем обновлении архитектуры интерфейса и о том, как внедрили GraphQL в систему Marketing Tech.
Наше основное приложение для управления созданием и сборкой рекламы для внешних издательских платформ, внутренне называется Monet. Оно используется для ускорения создания рекламы и автоматизации управления маркетинговыми кампаниями на внешних рекламных платформах. Monet помогает стимулировать конверсии, взаимодействовать с нашим продуктом и в целом предоставлять богатую историю о нашем контенте и бренде Netflix пользователям по всему миру. Во-первых, это помогает масштабировать и автоматизировать производство рекламы и управлять миллионами  перестановок. Во-вторых, мы используем различные связи и собираем данные, такие как популярность контента в Netflix, чтобы обеспечить высокую релевантность рекламы. Наша общая цель — сделать так, чтобы наша реклама на всех внешних каналах публикации хорошо резонировала с пользователями и мы постоянно экспериментируем, чтобы повысить эффективность.

Monet and the high-level Marketing Technology flow

Когда мы только начинали, слой React UI для Monet осуществлял доступ к традиционным API REST, работающим на сервере Apache Tomcat. Со временем, по мере развития нашего приложения, варианты использования стали более сложными. Простые страницы должны получать данные из самых разных источников. Чтобы более эффективно загрузить эти данные в клиентское приложение, мы сначала попытались денормализовать данные на бэкэнде. Стало трудно поддерживать эту архитектуру, поскольку не все страницы нуждались во всех данных. Мы быстро столкнулись с узкими местами пропускной способности сети. Браузер должен будет получать гораздо больше денормализованных данных, чем когда-либо.
Чтобы определить количество полей, отправляемых клиенту, одним из подходов является создание пользовательских конечных точек для каждой страницы; это был довольно очевидный фейл. Вместо создания этих пользовательских конечных точек мы выбрали GraphQL в качестве среднего уровня приложения. Мы также рассматривали Falcor как возможное решение, так как оно показало отличные результаты на Netflix во многих основных случаях использования и имеет множество применений, но надежная экосистема GraphQL и мощные инструменты сторонних разработчиков сделали GraphQL лучшим вариантом для нашего варианта использования. Кроме того, поскольку наши структуры данных становятся все более ориентированными на графы, они оказались очень естественными. Добавление GraphQL не только помогло устранить узкое место в полосе пропускания сети, но также дало множество других преимуществ, которые помогли нам быстрее добавлять функции.

Architecture before and after GraphQL

Преимущества

Мы работали с GraphQL на Node.js около 6 месяцев и это значительно повысило скорость разработки и общую загрузку страниц. Вот некоторые из преимуществ, которые хорошо для нас сработали.

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

Часто некоторые машины лучше подходят для определенных задач, чем другие. Когда мы добавили средний уровень GraphQL, серверу GraphQL все еще требовалось вызывать те же сервисы и REST API, которые клиент вызывал бы напрямую. Разница в том, что большая часть данных передается между серверами в одном центре обработки данных. Эти межсерверные вызовы имеют очень низкую задержку и высокую пропускную способность, что дает нам увеличение производительности примерно в 8 раз по сравнению с прямыми сетевыми вызовами из браузера. Поскольку GraphQL позволяет клиенту выбирать только те данные, которые ему нужны, мы получаем значительно меньшую нагрузку. В нашем приложении страницы, которые раньше загружали 10 МБ данных, теперь получают около 200 КБ. Загрузка страниц стала намного быстрее, особенно в мобильных сетях с ограниченными данными и приложение использует гораздо меньше памяти. Эти изменения произошли за счет более высокой загрузки сервера для выполнения выборки и агрегации данных, но несколько дополнительных миллисекунд серверного времени на запрос были значительно перевешены меньшими полезными нагрузками клиента.

Возобновляемые абстракции

Разработчики программного обеспечения обычно хотят работать с повторно используемыми абстракциями вместо одноразовых методов. В GraphQL мы определяем каждый фрагмент данных один раз и определяем, как он соотносится с другими данными в нашей системе. Когда потребительское приложение извлекает данные из нескольких источников, ему больше не нужно беспокоиться о сложной бизнес-логике, связанной с операциями объединения данных.
Рассмотрим следующий пример: мы определяем сущности в GraphQL ровно один раз: каталоги, объявления и комментарии. Теперь можно построить представления для нескольких страниц из этих определений. Одна страница в клиентском приложении (catalogView) объявляет, что ему нужны все комментарии для всех объявлений в каталоге, в то время как другая страница клиента (creativeView) хочет знать связанный каталог, к которому принадлежит объявление, вместе со всеми его комментариями.

The flexibility of the GraphQL data model to represent different views from the same underlying data

Одна и та же графовая модель может использовать оба этих представления без каких-либо изменений кода на стороне сервера.

Системы с цепочками данных

Многие люди сосредотачиваются на системах типов в рамках одного сервиса, но редко в разных сервисах. После того как мы определили сущности на  сервере GraphQL, мы используем инструменты автоматического кодирования для генерации типов TypeScript для клиентского приложения. Свойства наших компонентов React получают типы, соответствующие запросу, который делает компонент. Поскольку эти типы и запросы также проверяются на соответствие схеме сервера, любые критические изменения со стороны сервера будут отслеживаться клиентами, потребляющими данные. Объединение нескольких сервисов вместе с GraphQL и подключение этих проверок к процессу сборки позволяет нам обнаружить гораздо больше проблем перед развертыванием плохого кода. В идеале мы хотели бы обеспечить безопасность типов от уровня базы данных до клиентского браузера.

Type safety from database to backend to client code

DI / DX — упрощение разработки

Общая проблема при создании клиентских приложений — это UI / UX, интерфейс и опыт разработчика так же важны для создания поддерживаемых приложений. Перед GraphQL для написания нового контейнерного компонента React требовалось поддерживать сложную логику для сетевых запросов к нужным нам данным. Разработчик должен будет рассмотреть, как одна часть данных связана с другой, как данные должны кэшироваться, делать ли вызовы параллельно или последовательно и где в Redux хранить данные. При использовании оболочки запросов GraphQL каждый компонент React должен описывать только те данные, которые ему необходимы, а оболочка заботится обо всех этих проблемах. Там гораздо меньше стандартного кода и четкое разделение проблем между данными и пользовательским интерфейсом. Эта модель декларативного извлечения данных делает компоненты React намного проще для понимания и служит для частичного документирования того, что делает компонент.

Другие преимущества

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

Растущая боль

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

Проблема с кешированием

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

Adding a cache to simplify data access from resolvers

Какую запутанную сеть мы создаем

Абстракции — отличный способ сделать разработчиков более эффективными… пока что-то пойдет не так. Несомненно, в нашем коде будут ошибки, и мы не хотим запутывать основную причину средним уровнем. GraphQL автоматически организует сетевые вызовы другим сервисам, скрывая сложности от пользователя. Журналы сервера обеспечивают способ отладки, но они все еще на шаг отстоят от естественного подхода отладки через вкладку сети браузера. Чтобы упростить отладку, мы добавили журналы непосредственно в полезную нагрузку ответа GraphQL, которые отображают все сетевые запросы, которые делает сервер. Когда флаг отладки включен, вы получаете те же данные в клиентском браузере, что и если бы браузер выполнял сетевой вызов напрямую.

Ломая типизацию

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

Что будет дальше

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

Visualization of our GraphQL Schema

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

Netflix Technology Blog. Our learnings from adopting GraphQL by Artem Shtatnov and Ravi Srinivas Ranganathan

Добавить комментарий