Как типизировать подобный паттерн?

Заранее прошу прощения - получился некоторый лонгрид, и, возможно, решение простое, но мне пришлось подробно описать контекст и привести примеры своей реализации.
Есть приложение, построенное на микрофронтендах. Каждое используемое в нем микроприложение, будь то вью или реакт, раньше общалось с бэком через кастомную либу, использующую NGSI (что в принципе не суть важно, но важно понимать, что у NGSI есть свои конкретные методы для работы с бэком).
Теперь понадобилось дать микроприложениям возможность работать через разные api - т.е. в каких-то случаях это может оказаться NGSI, в каких-то - Axios, ну или мало ли что еще.
Я решил сделать посредническую либу, что-то типа паттерна "репозиторий", которая имеет точку входа, и в зависимости от переданных пропсов, инстанцирует тот или иной класс для работы с api. Выглядит примерно так

1. Есть файл базового репозитория. Это абстрактный класс, от которого будут экстендиться конкретные репозитории
// File BaseRepository.ts
export abstract class BaseRepository {
  readonly defaultErrorMessage = "Method not implemented."

  entity(payload: IEntityPayload) {
    throw new Error(this.defaultErrorMessage);
  }

  list(payload: IListPayload) {
    throw new Error(this.defaultErrorMessage);
  }
  // В реальности методов гораздо больше. Какие-то из них могут быть
  // лишь в одном репозитории и отсутствовать в другом.
  // Но большинство методов есть и там, и там.
  // Но наверное, тут, в абстрактном классе нужно перечислить их все,
  // даже если в каком-то репозитории они останутся нереализованными?
}


2. Есть файлы самих репозиториев
// File ApiRepo1.ts
export class ApiRepo1 extends BaseRepository {
  readonly api

  constructor(config: AxiosRequestConfig) {
    super();
    this.api = axios.create(config)
  }
  
  async list({ someQueryKey }: IListPayload) {
    // api.get - обычный метод get у Axios
    // apiURLs - это Map с урлами запросов
    return await this.api.get(String(apiURLs.get(someQueryKey)))
  }
  
  async entity({ someQueryKey }: IEntityPayload) {
    return await this.api.get(String(apiURLs.get(someQueryKey)))
  }
}

// File ApiRepo2.ts
export class ApiRepo2 extends BaseRepository {
  async list(payload: IListPayload) {
    // listEntities, как и getEntity ниже - это методы NGSI, предварительно импортированные
    return await listEntities(payload);
  }
  
  async entity(payload: IEntityPayload) {
    return await getEntity(payload);
  }
}


3. Далее в индексном файле либы я импортирую все эти репозитории и создаю точку для подключения:
import { ApiRepo1 } from "./ApiRepo1";
import { ApiRepo2 } from "./ApiRepo2";

type TRepoLib = {
  name: string
  config: {
    [index: string]: string | boolean | number
  }
}

const repoMap = new Map<string, any>([
  ["api-repo-1", ApiRepo1],
  ["api-repo-2", ApiRepo2]
]);

export {
  type TRepoLib,
  repoMap
}


При использовании в микроприложении, на основе полученных им пропсов, я инициализирую для работы тот или иной репозиторий:
import { repoMap } from '@lib-api-repo'

// lib - объект из пропсов. Его может и не быть, тогда передается дефолтное значение
const APILib = repoMap.get(lib?.name || 'api-repo-2')
const api = new APILib(lib?.config)


Это еще довольно сырая версия, но основная идея, думаю, понятна.
Тут возникает ряд вопросов, касающихся типизации:

1. Как правильней избавиться от any в этой конструкции? Или, может, заменить эту конструкцию чем-то совсем другим?
const repoMap = new Map<string, any>([
  ["api-repo-1", ApiRepo1],
  ["api-repo-2", ApiRepo2]
])

Тут надо учесть, что по факту в двух репозиториях какие-то методы пересекаются, а какие-то объединяются (я имею в виду union и intersection).
Поэтому, скажем, в таком виде было бы некорректно, поскольку часть методов просто выпадет из списка типов.
type TRepoUnion = typeof ApiRepo1 | typeof ApiRepo2
const repositoriesMap = new Map<string, TRepoUnion>()

Равно как и intersection тут не прокатит - сразу посыпятся ошибки.
Использовать тут дженерик не получится, как мне кажется, т.к., собственно, откуда он возьмется - ведь в пропсы невозможно передать тип, а микроприложение понятия не имеет, с каким репозиторием ему предстоит работать. Оно знает только имя.

2. Как типизировать response внутри микроприложения. Но тут попроще - по сути, независимо от репозитория, который используется, респонсы должны быть одинаковыми, т.е. их нужно в самой либе приводить к некоему унифицированному виду. Иначе, понятно, невозможно будет пользоваться. Но опять же, пока в Map не определен тип репозитория - респонс будет тупо any.

3. Ну и в целом, буду рад каким-то прочим советам и альтернативным взглядам на реализацию подобной задачи.
  • Вопрос задан
  • 80 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы