Где находится ваш глубокий объект в запросе?
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
также позволяет поддерживать проверку модели в классе и не распространять дублирование ограничений общих параметров запроса. Это дает возможность логически комбинировать параметры.
Оригинал