2014-10-20 70 views
6

我在写一个HTTP REST API,我想在Scala中强类型化模型类,例如如果我有一个汽车模型Car,我想创建以下REST风格/car API:部分模型的斯卡拉成语?

1)POST S(创建一个新的车):

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

2)对于PUT S(编辑现有汽车)和GET S,我想沿着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 
+1

只是评论是什么感觉就像一个代码/设计的味道。你的实体是汽车 - 带ID,这就是你的领域模型应该包含的内容,这就是应该坚持的东西。您的REST请求CRU [D]模型瞬态操作 - 创建汽车,更新汽车,获取汽车,并且您应该有一个对象模型,以清楚它们是否是请求。 – 2014-10-20 12:33:46

+0

@Paul:即使我为所有这些创建了单独的模型,并且说我有一个域模型“Car.scala”和一个对象模型“CreateCarRequest.scala”,许多字段会一遍又一遍地重复。 – pathikrit 2014-10-29 00:25:57

回答

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 

随着邮政&把一切简单的结合。补丁有点复杂。我很确定CarUpdate类可以用自动生成的宏替换。

如果你更新你的车型,你一定不会忘记补丁,因为它在编译时会失败。但是,这两个模型看起来太“复制粘贴”。

-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合同。

+0

是的,这会起作用。我正在寻找更强的类型,例如我不想手动检查我的'PUT'请求,所有这些都是'isDefined',我不必在我的POST中检查'id'是'None'。如果我首先使用了显式模型,那么我最终要写的检查有效性的断言的数量可能会达到相同数量的代码行。另外,我很容易犯一个错误。为我的'Car'模型添加一个新字段现在也需要添加它的断言。为什么我应该在编译器可以为我做静态检查时编写动态检查! – pathikrit 2014-10-30 06:15:23

+0

同意。但是这些问题产生的原因是不明确的决定:你应该有一个内部的汽车表示(带有可选的id)和3个不同的资源对象,每个路线对应一个精确的字段。通过这种方式,您还可以将验证委托给外部对象(可能需要“需要”),并假定内部是内部的。 (这是我通常的工作流程) – 2014-10-30 10:21:40

+0

我可以问低调选民为什么他们觉得答案需要投票吗?我愿意学习,但你需要评论! >。< – 2014-11-03 14:35:47

3

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

+0

好主意。有没有无形记录的JSON序列化? – pathikrit 2014-11-01 17:58:50

+0

我见过有人在IRC上谈论实施这样的事情,但我不知道任何完整的,已发布的解决方案。 (就像我说过的,我曾经自己写过一个,但它属于我以前的雇主) – lmm 2014-11-01 19:30:26

+0

https://github.com/fommil/spray-json-shapeless包含几乎(但不是完全)所有需要的代码执行这个。 – lmm 2016-08-02 22:47:46