В данной статье автор рассматривает вопрос изменения представления данных и объектов, и косяков, которые за этим изменением неотвратимо следуют. Он предлагает в таких случаях везде и всюду использовать сериализаторы, которые способны без потерь конвертировать один формат представления в другой. Это позволяет не только передавать данные по сети и сохранять в файлы. Когда у вас есть настроенная библиотека сериализации, вы можете сменить хранилище данных, когда вам удобно и без ущерба для проекта. Также становится легко возвращать ответы в том виде, в котором их удобно получить запрашивающей стороне.
Дисклеймер. Только что начал читать habrahabr.ru/post/260975. Перевод очень корявый, халтурный. Я начал было писать автору более привычные нашему уху варианты перевода некоторых фраз, дабы помочь ему это всё поправить, но потом понял, что при прочтении спотыкаюсь на каждой третьей фразе. Решил запилить целиком свой вариант. Хотя это и не совсем по-честному, но заставлять читать такого уровня переводы тоже не очень по-людски. Я бы даже сказал, весьма по-машинному. Ну да пусть сообщество рассудит. Текст действительно непростой, свои комментарии я буду оставлять курсивом. Перевожу неглядя уже в перевод drondo. Если где совпадёт — менять не буду. Всё-таки я перевожу английские слова на наиболее похожие русские слова, как и drondo, однако же Дьявол кроется в деталях. Об опечатках пожалуйста сообщайте в личку, я утром всё исправлю. Спасибо.
За год я тысячу раз говорил про опасные места в API. Только в 2015 это было на:
Часть доклада, которую я называю «добавляем слой представления к данным», где я говорю о серилиализации, получила большой отклик.
MSDN говорит об этом так:
Сериализация это процесс преобразования объекта в поток байтов с целью сохранить объект или передать его в память, базу данных или файл. Её основное предназначение состоит в сохранении состояния объекта, чтобы иметь возможность воссоздать его при необходимости. Обратный процесс называется десериализация.
Источник: MSDN Programming Concepts
PHP-разработчики зачастую под сериализацей понимают применение функции serialize(). Да, это один из вариантов, но не единственный. Другой распространённый способ сериализации конечно же функция json_encode(). Современные фреймворки автоматически преобразуют в JSON любой массив, вернувшийся из метода контроллера. А значит вам самим даже не придётся вызвывать json_encode().
Это весьма удобно, но если вы создаёте HTTP API (AJAX/RESTful/Hypermedia), то нужно быть крайне внимательным с тем, что вы выводите.
Вот вам наиболее частые грабли:
<?php
class PlaceController extends CoreController
{
public function show($id)
{
return Place::find($id);
}
}
Извиняюсь за крайне упрощённый пример, суть в том, что мы говорим о модели (возможно использующей ORM) и о возврате результата.
На вид всё очень мило, но в итоге будет куча проблем.
Хватит секретов
Каждый метод, который вы добавите в ваше хранилище данных, в итоге отправится в ответ вызвавшему API. Если это внутреннее API, то ещё не так страшно. Но если эта информация проскочет где-то в браузер, или в какой-то другой внешний источник типа мобильного устройства, то всё будет очень хреново.
Самый очевидный пример это пароли пользователей. Они конечно же зашифрованы, но вы же явно не хотите отдавать их чужим людям. Утечка не столь бросающихся во внимание вещей типа токенов сброса пароля также может привезти ко взлому ваших пользователей.
Но всё может случиться ещё незаметнее. В примере с «place» (прим.пер.: PlaceController) из бизнесс-процесса может возникнуть необходимость добавить «контактный email» для внутреннего пользования. Если вы потратили месяцы труда и наладили уникальные связи (прим. пер.: в бизнесе), вам не захочется, чтобы эти почтовые адреса утекли к вашим конкурентам.
Да, во многих ORM можно указать список видимых или скрытых свойств. Но с течением времени шансы скрыть все важные параметры тают. Особенно если вы обзавелись junior'ом, который ещё не в курсе, что одно из этих полей суперсекретно. А уставший проверяющий тяжёлым рабочим днём позволит всему этому делу просочиться незамеченным.
Вот один из примеров из Fractal — моей PHP-библиотеки для сериализации, которую я сделал, чтобы упростить сериализацию в моих API:
<?php
use League\Fractal\Manager;
use League\Fractal\Resource\Collection;
// Create a top level instance somewhere
$fractal = new Manager();
// Ask the ORM for 10 books
$books = Book::take(10)->get();
// Turn this collection
$resource = new Collection($books, function(array $book) {
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => (int) $book->yr,
'author' => [
'name' => $book->author_name,
'email' => $book->author_email,
],
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
]
];
});
Да, тут всё снова крайне упрощено, и используются колбэки вместо классов, но основная идея ясна.
Такие же инструменты найдутся для каждого языка на свете. Я работал с ActiveModel Serializer. Он почти такой же.
Независимо от языка, который вы используете на этой неделе, я бы хотел объяснить вам, почему это так важно. Как это делать, вы можете узнать сами. А эта статья — про почему.
Типы данных аттрибутов
Когда дело касается драйверов привязки данных, многие языки, включая PHP, оказываются не слишком умны. У таких штук вроде MySQL или PostgreSQL много всяких типов данных: целые числа, флоаты, булев тип и т.д. Но то, что приходит юзеру — это всегда строка.
Вместо true и false вы видите «1» и «0», или даже «t» и «f». Флоаты из -41.235 превращаются в "-41.235".
Для языка со слабой типизацией это может показаться не очень-то и важным. Но языки со строгой типизацией повылетают, если вдруг случится такое изменение. Пару раз я видел, как строка с числом превращается в integer из-за какой-то математики в акцессоре ORM, в котором «1» + «2» = 3. Такое преобразование в принципе может пройти ваши юнит-тесты, если они недостаточно чёткие. Но вашему iOS приложение это просто снесёт башку.
ActiveRecord в Rails отследит какой у поля должен быть тип данных, когда они добавляютя в схему через миграции. Но если что-то изменится — в акцессорах, или при изменении типа схемы — то жди беды.
Если использовать сериализацию как в примере выше, то вы сможете сами скастовать тип на ваши данные, и при выводе быть уверенными, что это именно тот самый тип, и поменяется он только если вы сами его поменяете в сериализаторе.
Смена имени поля
Переименование полей в хранилище данных не должно ломать ваше API. Если вам кажется больно напряжным обновлять все ваши тесты, то представьте каково разработчикам мобильных приложений и прочим фронтэнд командам, которым нужно обновлять и деплоить новые приложения. Может вы даже забыли lock-step (прим.пер.: нужна помощь с переводом этого дела. ближайшее русское слово, как это ни смешно, получается «запараллелить») деплой. Если да, то ваши пользователи окажутся с нерабочими приложениями, которые, даже если вы выпустите апдейт на iOS, так и будут нерабочими, пока они их не обновят.
Всё что вам нужно, чтобы обойти этот ад смены имени поля, это слой сериализации, который позволит вам обновить ссылку на поле, не меняя его внешнее представление.
Различные хранилища данных
Многие из этих ORM'ных способов сериализации имеют одно очень важное допущение: все ваши данные всё время живут в одном и том же хранилище. А что будет если часть ваших данных покинет SQL и переедет в Redis или ещё куда?
Даже если вы и не переносили часть данных из SQL в Redis, может вы разделили свою таблицу на две? Или стали использовать сводные таблицы? Большинство ORM сериализаторов просто
А может вместо этого воспользуетесь паттерном «репозиторий», который стал так популярен в Laravel? Вы можете передать все данные откуда угодно, где они хранятся, в сериализатор, а сериализатор позаботится о консистентности результата.
Версии сериализаторов
Когда-то у меня были сериализаторы разных версий. v1 и v2 FooSerializer могут обе существовать, каждая со своими тестами, и удовлетворят различные нужды клиентов API. (прим.пер. я не понял, к чему это он. Возможно v1 и v2 настолько разные, что воспринимаются как самостоятельные продукты и существуют параллельно)
Форматы сериализаторов
То, что ещё не удалось Fractal, но планируется исправить к v1.0 — это различные «адаптеры» форматов. Такое уже делали в сообществе Rails — можно было отправлять разные заголовки и получать разные форматы ответа.
Отправив mime-тип вы можете сказать сериализатору в каком формате присылать ответ, не засоряя весь ваш код потенциально сложной логикой.
Решения
Я мог бы приводить причины дни на пролёт, но сегодня мой самолёт сел в пять утра, а ребятки на соседних креслах не давали мне спать всю ночь.
Я раскрыл тему «почему» в сериализации, но не «как». Об этом рекомендую взглянуть на эти решения:
- Fractal — PHP
- JMSSerializerBundle — PHP + Symfony2
- Marshmallow — Python
- ActiveModel Serializer — Ruby + Rails
- JBuilder — Ruby + Rails
- Roar — Ruby
Я слышал прекрасный долклад на RailsConf 2015 моего нового друга Joao Moura, который назывался История любви AMS, API, Rails и разраба, в котором раскрывается кое-какая крутая функциональность.
Какую бы систему вы ни выбрали — там везде примерно одна идея.
Если вы делаете API любого вида — пожалуйста, используйте это.
API это не просто прокси для SQL команд. Его нужно планировать, обдумывать и заботливо поддерживать. И тогда простое измение места хранения данных не угробит все ваши сетевые приложения и сервисы.
maximaxsh
Спасибо. За перевод. На работе огребаю все те проблемы о которых написано :(
Может быть подскажите удобный сериализатор для java.
bromzh
Jackson
maximaxsh
Спасибо.
igordata Автор
Спасибо. Я старался.
Про сериализатор к сожалению подсказать не могу, т.к. я скорее работодатель-пхпешник, а таких лучше не слушать. Обычно я сам спрашиваю у профи, что на их взгляд лучше.