Стандартный (функциональный) способ обработки клиентского модуля REST API, где каждое действие / функция должна получать базовый URL-адрес сервера? - PullRequest
0 голосов
/ 20 апреля 2020

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

Используя здесь TypeScript в качестве языка, допустим, у меня есть функция аутентификации, которая ожидает, что сервер вернет тело форма { "token": "JWT ....." }. Прямо сейчас у меня это смоделировано как

export const authenticate = (baseUrl: string) => 
    (username: string, password: string): Promise<HttpClientResponse<{token:string}>> =>
        httpClient.get(...)

Это прекрасно работает. В моем клиенте я могу

import { authenticate } from '...'

const result = await authenticate('http://example.com/api/v1.0/')('my-username','some-password')
console.log(result.token)

Но, учитывая, сколько у меня есть этих функций, которые требуют baseUrl (и большинство из них также jwtToken:string для помещения в заголовок Authorization), это кажется, что я должен использовать шаблон считывателя:

export const getProjectsForUser = (baseUrl: string, jwtToken: string) => (userId:number) =>
    httpClient.get(...)

// OR

type Defs = {
    baseUrl: string,
    jwtToken: string,
}

export const getProjectsForUserAlt = (userId: number): Reader<Deps, Promise<...>> => 
    (deps: Deps) => httpClient.get(deps.baseUrl+...)

(В случае библиотеки fp-ts на самом деле есть typealias для Reader<E, Lazy<Promise<A>>>, то есть ReaderTaskEither<E, A> (работает немного как ReaderT (Reader monad преобразователь) Я думаю, но я только упомяну это, чтобы показать, что я знаю об этом для этой библиотеки.)

Тогда, если у меня есть куча этих Readers, я мог бы хотеть чтобы сгруппировать их для удобства в других местах:

const APIInstance: Reader<Deps, APIInterface> = (deps:Deps) => ({
   getProfile: Reader<Deps, Profile> = ...
   createContact: Reader<Deps, number> = ...
   getContact: Reader<Deps, Contact> = ...
})

Тогда мне не нужно обмениваться baseUrl. Просто создайте экземпляр одного экземпляра API.

Но тогда это заставляет меня задуматься о Reader который возвращает объект Readers с тем же объектом зависимости / структурой / классом данных. Есть ли лучший способ сделать это?

Если клиентское приложение, использующее модуль API клиента, предположительно, передало baseUrl через переменную среды или конфигурацию файл, у вас есть часть "Депс", прежде чем у вас есть что-нибудь еще, так что читатели должны быть обменены? Например, getUser = (baseUrl:string) => Reader<UserId, User>, а затем вы вызываете с помощью getUser('http:...')(5) (с помощью fp-ts) или getUser("http://...").run(5) (с помощью стрелки Kotlin) и т. Д. c.

Каков рекомендуемый способ делать это? Это похоже на шаблон GOF для OOP (не знаю имени) для

class getUserUseCase {
  constructor(baseUrl:string) { this.url=baseUrl+'/user' }
  async fun run(id: number): User {
    // return await http GET this.url
  }
}
...