Как была устроена сериализация на прошлом месте работы?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Общая архитектура сериализации
На прошлом месте работы мы строили сложное финансовое приложение с распределённой микросервисной архитектурой, что требовало надёжной, производительной и типобезопасной сериализации данных между клиентом (iOS-приложением) и бэкендом, а также для локального хранения. Мы использовали комбинированный подход, выбирая технологию в зависимости от конкретной задачи.
Ключевые технологии и их применение
1. JSON для сетевого взаимодействия (REST API)
Основным форматом обмена данными с сервером был JSON. Для работы с ним мы использовали фреймворк Codable, интегрированный в Swift.
- Структура моделей: Все модели данных (например,
User,Transaction,Portfolio) реализовывали протоколCodable. Для кастомного маппинга полей (когда имена ключей в JSON отличались от имён свойств в модели) использовали enumCodingKeys.
struct TransactionResponse: Codable {
let id: String
let amount: Decimal
let currency: String
let date: Date
let status: TransactionStatus
// Кастомный маппинг и обработка даты
enum CodingKeys: String, CodingKey {
case id = "transaction_id"
case amount
case currency
case date = "created_at"
case status
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
amount = try container.decode(Decimal.self, forKey: .amount)
currency = try container.decode(String.self, forKey: .currency)
status = try container.decode(TransactionStatus.self, forKey: .status)
// Кастомный декодер для даты из timestamp
let timestamp = try container.decode(Double.self, forKey: .date)
date = Date(timeIntervalSince1970: timestamp)
}
}
- Кастомные JSONDecoder/Encoder: Мы создавали синглтон-инстансы
JSONDecoderиJSONEncoderс общей конфигурацией для всего приложения (стратегии дат, обработка чисел, форматирование ключей).
extension JSONDecoder {
static let appDecoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .custom { decoder in
// Универсальная логика для парсинга дат из разных форматов
}
return decoder
}()
}
- Сетевой слой: Внутри нашего сетевого слоя (построенного на
URLSessionс использованиемAsync/Await) сериализация была инкапсулирована. После получения данных вызывалсяJSONDecoder.appDecoder.decode(SomeModel.self, from: data).
2. Protocol Buffers (Protobuf) для high-load и real-time данных
Для отдельных микросервисов, где была критична скорость передачи и размер пакетов (например, live-котировки на бирже, поток событий), мы использовали Protocol Buffers.
- Экосистема: Мы использовали официальный компилятор
protocс плагином SwiftProtobuf..proto-файлы делились между бэкенд- и мобильными командами через Git-субмодуль. - Преимущества: Значительное снижение размера данных (до 50-70% по сравнению с JSON) и более быстрый парсинг за счет бинарного формата и строгой схемы.
- Интеграция: Модели, сгенерированные
swift-protobuf, интегрировались в общий поток данных приложения. Для их преобразования в "родные" модели приложения мы использовали lightweight-адаптеры.
3. Swift Codable + PropertyList для локальных настроек
Для хранения простых пользовательских настроек (UserDefaults) и конфигураций мы использовали сериализацию в Property List (plist). Модели, сохраняемые таким образом, также реализовывали Codable, а для кодирования применялся PropertyListEncoder.
struct AppSettings: Codable {
var isBiometryEnabled: Bool
var preferredCurrency: String
}
class SettingsManager {
private let userDefaults = UserDefaults.standard
func save(settings: AppSettings) {
if let data = try? PropertyListEncoder().encode(settings) {
userDefaults.set(data, forKey: "app_settings")
}
}
}
4. Продвинутая сериализация для Realm и Core Data
Для локальной базы данных мы использовали Realm (ранее пробовали Core Data).
- Realm Object: Модели наследовались от
Objectи были живыми объектами, привязанными к базе. Их сериализация в чистые Swift-типы (Codable-модели) была частой операцией. - Мапперы: Мы разработали слой мапперов/конвертеров (паттерн Converter/Adapter), который преобразовывал
Realm ObjectвCodable structи обратно. Это было необходимо для:
1. Изоляции слоя хранения от бизнес-логики.
2. Передачи данных в UI-слой, который не должен знать о Realm.
3. Подготовки данных для отправки на сервер.
// Пример маппера
struct TransactionMapper {
static func toDomain(_ realmObject: RealmTransaction) -> Transaction {
return Transaction(
id: realmObject.id,
amount: realmObject.amount,
date: realmObject.date
)
}
static func toRealm(_ domain: Transaction) -> RealmTransaction {
let obj = RealmTransaction()
obj.id = domain.id
obj.amount = domain.amount
obj.date = domain.date
return obj
}
}
Принципы и выводы
- Единая точка конфигурации: Все кастомные настройки кодировщиков/декодировщиков (даты, числа, ключи) были централизованы.
- Разделение ответственности: Сетевой слой отвечал за первичную десериализацию из данных в
Codable-модели. Далее эти модели могли конвертироваться в объекты базы данных или в оптимизированные типы для UI. - Безопасность типов: Использование
Codableи сгенерированного кода Protobuf минимизировало ошибки из-за опечаток в ключах и несоответствия типов. - Производительность: Выбор формата (JSON/Protobuf) был осознанным решением, основанным на требованиях конкретного endpoint'a или потока данных.
- Тестирование: Все критичные мапперы и
CodingKeysпокрывались юнит-тестами с использованием как валидных, так и битых JSON-фикстур.
Такая гибридная, но структурированная система обеспечивала гибкость, производительность и поддерживаемость в большом проекте с постоянно меняющимися бизнес-требованиями.