← Назад к вопросам

Как была устроена сериализация на прошлом месте работы?

2.2 Middle🔥 171 комментариев
#Архитектура и паттерны#Работа с сетью

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Общая архитектура сериализации

На прошлом месте работы мы строили сложное финансовое приложение с распределённой микросервисной архитектурой, что требовало надёжной, производительной и типобезопасной сериализации данных между клиентом (iOS-приложением) и бэкендом, а также для локального хранения. Мы использовали комбинированный подход, выбирая технологию в зависимости от конкретной задачи.

Ключевые технологии и их применение

1. JSON для сетевого взаимодействия (REST API)

Основным форматом обмена данными с сервером был JSON. Для работы с ним мы использовали фреймворк Codable, интегрированный в Swift.

  • Структура моделей: Все модели данных (например, User, Transaction, Portfolio) реализовывали протокол Codable. Для кастомного маппинга полей (когда имена ключей в JSON отличались от имён свойств в модели) использовали enum CodingKeys.
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
    }
}

Принципы и выводы

  1. Единая точка конфигурации: Все кастомные настройки кодировщиков/декодировщиков (даты, числа, ключи) были централизованы.
  2. Разделение ответственности: Сетевой слой отвечал за первичную десериализацию из данных в Codable-модели. Далее эти модели могли конвертироваться в объекты базы данных или в оптимизированные типы для UI.
  3. Безопасность типов: Использование Codable и сгенерированного кода Protobuf минимизировало ошибки из-за опечаток в ключах и несоответствия типов.
  4. Производительность: Выбор формата (JSON/Protobuf) был осознанным решением, основанным на требованиях конкретного endpoint'a или потока данных.
  5. Тестирование: Все критичные мапперы и CodingKeys покрывались юнит-тестами с использованием как валидных, так и битых JSON-фикстур.

Такая гибридная, но структурированная система обеспечивала гибкость, производительность и поддерживаемость в большом проекте с постоянно меняющимися бизнес-требованиями.