Доброе время суток, уважаемое Хабр коммьюнити. В этой публикации я хотел бы показать несколько известных мне подходов к версионной миграции данных в контексте
DTO: Контроллер: В какой-то прекрасный момент Вам говорят, что хотелось бы использовать другой тип данных для номера телефона пользователя и возвращать его как число, а не текст как было прежде. Конечно же, если команда: может себе позволить содержать сервера с разными версиями серверного приложения (api.host.v1, api.host.v2) и умеет их грамотно поддерживать. В таком случае Вы постигли дзен и все у Вас хорошо) Цель статьи — показать как можно разворачивать новые версии серверных приложений не затрагивая при этом клиентское приложение, чтобы оно смогло в достаточной мере отладить взаимодействие с новыми полями/типами данных прежде чем было опубликовано. Ну и не положить прод, конечно 🙂 В этом случае просто в существующее DTO добавляется новое поле с постфиксом версии и старое помечается как устаревшее(в кодовой базе) для дальнейшего избавления от него. Старое поле продолжается поддерживаться до момента полного отказа в его использовании на стороне клиентского приложения. Такой вариант хорошо подходит если команда занимается разработкой веб-приложений и Вам доступна быстрая миграция данных (имеется в виду, что вам не нужно очень долго поддерживать старые версии данных для клиентов). Если будет сильно смущать постфикс — можно сделать еще одну миграцию и просто удалить его и в дальнейшем избавиться от устаревших полей. В случае когда клиенты очень долго «переезжают» на новые версии приложений (типичный пример мобильной разработки, переезд на более новую версию может идти годами), то рано или поздно DTO превратиться в сущий ад если мигрировать данные через поле. Такой код будет сложно поддерживать, так как подобных миграций в период жизни приложения может быть достаточно много. В данном случае можно выделить новый класс DTO. При таком подходе избегается ситуация когда правок в классе становится настолько много, что начинаются трудности с взаимодействием и поддержкой, но при этом увеличивается число методов, которые отвечают за преобразование данных из БД в DTO. В таком случае можно воспользоваться шаблоном Фабрика и на каждую версию DTO создавать реализацию, которая будет отвечать за представление конкретной версии, чтобы выдавать клиентскому приложению версию DTO с которой он может работать. В случае с контроллером есть тоже два варианта как можно реализовать версионность. Так же есть хорошая статья которая описывает процессы версионирования конкретно API. Такой подход хорош при быстрой миграции, но поддерживать кучу таких методов может рано или поздно стать головной болью разработчика так как логика конвертации модели в DTO между такими методами будет «размазана». В данном случае передается заголовок Accept в котором указывается конкретная версия JSON объекта которую готов принимать клиент. Пример: При данном подходе может затрудниться документирование системы так как один и тот же эндпоинт возвращает не конкретное представление, а сразу несколько. Если Ваша система документирования способна это описать, то проблем не возникнет и дальнейшие правки будут согласовыватьсся быстрее. Конечное решение о использовании того или иного подхода остается за разработчиком. Каждый описанный вариант подходит для конкретного случая. Было бы очень интересно узнать, как именно Ваша компания проводит миграцию данных)@Getter public class UserDto { private Long id; @JsonProperty("phone_number") private String phoneNumber; }@RestController public class UserController { @GetMapping("/api/users/{user_id}") public UserDto getUser(@RequestParam("user_id") Long userId) { // Проверка существования пользователя и получение из БД // Вызов логики обработки // Конвертация модели в DTO return userDto; } }HTTP
GET /api/users/{user_id} Host: api.host Accept: application/json { id: 0, phone_number: '+70123456789' }
состоит из волшебниковможет развернуть серверное и клиентское приложение одновременно, ничего при этом не сломав в продакшене;Миграция через добавление нового поля
Java
@Getter public class UserDto { private Long id; @Deprecated @JsonProperty("phone_number") private String deprecatedPhoneNumber; @JsonProperty("phone_number_v1") private Long phoneNumber; }JSON
UserDto: { id: 0, phone_number: '+70123456789', phone_number_v1: 70123456789 }Миграция через создание новой версии объекта
Java
@Getter @Deprecated public class UserDto { private Long id; @JsonProperty("phone_number") private String phoneNumber; } @Getter public class UserDtoV1 { private Long id; @JsonProperty("phone_number") private Long phoneNumber; }JSON
UserDto: { id: 0, phone_number: '+70123456789' } UserDtoV1: { id: 0, phone_number: 70123456789 }Что насчет контроллеров?
Миграция через новую версию HTTP метода
Java
@RestController public class UserController { @Deprecated @GetMapping("/api/users/{user_id}") public UserDto depreactedGetUser(@RequestParam("user_id") Long userId) {} @GetMapping("/api/users/{user_id}/v1") public UserDtoV1 getUser(@RequestParam("user_id") Long userId) {} }HTTP
GET /api/users/{user_id} Host: api.host Accept: application/json { id: 0, phone_number: '+70123456789' } ------------------------------ GET /api/users/{user_id}/v1 Host: api.host Accept: application/json { id: 0, phone_number: 70123456789 }Миграция через заголовок запроса
Java
@RestController public class UserController { // Вместо возвращаемого типа Object можно создать UserBaseDto // в который можно вынести общие поля и использовать его как возвращаемый // тип метода @GetMapping("/api/users/{user_id}") public Object getUser( @RequestParam("user_id") Long userId, @RequestHeader("Accept") String acceptMimeType) { // Извлекаем версию конечного объекта из заголовка Accept // Формируем конечный объект return suitableUserDto; } }HTTP
GET /api/users/{user_id} Host: api.host Accept: application/json Response object: { id: 0, phone_number: '+70123456789' } ------------------------------ GET /api/users/{user_id} Host: api.host Accept: application/json;v=1 Response object: { id: 0, phone_number: 70123456789 }Заключение
P.S.
ссылка на оригинал статьи https://habr.com/ru/post/720478/
Добавить комментарий