Scala习惯用于部分模型?

时间:2014-10-20 03:56:44

标签: scala

我正在编写HTTP REST API,我想在Scala中使用强类型模型类,例如如果我有车型Car,我想创建以下RESTful /car API:

1)对于POST s(创建新车):

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

2)对于PUT s(编辑现有汽车)和GET,我也想在id旁边添加标签:

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

3)对于PATCH es(部分编辑现有汽车),我想要这个部分对象:

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

但保留3个基本相同的模型是多余的,容易出错(例如,如果我编辑一个模型,我必须记得编辑其他模型)。

是否有类型安全的方法来维护所有3个型号?对于使用宏的答案我也没关系。

我确实设法将前两个结合起来如下

trait Id {
  val id: Long
}

type PersistedCar = Car with Id 

4 个答案:

答案 0 :(得分: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&amp;把一切都很简单。补丁有点复杂。我很确定CarUpdate类可以用自动生成的宏替换。

如果您要更新Car模型,您肯定不会忘记补丁,因为它会在编译时失败。然而,这两个模型看起来太像“复制粘贴”。

答案 1 :(得分:3)

您可以将模型表示为Shapeless records,然后id只是前面的一个字段,并且使用普通的无形类型级编程技术可以完成到/从选项的映射。也应该可以将这些事物一般地序列化/反序列化为JSON(我过去已经这样做了,但相关代码属于以前的雇主)。但你肯定会推动边界并进行复杂的类型级编程;我不认为这种方法存在成熟的图书馆解决方案。

答案 2 :(得分: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)
)

答案 3 :(得分:-1)

虽然我同意Paul的评论(是的,你会有很多重复的字段,但这是因为你将字段的外部表示与字段的内部表示分离,这是一件好事,以防万一你想在不改变API的情况下改变你的内部表示,这是实现你想要的东西的可能方式(如果我理解正确,那就是有一个代表):

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

由于您拥有设置为None的所有内容的默认值,因此您可以从所有路由中实例化此CClass,唯一的缺点是在实例化期间必须使用命名参数并在字段的所有用法中检查None。

但我强烈建议您为内部表示和每个可能的外部请求资源使用不同的类型:它可能看起来像开头的代码重复,但您在世界范围内建模汽车的方式应该由所使用的资源分开在外部世界中,为了解耦它们并允许您在新需求出现时更改内部表示而不改变与外部的api合同。