2014-10-20 88 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