TypedAPI: клиент-сервер для TypeScript

от автора

Предлагаю вашему вниманию TypedAPI: библиотеку API клиента-сервера для проектов, в которых фронт и бэк написаны на TypeScript. Акцент делается на максимальной простоте использования. Суть такая: вы пишете API как обычный TypeScript класс с некоторыми ограничениями, потом автоматом из класса строится интерфейс, которым будет пользоваться клиентское приложение. Также генерируется некоторая служебная информация. Остается только настроить коннекторы (доступны HTTP и WebSocket). Валидацию, хранение подключений, и др. берет на себя TypedAPI. Есть поддержка событий.

Чтобы поиграться, есть два простеньких демонстрационных приложения: Hello, world! и Демо-чат.

Установка

TypedAPI состоит из нескольких библиотек, которые подключаются в зависимости от типа использования.

  • typedapi-core: ядро, подключается клиентом и сервером
  • typedapi-server: ядро сервера, подключается на сервере, включает в себя функционал HTTP сервера
  • typedapi-client: ядро клиента, подключается на клиенте
  • typedapi-server-ws — WebSocket сервер
  • typedapi-client-browser-http — HTTP клиент
  • typedapi-client-browser-ws — WebSocket клиент
  • typedapi-parser — Парсер для API класса и создания интерфейса. Подключается на сервере с флагом —dev
  • typedapi-redis-signaling — для проксирования событий из микросервисов во входной API

Пример, если мы хотим использовать WebSocket соединение:

# на сервере npm install --save typedapi-core typedapi-server typedapi-server-ws npm install --save-dev typedapi-parser # на клиенте npm install --save typedapi-core typedapi-client typedapi-client-browser-ws 

Для HTTP соединения:

# for server npm install --save typedapi-core typedapi-server npm install --save-dev typedapi-parser # for client npm install --save typedapi-core typedapi-client typedapi-client-browser-http 

Требования к API классу

  • Все методы должны возвращать Promise
  • Есть ограничения по типу данных, которые могут принимать и возвращать методы (см. ниже)
  • Класс может содержать дочерние свойства-объекты, построеные по тем же правилам

Дополнительно класс может содержать свойства типа Event и ParametricEvent для реализации событий, и есть заранее определенные инъекции для авторизации и хранения базовой информации о пользователе (см. ниже).
Пример класса для «Hello, world!»:

export class Api {     async hello(name: string): Promise<string> {         return `Hello, ${name}!`     } } 

Ограничения по типам данных

Методы могут возвращать/принимать следующие типы данных:

  • Скалярные: number, string, boolean, Date, undefined, null
  • Array, Tuple, Enum, Union. Могут содержать только описанные здесь типы.
  • Объекты-структуры без методов
  • Индексированные объекты типа { [key: string | number]: SomeOtherType }

Прием/передача any, unknown запрещена.

Генерация интерфейса

Генерация интерфейса производится путем использования команды typedapi-parse из пакета typedapi-parser:

typedapi-parse [sourceFilename] [sourceObjectName] [outFilename] [reflectionOutFileName] 

где:

  • sourceFilename: Путь к файлу, где хранится ваш API
  • sourceObjectName: Название класса в этом файле
  • outFilename: Путь к файлу, в который будет записан интерфейс для клиента
  • reflectionOutFileName: Путь к файлу, который будет хранить reflections для всех методов и данных. Он используется на сервере для валидации данных

Создание соединения

После того, как вы настроили API и сегенерировали интерфейсы, можно настраивать соединение. Пример как это может выглядеть для WebSocket:

Сервер

import { WebSocketServer } from "typedapi-server-ws" import { buildMap } from "typedapi-server" // файл, сгенерированный командой typedapi-parse для сервера import { reflection } from "./apiReflection" // Ваш API класс import { Api } from "./Api" new WebSocketServer({     apiMap: buildMap(reflection, new Api),     port: 8090 }) 

Клиент

import { WebSocketTransport } from "typedapi-client-browser-ws" // файл, сгенерированный командой typedapi-parse для клиента import { createClient } from "./apiReflection" const api = createClient({ transport }) // теперь можно вызывать методы API let result = await api.hello(name) 

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

События

TypedAPI поддерживает события. При WebSocket соединении оповещение происходит обычным образом, отправкой данных через сокет. При HTTP соединении используется HTTP polling.

Пример cоздания события на сервере:

import { Event } from "typedapi-server" export class Api {     someEvent = new Event<string>() }  // отправляем событие всем продписчикам api.someEvent.fire(data) // отправляем событие одному пользователю api.someEvent.fireForUser(data, userId) // Отправляем группе пользователей api.someEvent.fireForGroup(data, groupName) // отправляем конкретной сессии api.someEvent.fireForSession(data, sessionId) // Отправялем конкретному соединению api.someEvent.fireForConnection(data, connectionId) 

Обработка на клиенте:

// Подписываемся на событие const subscription = await api.someEvent.subscribe(data => {     // обработка события }) // Отписка await subscription.unsubscribe() 

Параметрические события

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

Авторизация

Для того, чтобы реализовать авторизацию в вашем API, надо:

  • Реализовать SessionProviderInterface и передать его в конструктор сервиса. По умолчанию используется MemorySessionProvider, который будет сбрасываться при каждой перезагрузке сервера.
  • Реализовать методы, которые возвращают AuthDataResponse. Это специальный ответ метода, который обрабатывается по другому, нежели другие ответы.

Интерфейс AuthDataResponse выглядит так:

type AuthDataResponse = {     newAuthData: {         id?: string | number         groups?: string[]         name?: string         email?: string         phone?: string     }     // ответ, который будет отправлен пользователю     // newAuthData используется только для внутренних нужд     response: boolean } 

пример реализации API с авторизацией:

import { AuthDataResponse } from "typedapi-server"  export class ClientApi {     /**      * Проверяем логин и пароль, если подходит авторизуем     **/     async login(username: string, password: string): Promise<AuthDataResponse> {         let user = await usersRepository.login(username, password)         if(!user) {             return {                 response: false,                 newAuthData: {}             }         } else {             return {                 response: true,                 newAuthData: {                     id: user.id,                     groups: user.groups                 }             }         }     }     /**     * Logout     **/     async logout(): Promise<AuthDataResponse> {         return {             response: true,             newAuthData: {}         }     }     /**     * в apiUserID будет автоматически добавлен идентификатор пользователя.     * Если пользватель не авторизован, он получит NotAuthorizedError     **/     async getUserData(apiUserId: number): Promise<SomeUserData> {         let userData = await usersRespotory.getUserData(apiUserId)         return userData     } } 

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

Микросервисы

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

  • HttpProxyClient
    Это объект проксирует вызовы методов со входного API на внутренний сервис
  • HttpTrustServer
    Этот объект принимает вызов от HttpProxyClient и вызывает запрашиваемый метод
  • RedisPublisher
    Присоединяется к API, слушает события, и перенаправляет их в Redis. Используется во внутренних сервисах.
  • RedisSubscriber
    Слушает события из Редиса, и запускает их на входном API

Архитектура может выглядеть примерно так:

Примеры кода можно найти в документации.

Заключение

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

Спасибо за внимание.

ссылка на оригинал статьи https://habr.com/ru/post/447052/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *