Каков наилучший способ сопоставления шаблонов URL REST с объектами модели для платформы Siesta?

Я хотел бы использовать ResponseTransformer (или их серию) для автоматического сопоставления классов моей объектной модели с ответами, возвращаемыми из службы Siesta, так что мои ресурсы Siesta являются экземплярами моих классов моделей. У меня есть рабочая реализация для одного класса, но я хотел бы знать, есть ли более безопасный, умный или более эффективный способ сделать это, прежде чем я создам отдельный ResponseTransformer для каждого типа ресурса (модели).

Вот пример класса модели:

import SwiftyJSON

class Challenge {
    var id:String?
    var name:String?

    init(fromDictionary:JSON) {
        if let challengeId = fromDictionary["id"].int {
            self.id = String(challengeId)
        }
        self.name = fromDictionary["name"].string
    }
}

extension Challenge {

    class func parseChallengeList(fromJSON:JSON) -> [Challenge] {
        var list = [Challenge]()

        switch fromJSON.type {
        case .Array:
            for itemDictionary in fromJSON.array! {
                let item = Challenge(fromDictionary: itemDictionary)
                list.append(item)
            }
        case .Dictionary:
            list.append(Challenge(fromDictionary: fromJSON))
        default: break
        }

        return list
    }
}

А вот ResponseTransformer, который я создал для сопоставления ответа от любой конечной точки, которая возвращает либо коллекцию этого типа модели, либо один экземпляр этого типа модели:

public func ChallengeListTransformer(transformErrors: Bool = true) -> ResponseTransformer {
    return ResponseContentTransformer(transformErrors: transformErrors)
        {
            (content: NSJSONConvertible, entity: Entity) throws -> [Challenge] in        
            let itemJSON = JSON(content)        
            return Challenge.parseChallengeList(itemJSON)
    }
}

И, наконец, вот сопоставление шаблонов URL-адресов, которое я выполняю при настройке службы Siesta:

class _GFSFAPI: Service {

    ...

    configure("/Challenge/*")    { $0.config.responseTransformers.add(ChallengeListTransformer()) }
}

Я планирую создать отдельный ResponseTransformer для каждого типа модели, а затем индивидуально сопоставить каждый шаблон URL с этим преобразователем. Это лучший подход? Кстати, я очень взволнован новой платформой Siesta. Мне нравится идея ресурс-ориентированной сетевой библиотеки REST.

1 ответ

Решение

Ваш подход твердый! Вы в основном получили это. Есть несколько вещей, которые вы можете сделать, чтобы упростить трансформаторы.

Большая фотография

Похоже, вы уже поняли этот компромисс, но для тех, кто находит этот ответ... у вас есть два основных подхода на выбор:

  1. создайте свой модельный объект в наблюдателе Siesta или
  2. построить свой модельный объект в трансформаторе.

Вариант 1 проще в настройке - просто сделайте модель на месте, и все готово!

func resourceChanged(resource: Resource, event: ResourceEvent) {
    let challenges = Challenge.parseChallengeList(
        JSON(resource.latestData?.jsonDict))
    ...
}

Это хорошо работает для многих проектов. Однако у него есть недостатки:

  • Вариант 1 создает новый объект модели для каждого события, умноженного на каждого наблюдателя; Вариант 2 создает экземпляр объекта модели только для ответа "новые данные".
  • В варианте 1 нет центрального места, которое отслеживает, какие маршруты отображаются на какие объекты модели.
  • Вариант 2 дает лучшие ошибки, если сервер не возвращает ожидаемый тип контента.

Я предпочитаю вариант 1, если (и только если) проект небольшой, а модели легкие.

Подробную документацию по варианту 2 вы найдете в разделе " Конвейер" в руководстве пользователя. Вот краткий обзор.

С помощью configureTransformer

Вы можете упростить свой ChallengeListTransformer используя configureTransformer(...):

configureTransformer("/Challenge/*") {
    (content: NSJSONConvertible, entity: Entity) throws -> [Challenge] in        
    let itemJSON = JSON(content)        
    return Challenge.parseChallengeList(itemJSON)
}

Но подождите, это еще не все! Посмотрите, как удивительные срезы и кубики Swift для вас:

configureTransformer("/Challenge/*") {
    Challenge.parseChallengeList(
        JSON($0.content as NSJSONConvertible))
}

(Обратите внимание, что configureTransformer наборы transformErrors ложно. Это почти наверняка то, что вы хотите... если ваш сервер не отправит модель JSON "вызов" в качестве тела сообщения об ошибке! transformErrors Параметр обычно используется только для преобразователей общего назначения, таких как анализ текста и JSON, связанных с типом содержимого, но не связанных с маршрутом.)

Глобальный преобразователь SwiftyJSON

Если вы используете SwiftyJSON (который мне тоже нравится, кстати, BTW), вы можете применить его ко всем ответам JSON:

private let SwiftyJSONTransformer =
    ResponseContentTransformer(skipWhenEntityMatchesOutputType: false)
        { JSON($0.content as AnyObject) }

…а потом:

service.configure {
    $0.config.responseTransformers.add(
        SwiftyJSONTransformer, contentTypes: ["*/json"])
}

… Что еще больше упрощает преобразователь контента каждого маршрута:

configureTransformer("/Challenge/*") {
    Challenge.parseChallengeList($0.content)
}

Обратите внимание, что вывод типа Swift сообщает Сиесте, что этот преобразователь ожидает JSON struct as input, и Siesta использует это, чтобы пометить его как ошибку, если он не вышел из конвейера трансформатора таким образом. Все связанные с JSON трансформаторы подключены к */json типы контента, поэтому, если сервер возвращает что-то неожиданное, ваши наблюдатели увидят приятную ошибку "Эй, это не JSON!".

См. Руководство пользователя для более подробной информации обо всем этом.

Получение модели из ресурса

Поскольку Siesta API в настоящее время не работает, вам необходимо понизить содержание модели:

func resourceChanged(resource: Resource, event: ResourceEvent) {
    let challenges = resource.latestData?.content as? [Challenge]
    ...
}

Кроме того, вы можете использовать TypedContentAccessors методы расширения протокола для одновременного выполнения приведения и получения значения по умолчанию, если либо данные еще не представлены, либо приведение не выполнено. Например, этот код по умолчанию равен пустому массиву, если нет проблем:

func resourceChanged(resource: Resource, event: ResourceEvent) {
    let challenges = resource.typedContent(ifNone: [Challenge]())
    ...
}

В настоящее время Siesta не предоставляет статически типизированный способ привязки типа модели к ресурсу; Вы должны сделать бросок. Это связано с тем, что ограничения системы типов Swift не позволяют создать универсальный тип ресурса (например, Resource<[Challenge]>) от того, чтобы быть работоспособным на практике. Надеемся, что Swift 3 решит эти проблемы, так что некоторые будущие версии Siesta могут это обеспечить. Обновление: необходимые улучшения дженериков для Swift 3 отсутствуют, так что, надеюсь, в Swift 4….

Другие вопросы по тегам