Nest JS Graphql с Apollo Federation Gateway для микросервисов #часть-2


Построение Graphql apollo federation с помощью фреймворка nestjs 🚀 🚀 🚀

Ссылка на Github
https://github.com/tkssharma/nestjs-with-apollo-federation-gateway

Все, о чем мы здесь говорим, относится к nestjs, вы можете написать что-то, используя express или любой другой node js фреймворк.
Мы будем использовать nestjs для создания сервисов, которые будут открывать интерфейс graphql.

Давайте построим шлюз федерации apollo

Ребята, я столкнулся со многими трудностями при создании этого примера и репозитория, и у нас все еще есть много беспорядка, созданного с версиями nestjs/graphql с остальными экосистемами apollo.

федерация

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

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

Федерация против сшивания схем

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

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

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

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

В нашем примере мы будем говорить о федерации Graphql.
мы можем создать простое приложение nestjs с помощью nest-cli
Чтобы начать работу, вы можете либо создать проект с помощью Nest CLI, либо клонировать стартовый проект (оба варианта дадут одинаковый результат).

Чтобы создать проект с помощью Nest CLI, выполните следующие команды. Это создаст новый каталог проекта и заполнит его исходными файлами ядра Nest и вспомогательными модулями, создавая обычную базовую структуру для вашего проекта. Создание нового проекта с помощью Nest CLI рекомендуется для начинающих пользователей. Мы продолжим использовать этот подход в «Первых шагах».

$ npm i -g @nestjs/cli
$ nest new project-name
Вход в полноэкранный режим Выход из полноэкранного режима

Альтернативы
Альтернативный вариант — установить стартовый проект TypeScript с помощью Git:

$ git clone https://github.com/nestjs/typescript-starter.git project
$ cd project
$ npm install
$ npm run start
Войти в полноэкранный режим Выйти из полноэкранного режима

Начнем с добавления необходимых зависимостей

 "dependencies": {
    "@apollo/gateway": "0.46.0",
    "@nestjs/apollo": "9.2.4",
    "@nestjs/common": "8.2.3",
    "@nestjs/core": "8.2.3",
    "@nestjs/graphql": "10.0.0",
    "@nestjs/platform-express": "8.2.3",
    "apollo-server-express": "3.6.2",
    "dotenv": "^16.0.0",
    "graphql": "15.7.2",
    "graphql-tools": "8.0.0",
    "graphql-upload": "^13.0.0",
    "jsonwebtoken": "^8.5.1",
    "reflect-metadata": "0.1.13",
    "rimraf": "3.0.2",
    "rxjs": "7.4.0",
    "ts-morph": "12.2.0"
  }
Войти в полноэкранный режим Выйти из полноэкранного режима

С последней версией Migration https://docs.nestjs.com/graphql/migration-guide
теперь можно легко построить простой шлюз федерации apollo без использования каких-либо других внешних библиотек.

import { RemoteGraphQLDataSource } from '@apollo/gateway';
import {
  Module,
  BadRequestException,
  HttpStatus,
  HttpException,
  UnauthorizedException,
  MiddlewareConsumer,
} from '@nestjs/common';
import { IntrospectAndCompose } from '@apollo/gateway';
import { ApolloGatewayDriver, ApolloGatewayDriverConfig } from '@nestjs/apollo';
import { GraphQLModule } from '@nestjs/graphql';
GraphQLModule.forRoot <
  ApolloGatewayDriverConfig >
  {
    driver: ApolloGatewayDriver,
    gateway: {
      supergraphSdl: new IntrospectAndCompose({
        subgraphs: [
          { name: 'User', url: 'http://localhost:5006/graphql' },
          { name: 'Home', url: 'http://localhost:5003/graphql' },
        ],
      }),
    },
  };
Войти в полноэкранный режим Выйти из полноэкранного режима

С последней миграцией nestjs/graphql 10.x мы можем просто передать драйвер и тот же GraphqlModule будет работать как модуль шлюза

GraphQLModule.forRoot <
  ApolloGatewayDriverConfig >
  {
    driver: ApolloGatewayDriver,
  };
Вход в полноэкранный режим Выйти из полноэкранного режима

А определение шлюза содержит список всех сервисов подграфа


gateway: {
    supergraphSdl: new IntrospectAndCompose({
      subgraphs: [
        { name: 'User', url: 'http://localhost:5006/graphql' },
        { name: 'Home', url: 'http://localhost:5003/graphql' },
        { name: 'Booking', url: 'http://localhost:5004/graphql' },
      ],
    }),
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте посмотрим на все определение файла

import { RemoteGraphQLDataSource } from '@apollo/gateway';
import {
  Module,
  BadRequestException,
  HttpStatus,
  HttpException,
  UnauthorizedException,
  MiddlewareConsumer,
} from '@nestjs/common';
import { IntrospectAndCompose } from '@apollo/gateway';
import { ApolloGatewayDriver, ApolloGatewayDriverConfig } from '@nestjs/apollo';
import { GraphQLModule } from '@nestjs/graphql';
import { verify, decode } from 'jsonwebtoken';
import { INVALID_AUTH_TOKEN, INVALID_BEARER_TOKEN } from './app.constants';
import { graphqlUploadExpress } from 'graphql-upload';

const getToken = (authToken: string): string => {
  console.log(authToken);
  const match = authToken.match(/^Bearer (.*)$/);
  if (!match || match.length < 2) {
    throw new HttpException({ message: INVALID_BEARER_TOKEN }, HttpStatus.UNAUTHORIZED);
  }
  console.log(match[1]);
  return match[1];
};

const decodeToken = (tokenString: string) => {
  const decoded = verify(tokenString, process.env.SECRET_KEY);
  if (!decoded) {
    throw new HttpException({ message: INVALID_AUTH_TOKEN }, HttpStatus.UNAUTHORIZED);
  }
  return decoded;
};
const handleAuth = ({ req }) => {
  try {
    if (req.headers.authorization) {
      const token = getToken(req.headers.authorization);
      const decoded: any = decodeToken(token);
      return {
        userId: decoded.userId,
        permissions: decoded.permissions,
        authorization: `${req.headers.authorization}`,
      };
    }
  } catch (err) {
    throw new UnauthorizedException('User unauthorized with invalid authorization Headers');
  }
};
@Module({
  imports: [
    GraphQLModule.forRoot <
      ApolloGatewayDriverConfig >
      {
        server: {
          context: handleAuth,
        },
        driver: ApolloGatewayDriver,
        gateway: {
          buildService: ({ name, url }) => {
            return new RemoteGraphQLDataSource({
              url,
              willSendRequest({ request, context }: any) {
                request.http.headers.set('userId', context.userId);
                // for now pass authorization also
                request.http.headers.set('authorization', context.authorization);
                request.http.headers.set('permissions', context.permissions);
              },
            });
          },
          supergraphSdl: new IntrospectAndCompose({
            subgraphs: [
              { name: 'User', url: 'http://localhost:5006/graphql' },
              { name: 'Home', url: 'http://localhost:5003/graphql' },
              { name: 'Booking', url: 'http://localhost:5004/graphql' },
            ],
          }),
        },
      },
  ],
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(graphqlUploadExpress()).forRoutes('graphql');
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь содержится много кода, давайте поговорим обо всем по порядку

  • этот шлюз составляет все подграфы
  • этот шлюз управляет авторизацией также путем декодирования токена
  • этот шлюз создает ресурс запроса RemoteGraphQLDataSource перед тем, как сделать фактический api вызов к downstream сервису
  • этот шлюз отправляет запрос на список определенных подграфов, основываясь на том, что клиент запрашивает в запросе.
GraphQLModule.forRoot <
  ApolloGatewayDriverConfig >
  {
    server: {
      context: handleAuth,
    },
  };

const handleAuth = ({ req }) => {
  try {
    if (req.headers.authorization) {
      const token = getToken(req.headers.authorization);
      const decoded: any = decodeToken(token);
      return {
        userId: decoded.userId,
        permissions: decoded.permissions,
        authorization: `${req.headers.authorization}`,
      };
    }
  } catch (err) {
    throw new UnauthorizedException('User unauthorized with invalid authorization Headers');
  }
};
Вход в полноэкранный режим Выйти из полноэкранного режима

context handleAuth будет проверять заголовок авторизации и возвращать данные в контекст, чтобы мы могли получить доступ к полезной нагрузке контекста дальше

buildService: ({ name, url }) => {
  return new RemoteGraphQLDataSource({
    url,
    willSendRequest({ request, context }: any) {
      request.http.headers.set('userId', context.userId);
      // for now pass authorization also
      request.http.headers.set('authorization', context.authorization);
      request.http.headers.set('permissions', context.permissions);
    },
  });
};
Войти в полноэкранный режим Выход из полноэкранного режима

buildService строит запрос для всех подграфов, отправляя данные в заголовках из контекста, в этом примере мы отправляем метаданные пользователя в заголовках для запроса.

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

[Nest] 6612  - 05/23/2022, 11:25:39 AM     LOG [NestFactory] Starting Nest application...
[Nest] 6612  - 05/23/2022, 11:25:39 AM     LOG [InstanceLoader] AppModule dependencies initialized +30ms
[Nest] 6612  - 05/23/2022, 11:25:39 AM     LOG [InstanceLoader] GraphQLSchemaBuilderModule dependencies initialized +1ms
[Nest] 6612  - 05/23/2022, 11:25:39 AM     LOG [InstanceLoader] GraphQLModule dependencies initialized +0ms
[Nest] 6612  - 05/23/2022, 11:25:39 AM     LOG [NestApplication] Nest application successfully started +190ms
Войдите в полноэкранный режим Выход из полноэкранного режима

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

            { name: 'User', url: 'http://localhost:5006/graphql' },
            { name: 'Home', url: 'http://localhost:5003/graphql' },
            { name: 'Booking', url: 'http://localhost:5004/graphql' },
Вход в полноэкранный режим Выйти из полноэкранного режима

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

Заключение

Это было лишь краткое введение в то, что такое шлюз федерации apollo graphql, давайте узнаем больше об этом в нашем следующем блоге, где мы рассмотрим фактическую реализацию кода шлюза graphql в nestjs.

  • https://tkssharma.com/nestjs-with-apollo-federation-for-microservices-part-2
  • https://tkssharma.com/nestjs-with-apollo-federation-for-microservices-part-3
  • https://tkssharma.com/nestjs-with-apollo-federation-for-microservices-part-4

Ссылки

  • https://www.apollographql.com/docs/federation/
  • https://www.apollographql.com/docs/federation/federation-2/new-in-federation-2
  • https://github.com/apollographql/supergraph-demo-fed2
  • https://www.apollographql.com/docs/federation/federation-spec/
  • https://docs.nestjs.com/graphql/migration-guide
  • https://docs.nestjs.com/graphql/federation

Оцените статью
Procodings.ru
Добавить комментарий