Руководство по составлению и интеграции API вместе

Руководство по составлению и интеграции API вместе

18 декабря 2022 г.

Объединение двух API для приложения, которое показывает самые масштабные исторические концерты по столице страны.

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

Поздравляем, вы нашли последнего босса веб-разработки: создание многоразовых «блоков LEGO» из компонентов, которые можно масштабировать бесконечно.

Если вы не использовали React/Next.js для этой битвы с самого начала, в какой-то момент вы неизбежно закончите тем, что повторно реализуете гораздо худший, специальный «React» из jQuery — и нести ответственность за его обслуживание.

Но создание составных пользовательских интерфейсов — это только полдела. Самый масштабируемый пользовательский интерфейс в мире был бы ничем без отображаемых данных. Итак, вторая половина: работа с API, базами данных и микросервисами.

Если мы хотим полностью использовать масштабируемые модульные веб-приложения, мы не можем забывать о компонуемости и в этой проблемной области.

Здесь может помочь WunderGraph — платформа для разработки API с открытым исходным кодом. В ментальной модели React вы уже привыкли перечислять все свои зависимости в файле package.json и позволять менеджерам пакетов делать все остальное, когда вы npm install && npm запустить проект.

WunderGraph позволяет вам сохранить эту интуицию и делать то же самое с вашими источниками данных:

  1. Явно назовите нужные вам API, микросервисы и базы данных в формате конфигурации как кода, а затем

2. WunderGraph генерирует код на стороне клиента, который предоставляет вам первоклассный безопасный доступ (через перехватчики Next.js/React) ко всем этим источникам данных во время работы над внешним интерфейсом.

В этом руководстве мы немного рискнем и объединим два совершенно разных API в приложении Next.js, чтобы показать, что WunderGraph (и никакие другие зависимости) работает вместе с вашим интерфейсом в качестве независимого сервера. API Gateway/BFF, вы могли бы, по сути, написать интерфейсный код для несколько REST, GraphQL, MySQL, Postgres, DBaaS, таких как Fauna, MongoDB и т. д., как если бы они были единым монолитом.

Прежде чем мы начнем, давайте быстро TL;DR концепцию:

Что вообще означает «компонуемость» для API?

Сейчас ваше взаимодействие с источниками данных полностью запрограммировано. Вы пишете код для выполнения вызовов конечной точки API или базы данных (с учетными данными в файле .env), а затем пишете дополнительный код. На этот раз асинхронный шаблон/клей для управления возвращаемыми данными.< /p>

Что не так с этой картинкой?

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

Конечно, этот API погоды не является axios или react-dom (пакеты библиотеки/фреймворка), но, тем не менее, является зависимостью, и теперь сторонний API, который всегда отражает только временные данные, был передан вашему репозиторию, стал частью вашего основного бизнеса, и теперь вы будете поддерживать его на протяжении всего срока его службы.

Придерживаясь аналогии с Lego: это похоже на склеивание ваших наборов. Добро пожаловать в раздутые, плохо читаемые и плохо поддерживаемые кодовые базы с жесткими ограничениями.

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

Это даже не считая всех JOIN, которые вы будете выполнять в этих многочисленных сервисах/API, чтобы получить нужные вам данные.

Итак, как может выглядеть масштабируемый подход к компоновке API без ущерба для удобства разработчиков?

  1. Он должен поддерживать различные данные, не нуждаясь в отдельном клиенте для каждого типа, и нам нужно будет иметь возможность явно определять эти зависимости данных вне нашего кода приложения — возможно, в файле конфигурации.

2. Это должно позволить нам добавлять дополнительные источники данных по мере необходимости, удалять устаревшие/мертвые источники из массива зависимостей и автоматически обновлять клиент, чтобы отразить эти изменения.

3. Это должно позволять нам объединять данные из нескольких источников (API, базы данных, федерации Apollo, микросервисы и т. д.), чтобы нам вообще не приходилось выполнять JOIN в коде на внешнем интерфейсе.

Как оказалось, это именно то, что позволяет WunderGraph благодаря сочетанию архитектур API Gateway и BFF.

Если вы явно указываете свои зависимости API в конфигурации WunderGraph, как показано справа, здесь:

Концептуально, в чем разница? Их нет; оба являются конфигурационными файлами со списком вещей, которые нужны вашему приложению. Конечно, вы пишете немного больше кода справа, но это просто то, что React/Next.js делает для вас под капотом слева.

WunderGraph анализирует и объединяет эти источники данных (а не только конечные точки) в виртуальный граф с пространством имен и строит на его основе схему.

Теперь вам больше не нужно:

  1. Насколько по-разному работают ваши зависимости данных.

2. Доставка любых сторонних клиентов на внешний интерфейс для поддержки этих различных источников данных.

3. Как ваши команды должны общаться между доменами.

Потому что теперь у вас уже есть все зависимости данных в виде канонического уровня, единого источника достоверностиGraphQL

Как вы могли догадаться, дальше нужно просто написать операции (запросы/мутации GraphQL, которые WunderGraph предоставляет вам для автозаполнения в вашей IDE), чтобы получить нужные данные из этого стандартизированного слоя данных.

Они компилируются в собственный клиент во время сборки, сохраняются и предоставляются с помощью JSON-RPC (HTTP).

Все DevEx выигрывают от использования GraphQL, фактически не имея общедоступной конечной точки GraphQL, поэтому на стороне клиента не возникает проблем с безопасностью/кэшированием/размером пакета.

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

Четкий, интуитивно понятный и удобный в обслуживании.

Конечный результат? Парадигма контейнеризации/менеджера пакетов, подобная Docker или NPM, но для источников данных. Со всеми вытекающими отсюда преимуществами:

* API, базы данных и микросервисы превратились в модульные сборные кубики Lego, как и ваши компоненты пользовательского интерфейса.

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

* Поскольку конечной «конечной точкой» является JSON-RPC поверх старого доброго HTTP, кэширование, разрешения, аутентификация и безопасность — все становится решенными проблемами независимо от типа источника данных.

Но зачем придерживаться теории? Давайте начнем!

Приключение в подходе к API как к LEGO

Возможность создавать и объединять источники данных, как если бы вы это делали в библиотеках, может привести вас к очень интересным вещам и идеям, которые вам никогда не подскажет ни один заурядный общедоступный API.

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

Там буквально нет такого API. Вы можете построить первый. Весь мир — твоя устрица!

Шаг 1. Выбор данных

Итак, здесь мы будем использовать два API: API стран и Агрегатор MusicBrainz. Не стесняйтесь поиграть с Insomnia/Postman/Playgrounds и понять, какие данные вы можете обоснованно запрашивать с помощью этих API. Вероятно, вы найдете массу дополнительных творческих вариантов использования.

Шаг 2. Быстрый запуск приложения WunderGraph + Next.js

Когда вы будете готовы двигаться дальше, используйте начальный шаблон в репозитории WunderGraph для приложения Next.js, которое использует первый в качестве шлюза BFF/API.

npx -y @wundergraph/wunderctl init — template nextjs-starter -o wg-concerts

Это создаст новый каталог проекта с именем wg-concerts (или именем папки по вашему выбору), запустит WunderGraph (на локальном хосте:9991) и сервер Next.js (на локальном хосте:3000). ), используя пакет npm-run-all; в частности, используя псевдоним run-p для параллельного запуска обоих.

Шаг 3. Концерты Capital – кросс-присоединение API без кода.

Вот суть этого руководства. Я много говорил о том, что объединение данных из разных источников является серьезной проблемой, когда выполняется в коде, и теперь вы воочию увидите, как WunderGraph упрощает их.

Вы можете объединить 2 ответа API — в нашем случае получить столицу страны, а затем использовать эту информацию для запроса концертов, которые там проходили, — вот так:

query ConcertsByCapital($countryCode: ID!, $capital: String! @internal) {
  country: countries_country(code: $countryCode) {
    name
    capital @export(as: "capital")
    concerts: _join @transform(get: "music_search.areas.nodes.events.nodes") {
      music_search {
        areas(query: $capital, first: 1) {
          nodes {
            events {
              nodes {
                name
                relationships {
                  artists(first: 1) {
                    nodes {
                      target {
                        mbid
                      }
                    }
                  }
                }
                lifeSpan {
                  begin
                }
                setlist
              }
            }
          }
        }
      }
    }
  }

Представьте себе реализацию этого запроса в JavaScript. Действительно, жуткий сезон.

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

* Директива @export работает рука об руку с @internal и тем, что вы экспортируете (или псевдонимом — это то, что 'as', должно иметь то же имя и тип, что и аргумент, который вы пометили как внутренний.

* _join означает фактическую операцию JOIN:

* Как вы можете заметить, ввод (запрос) этого второго запроса использует тот же аргумент, который мы пометили как внутренний на верхнем уровне этого запроса GraphQL.

* Несмотря на то, что это необязательно, мы используем директиву @transform (а затем поле 'get', указывающее на точную структуру данных, которая нам нужна) для создания псевдонима ответа 2-й запрос в «концерты», потому что любой дополнительный запрос, к которому мы присоединяемся, конечно, добавит еще одну сложную, надоедливо вложенную структуру, и мы хотим упростить и сделать ее как можно более читабельной.

* Мы также (необязательно) включаем поле relationships для каждого концерта, чтобы получить здесь mbid (внутренний идентификатор MusicBrainz артиста, участвующего в этом концерте), потому что мы по-прежнему хотите запросить объект Artist отдельно позже (для баннеров, эскизов, биографий и т. д. Опять же, необязательно).

Шаг 4. Получение сведений об исполнителе

query ArtistBanner($artistId: music_MBID!) {
  music_lookup {
    artist(mbid: $artistId) {
      name
      theAudioDB {
        banner
      }
    }
  }
}

query ArtistDetail($mbid: music_MBID!) {
  music_lookup {
    artist(mbid: $mbid) {
      name
      theAudioDB {
        banner
        thumbnail
        biography
      }
    }
  }
}

Говоря об опыте разработчиков… вот небольшая загвоздка с переменной artistId, имеющей тип MBID!, а не String!. Благодаря WunderGraph вы получите подсказку кода для этого в своей IDE!

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

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

Шаг 5. Отображение наших данных во внешнем интерфейсе

Мы на финишной прямой! Давайте не будем слишком заморачиваться, просто каждый концерт сопоставлен с компонентами <ConcertCard>, а <NavBar> с <Dropdown> выбрать страну для проведения концертов в ее столице. Ну и, конечно же, TailwindCSS для стилей.

/* NextJS stuff */
import { NextPage } from "next";
/* WunderGraph stuff */
import { useQuery, withWunderGraph } from "../components/generated/nextjs";
/* my components */
import ConcertCard from "../components/ConcertCard";
import NavBar from "../components/NavBar";
/* my types */
import { AllConcertsResult } from "../types/AllConcerts";

const Home: NextPage = () => {
  // WunderGraph-generated typesafe hook for data fetching
  const { result, refetch } = useQuery.ConcertsByCapital({
    input: { countryCode: "BG" },
  });

  // we can just use the provided refetch here (with a callback to our NavBar component) to redo the query when the country is changed.  Neat!
  const switchCountry = (code: string) => {
    refetch({
      input: { countryCode: code },
    });
  };
  const concertsByCapital = result as AllConcertsResult;

  const data = concertsByCapital.data;
  const country = data?.country?.name;
  const capital = data?.country?.capital;
  const concerts = data?.country?.concerts;

  return (
    <div>
      <NavBar
        country={country}
        capital={capital}
        switchCountry={switchCountry}
      />
      <div className="font-mono m-10 text-zinc-50">
        {data ? (
          <div>
            {concerts?.map((concert) => {
              let name = concert?.name;
              let date = concert?.lifeSpan.begin as string;
              let setList = concert?.setlist;
              let artistId =
                concert?.relationships?.artists?.nodes[0]?.target.mbid;
              return (
                <ConcertCard
                  name={name}
                  date={date}
                  setList={setList}
                  artistId={artistId}
                />
              );
            })}
          </div>
        ) : (
          <div className="grid h-screen place-items-center"> Loading...</div>
        )}
      </div>
      <hr />
    </div>
  );
};

export default withWunderGraph(Home); // to make sure SSR works

Index.tsx

import Link from "next/link";
import React from "react";

type Props = {
  country?: string;
  capital?: string;
  switchCountry?(code: string): void;
};

const NavBar = (props: Props) => {
  const [selectedOption, setSelectedOption] = React.useState("BG");

  // Dropdown subcomponent that's just a styled, state-aware <select>
  function Dropdown() {
    return (
      <select
        onChange={handleChange}
        className="cursor-pointer"
        name="country"
        id="countries"
        value={selectedOption}
      >
        <option value="BG">Bulgaria</option>
        <option value="ES">Spain</option>
        <option value="JP">Japan</option>
      </select>
    );
  }

  // handle a country change
  function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
    event.preventDefault();
    setSelectedOption(event.target.value); // to reflect changed country in UI
    props.switchCountry(event.target.value); // callback
  }

  return (
    <nav className="sticky top-0 z-50 h-12 shadow-2xl  w-full bg-red-600">
      <ul className="list-none m-0 overflow-hidden p-0 fixed top-0 w-full flex justify-center">
        <li className="cursor-pointer">
          <div className="block py-3 text-center text-white hover:underline text-lg text-slate-50 ">
            <Link href="/">Home</Link>
          </div>
        </li>
        {props.country && (
          <li className="cursor-pointer">
            <div className="block py-3 px-4 text-center text-white no-underline text-lg text-black ">
              <Dropdown />
            </div>
          </li>
        )}

        {props.capital && (
          <li>
            <div className="block py-3 text-center text-white no-underline text-lg text-slate-50 ">
              @ {props.capital}
            </div>
          </li>
        )}
      </ul>
    </nav>
  );
};

export default NavBar;

Navbar.tsx

/* WunderGraph stuff */
import { useRouter } from "next/router";
import { useQuery } from "../components/generated/nextjs";
/* my types */
import { ArtistResult } from "../types/Artist";
/* utility functions */
import parseVenue from "../utils/parse-venue";

type Props = {
  name: string;
  date: string;
  setList: string;
  artistId: string;
};

const ConcertCard = (props: Props) => {

  const router = useRouter();

  const artist = useQuery.ArtistBanner({
    input: { artistId: props.artistId },
  }) as ArtistResult;

  const banner = artist.result.data?.music_lookup.artist?.theAudioDB?.banner;
  const artistName = artist.result.data?.music_lookup.artist?.name;
  const venue = parseVenue(props.name);

  return (
    <div className="concert grid place-items-center mb-5 ">
      {banner ? (
        <>
          <img
            className="hover:shadow-[20px_5px_0px_5px_rgb(220,38,38)] hover:ring-1 ring-red-600 hover:scale-105 cursor-pointer"
            onClick={() => router.push({
              pathname: `/concert/${props.artistId}`
            })}
            src={banner}
            width="1000"
            height="185"
          />
        </>
      ) : (
        <>
          <img
            src={`https://via.placeholder.com/1000x185.png`}
            width="1000"
            height="185"
          />
        </>
      )}

      <p className="text-3xl mt-5"> {artistName}</p>
      <p className="text-xl mt-5"> {venue}</p>
      <p className=" font-medium  mb-5"> {props.date}</p>
      <hr />
    </div>
  );
};

export default ConcertCard;

ConcertCard.tsx

/* NextJS stuff */
import { NextPage } from "next";
import { useRouter } from "next/router";
/* WunderGraph stuff */
import { useQuery, withWunderGraph } from "../../components/generated/nextjs";
import NavBar from "../../components/NavBar";

/* my types */
import { ArtistDetailResult } from "../../types/ArtistDetail";

type Props = {};

const Concert: NextPage = (props: Props) => {
  const router = useRouter();
  const { id } = router.query;
  const artistId: string = id as string;

  const result = useQuery.ArtistDetail({
    input: {
      mbid: artistId,
    },
  }).result as ArtistDetailResult;

  const data = result.data;

  const artistName = data?.music_lookup?.artist?.name;
  const bannerImg = data?.music_lookup?.artist?.theAudioDB?.banner;
  const thumbnailImg = data?.music_lookup?.artist?.theAudioDB?.thumbnail;
  const bio = data?.music_lookup?.artist?.theAudioDB?.biography;

  return (
    <div className="flex grid h-full place-items-center bg-black text-zinc-100">
      <NavBar />
      {data ? (
        <div className="mt-1">
          <div className="banner mx-1 object-fill">
            <img className="" src={bannerImg} />
          </div>
          <div className="grid grid-cols-2 mt-2 mx-5">
            <div className="w-full mt-2">
              <img
                className="rounded-lg ring-2 shadow-[10px_10px_0px_5px_rgb(220,38,38)] hover:ring-1 ring-red-600 thumbnail"
                src={thumbnailImg}
                width="500px"
                height="500px"
              />
            </div>

            <div className="flex flex-col ml-8">
              <div className="mb-10 font-black text-7xl ">{artistName}</div>
              <div className="w-5/6 mx-2 font-mono break-normal line-clamp-4">
                {bio}
              </div>
            </div>
          </div>
        </div>
      ) : (
        <div className="grid h-screen place-items-center"> Loading...</div>
      )}
    </div>
  );
};

export default withWunderGraph(Concert);

[id].tsx

Все сделано! Запустите localhost:3000, и вы увидите свое приложение.

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

Что делать, если я не использую Next.js/React?

WunderGraph по-прежнему работает как простой шлюз API/BFF без автоматического создания клиентского интерфейса для выборки данных.

В этом сценарии, однако, у вас не будет доступа к хукам React с безопасным типом, которые WunderGraph генерирует для вашей клиентской стороны, поэтому вам придется взять на себя больше забот — реализовать выборку данных самостоятельно, следить за безопасностью типов и создавать внутренние вызовы GET/POST вручную.

Используя конфигурации WunderGraph по умолчанию, каждая ваша операция (файл .graphql) отображается как JSON-RPC (HTTP) по адресу:

http://localhost:9991/app/main/operations/ [имя_операции]

Таким образом, выборка ваших данных будет выглядеть примерно так:

Где Weather.graphql — это имя файла вашей операции.

Разблокировано достижение: компонуемость данных

WunderGraph является частью вашего инструментария для объединения всех ваших API, баз данных и микросервисов — будь то BFF, API Gateway, View-Aggregator, который всегда отражает данные только для чтения, или что-то еще — вы получаете все преимущества компоновки пользовательского интерфейса в области данных.

  1. Постепенное улучшение: пересматривайте код в любое время, чтобы конкретизировать его, или добавляйте новые части по мере роста потребностей бизнеса.

2. Гибкость: заменяйте детали по мере необходимости, чтобы ваш технический стек не кальцинировался.

  1. Улучшенный сквозной опыт для разработчиков:

  2. Единый источник достоверности (уровень GraphQL) для всех данных.

* Идеально сформированный клиент с помощью генерации кода означает, что каждая команда точно знает, что они могут или не могут делать с данными (через автозаполнение в вашей IDE) при написании операций в виде запросов или мутаций GraphQL, что позволяет вам создавать именно тот опыт, который вы хотите для своих пользователей, без пробной версии & ошибка.

* В сочетании с Next.js вы можете иметь готовые запросы к работе в границах <Suspense>, чтобы вы точно знали, что отображается в каждом из них, и какие именно запросы выполняются под капотом. Это знание позволяет лучше устанавливать исправления и оптимизировать, потому что вы будете точно знать, где могут быть какие-либо проблемы или узкие места.

Что касается современной бессерверной веб-разработки, WunderGraph может работать на всем, что может работать с Docker, поэтому интеграция в ваш технический стек будет бесшовной.

Это мощная игра WunderGraph. Компонуемость для всех зависимостей, позволяющая создавать модульные приложения с интенсивным использованием данных для современного Интернета без ущерба для удобства разработчиков.


Также опубликовано здесь

Фото Брента Нинабера на Unsplash


Оригинал