2014-10-20 2 views
6

Я пишу HTTP REST API, и я хочу строго типизированных классов модели в Scala, например. если у меня есть модель автомобиля Car, я хочу создать следующий RESTful /car API:Scala идиома для частичных моделей?

1) Для POST с (создать новый автомобиль):

case class Car(manufacturer: String, 
       name: String, 
       year: Int) 

2) Для PUT сек (изменить существующий автомобиль) и GET s, я хочу тег вдоль id тоже:

case class Car(id: Long, 
       manufacturer: String, 
       name: String, 
       year: Int) 

3) Для PATCH эсов (частичное редактирование существующего автомобиля), я хочу этот частичный объект:

case class Car(id: Long, 
       manufacturer: Option[String], 
       name: Option[String], 
       year: Option[Int]) 

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

Существует ли типичный способ поддерживать все 3 модели? Я в порядке с ответами, которые также используют макросы.

мне удалось объединить первые два из них следующим образом

trait Id { 
    val id: Long 
} 

type PersistedCar = Car with Id 
+1

Просто комментируя то, что чувствует, как код/​​дизайн запах. Ваш фактический объект - это автомобиль - с идентификатором, и это то, что должна содержать ваша модель домена, и это то, что нужно сохранить. Ваш REST запрашивает временные операции модели CRU [D] - создайте автомобиль, обновите автомобиль, получите автомобиль, и у вас должна быть объектная модель, которая дает понять, что это запросы. –

+0

@Paul: Даже если я создаю отдельные модели для всех этих и скажу, что у меня есть модель домена 'Car.scala' и объектная модель' CreateCarRequest.scala', много полей повторяются снова и снова. – pathikrit

ответ

0

На самом деле мне удалось решить эту проблему, используя маленькую библиотеку, которую я написал: https://github.com/pathikrit/metarest

Использование выше библиотеки, это становится просто:

import com.github.pathikrit.MetaRest._ 

@MetaRest case class Car(
    @get @put id: Long, 
    @get @post @put @patch manufacturer: String, 
    @get @post @put @patch name: String, 
    @get @post @put @patch year: Int) 
) 
5

Я бы с чем-то вроде этого

trait Update[T] { 
    def patch(obj: T): T 
    } 

    case class Car(manufacturer: String, name: String, year: Int) 

    case class CarUpdate(manufacturer: Option[String], 
         name: Option[String], 
         year: Option[Int]) extends Update[Car] { 
    override def patch(car: Car): Car = Car(
     manufacturer.getOrElse(car.manufacturer), 
     name.getOrElse(car.name), 
     year.getOrElse(car.year) 
    ) 
    } 


    sealed trait Request 
    case class Post[T](obj: T) extends Request 
    case class Put[T](id: Long, obj: T) extends Request 
    case class Patch[T, U <: Update[T]](patch: U) extends Request 

С Post & Положите все просто. С Patch немного сложнее. Я уверен, что класс CarUpdate можно заменить автоматическим с помощью макросов.

Если вы обновите модель автомобиля, вы наверняка не забудете о патче, потому что она не сработает во время компиляции. Однако эти две модели выглядят слишком «copy-paste-like».

-1

Хотя я согласен с комментарием Павла (да, у вас было бы много дублированных полей, но это потому, что вы развязываете внешнее представление полей во внутреннее представление полей, что хорошо в если вы хотите изменить свое внутреннее представление без изменения API), возможный способ добиться того, что вы хотите, может быть (что, если я правильно понял, это иметь единое представление):

case class CarAllRepresentationsInOne(
    id: Option[Long] = None, 
    manufacturer: Option[String] = None, 
    name: Option[String] = None, 
    year: Option[Int] = None) 

Поскольку у вас есть значения по умолчанию для всех, заданных как «Нет», вы можете создать экземпляр этого CClass со всех маршрутов, при этом единственным недостатком является использование именованных параметров во время создания экземпляра и проверка на отсутствие во всех использование полей.

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

+0

Да, это сработает. Я искал что-то более строго типизированное, например. Я не хочу вручную проверять свой запрос 'PUT', что все это' isDefined', и мне не нужно проверять, что в моем POST 'id' является' None'. Количество утверждений, которые я мог бы написать для проверки действительности, вероятно, будет равным количеству строк кода, если бы я использовал явные модели в первую очередь. Кроме того, я могу легко ошибиться. Добавление нового поля в мою модель «Car» теперь также требует добавления его утверждений. Зачем мне писать динамические проверки, когда компилятор может делать статические проверки для меня! – pathikrit

+0

Согласовано. Но причина, из-за которой возникают эти проблемы, imho, - это плохое решение: у вас должно быть внутреннее представление автомобиля (с дополнительным идентификатором) и 3 разных ресурсных объекта, по одному для каждого маршрута, с точными полями. Таким образом, вы также можете делегировать проверку внешним объектам (возможно, «требуется») и предположить, что w/e является внутренним, это нормально. (Это мой обычный рабочий процесс в целом) –

+0

Могу ли я попросить избирателей о том, почему они считают, что ответ требует голосования по голосу? Я открыт для изучения, но вам нужны комментарии! >. < –

3

Вы можете представить свои модели как Shapeless records, тогда id - это просто еще одно поле на передней панели, а отображение в/из параметров можно выполнить в общих чертах, используя обычные методы программирования типа Shapeless.Также должно быть возможно обобщить/десериализовать такие вещи на JSON (я делал это в прошлом, но соответствующий код принадлежит предыдущему работодателю). Но вы определенно будете толкать границы и выполнять сложное программирование на уровне уровня; Я не думаю, что до сих пор существуют зрелые библиотечные решения с таким подходом.

+0

Хорошая идея. Существует ли сериализация JSON для бесформенных записей? – pathikrit

+0

Я видел, как люди говорили в IRC о реализации такой вещи, но я не знаю никаких полных опубликованных решений. (Как я уже сказал, однажды я написал один, но он принадлежит моему предыдущему работодателю) – lmm

+0

https://github.com/fommil/spray-json-shapeless содержит почти (но не совсем) весь код, который потребуется для реализовать это. – lmm

Смежные вопросы