У меня есть спецификация REST API, которая говорит с фоновыми микросервисами, которые возвращают следующие значения:
В ответах "коллекций" (например, GET/users):
{
users: [
{
... // single user object data
}
],
links: [
{
... // single HATEOAS link object
}
]
}
В ответах "один объект" (например, GET /users/{userUuid}
):
{
user: {
... // {userUuid} user object}
}
}
Этот подход был выбран таким образом, чтобы отдельные ответы были бы расширяемыми (например, возможно, если GET /users/{userUuid}
получает дополнительный параметр запроса по строке, такой как ?detailedView=true
, у нас будет дополнительная информация запроса).
В сущности, я считаю, что это подход OK для минимизации изменений разрыва между обновлениями API. Однако преобразование этой модели в код очень сложно.
Скажем, что для отдельных ответов у меня есть следующий объект модели API для одного пользователя:
public class SingleUserResource {
private MicroserviceUserModel user;
public SingleUserResource(MicroserviceUserModel user) {
this.user = user;
}
public String getName() {
return user.getName();
}
// other getters for fields we wish to expose
}
Преимущество этого метода заключается в том, что мы можем выставлять только поля из внутренних моделей, для которых у нас есть публичные геттеры, но не другие. Тогда для ответов коллекций у меня будет следующий класс оболочки:
public class UsersResource extends ResourceSupport {
@JsonProperty("users")
public final List<SingleUserResource> users;
public UsersResource(List<MicroserviceUserModel> users) {
// add each user as a SingleUserResource
}
}
Для однопоточных ответов у нас будет следующее:
public class UserResource {
@JsonProperty("user")
public final SingleUserResource user;
public UserResource(SingleUserResource user) {
this.user = user;
}
}
Это дает ответы JSON
, которые отформатированы в соответствии со спецификацией API в верхней части этой публикации. Поверхность этого подхода заключается в том, что мы раскрываем только те поля, которые мы хотим разоблачить. Тяжелый недостаток заключается в том, что у меня есть тонна классов-оболочек, которые летают вокруг, которые не выполняют никакой заметной логической задачи, кроме чтения Джексоном, чтобы получить корректно отформатированный ответ.
Мои вопросы таковы:
-
Как я могу обобщить этот подход? В идеале я хотел бы иметь один класс
BaseSingularResponse
(и, возможно, классBaseCollectionsResponse extends ResourceSupport
), который все мои модели могут расширять, но, видя, как Джексон выводит ключи JSON из определений объектов, мне нужно было бы что-то использовать пользователю например,Javaassist
, чтобы добавить поля к базовым классам ответов в Runtime - грязный хак, который я хотел бы оставить как можно дальше от человека. -
Есть ли более простой способ сделать это? К сожалению, я могу иметь переменное количество объектов JSON верхнего уровня в ответе через год, поэтому я не могу использовать что-то вроде Jackson
SerializationConfig.Feature.WRAP_ROOT_VALUE
, потому что это обертывает все в один объект на уровне корня (насколько мне известно). -
Возможно, что-то вроде
@JsonProperty
для уровня класса (в отличие от только метода и уровня поля)?