Где находится ваш глубокий объект в запросе?

Где находится ваш глубокий объект в запросе?

9 ноября 2022 г.

Распространенная ситуация, когда в приложении многие запросы содержат параметры запроса. Начиная с версии v3 стандарт OpenApi предлагает еще один способ ввода параметров запроса через DeepObject. Спецификацию стандарта можно найти здесь.

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

{YOUR_DOMAIN}/[PATH]?[QUERY_PARAMETERS]

e.g.

https://test.com/users?userName=test_user&isBlocked=true

В новом способе аннотации запроса каждый из параметров запроса будет иметь префикс — имя объекта или уникальный id другими словами. И свойства будут заключены в квадратные скобки [QUERY_PARAMTER]. Я могу переписать предыдущий URL-адрес на новый с помощью этого подхода:

https://test.com/users?uq[userName]=test_user&uq[isBlocked]=true

В приведенном выше примере uq представляет объект с полями userName и isBlocked. Его можно записать как типичный JSON, например:

{
  uq: {
    userName: 'test_user',
    isBlocked: true
  }
}

Таким образом, на первый взгляд, этот подход только добавляет дополнительные данные в строку запроса. Но давайте посмотрим, как это будет представлено на стороне Frontend и Backend и когда будут более сложные запросы.

Где обычно находятся более сложные запросы? Одно место, где они начали свою жизнь, — это нумерация страниц. Когда полученные данные массива достаточно велики, достаточно проблематично извлечь все данные из БД и отправить их обратно пользователю за короткое время.

Внешняя часть

С точки зрения пользовательского интерфейса будет удобнее добавить дополнительную библиотеку, если вы, конечно, еще не использовали ее. Пакет QS — QueryString. Это помогает сопоставить объект запроса со строкой, которая будет добавлена ​​к URL-адресу.

import qs from 'qs';

qs.stringify(params, {
    arrayFormat: 'brackets',
    encodeValuesOnly: true,
    skipNulls: true,
});

Вы можете использовать эту функцию stringify для преобразования объектного запроса в соответствующее строковое значение.

const paginationQuery = { take: 100, skip: 4000 };
const params = { pq: paginationQuery };

const queryString = qs.stringify(params, {
    arrayFormat: 'brackets',
    encodeValuesOnly: true,
    skipNulls: true,
});

// It eventually will be translated to something like:
// https://test.com/users?pq[take]=100&pq[skip]=4000

Таким образом, это в значительной степени делается на стороне FE с помощью deepObject. В зависимости от того, какой программный интерфейс вы хотите использовать, стандартный Fetch API или некоторую библиотеку, вам нужно будет добавить строку запроса результата к URL-адресу, к которому вы хотите получить доступ. Это кажется более сложным, но повышает удобство сопровождения кодовой базы.

Внутренняя часть

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

@QueryParams('take', Number)
@QueryParams('skip', Number)
@QueryParams('searchText', String)
// e.t.c

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

@Description('Get run results')
@Get(endpoints.REPORT_DETAILS)
async getRunResults(
  @PathParams(WS_ID, Number) workspaceId: number,
  @PathParams(MODEL_ID, Number) modelId: number,
  @PathParams(RUN_ID, String) runId: string,
  @QueryParams(PAGINATION_QUERY, PaginationQuery)
  @GenericOf(RunTestView) paginationQuery: PaginationQuery<RunTestView>
): Promise<ApiResponse<PaginationResult<RunTestView>>> {
    const paginationResult = await this.reportService.findRunResults(
      workspaceId,
      modelId,
      runId,
      paginationQuery
    );
    return ApiResponse.ok(paginationResult);
}

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

@Generics('T')
export class PaginationQuery<T> {
    @Minimum(1)
    @Default(50)
    public take?: number;

    @Minimum(0)
    @Default(0)
    public skip?: number;

    @Property()
    @GenericOf('T')
    public order?: Order<T>;

    @Property()
    public searchText?: string;

    @Property()
    @GenericOf('T')
    public filters?: Filters<T>;
}

Кроме того, полезно, когда вам нужно использовать параметры запроса, которые фактически будут использоваться для различных логических операций, таких как запрос на разбиение на страницы и параметры запроса, для включения в ответ дополнительных полей, таких как: { pq: paginationQuery, fq: fieldsQuery }.

Хороший лайфхак

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


Оригинал