2021-05-15

Spring Data REST 初步心得

測試環境:

  • OpenJDK 14
  • Spring Boot 2.2.4

注意

這是在「REST 只有基本概念、Spring 碰不到一個月而且沒認真讀過文件」的情況下,在混亂的實做中得到的混亂廢話

因為 Spring 幾乎沒碰過,所以從頭 (明明就是鞋子) 開始講起。

Spring Boot 真的讚,pom.xml 加幾個 dependency,弄個 @SpringBootApplication 的 main class,就連 Tomcat(包含自動 reload)都幫你啟動完畢。瞬間把 Eclipse 那個越來越難用的 Servers view 給關了省的佔畫面(不過萬一要同時多個 webapp 運作可能還是… 算了到時候再說 XD)

@RestController 大概也是簡化的極限了,預設情況下連 return 值轉 JSON 變成 response 內容都自動幫你處理好,找不到可以抱怨的點(因為也沒寫過幾個… [逃])

Spring Data JPA 稍微棘手一點,自動掃 @Entity class 一直沒成功,然後又想搞可以自訂檔案位置(而不是設定檔寫死)的 H2,所以稍微花了一點時間。不過已經比想像中的還要簡單很多了。

撇開一些個人龜毛的部份,從 Spring 程度幾乎等於 0 的初學者(但是會基本 Maven、有用過 Hibernate)、到弄出一個 REST controller 來對 DB 做 CRUD,大概就是 10hr 以內的光景。當然,這裡說的是 POC 等級的程度。 是說我寫出來的東西好像也都只有 POC 等級

至於 Spring Data REST 嘛… 一句話就可以結案:「@Repository class 再加一個 @RepositoryRestResource(path = "foo")

立馬 /foo 就可以處理 GET / PUT… 了,就是這麼簡單~ 就是這麼神奇~

好,server side 收工!

才沒有這麼沒好… [眼神死]

第一個會炸到的問題是:「GET 的 response 要怎麼 unmarshal?」

不是直接把 List<Foo> 轉成 JSON 嗎?我也這麼希望,但就不是,而是長這樣(HATEOAS / HAL):

{
  "_embedded" : {
    "foos" : [ {
      "name" : "FooName",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/foo/2"
        },
        "location" : {
          "href" : "http://localhost:8080/foo/2"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/foo"
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/foo"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

真正的資料是在 ._embedded.foos,這還好,不算啥大事,而且有分頁功能好棒棒。問題是裡頭的 Foo 結構沒有掛 @Id 的 field。雖然這個去狗一下可以找到解決辦法,但終究得花上一些功夫才有可能轉回 Foo instance… (沒有實際測試,好像還得設法讓 gwt-jackson 忽略 "_links" 才能正常運作… )

這也許是用 GWT 的人才會有的困擾,畢竟 GWT 的賣點之一就是 server / client 可以使用同一份 code。如果 client 用純 JS 或許就沒問題,反正本來 就是假的 OOP 就沒有 code 可以用,要抓 JSON 中的值也可以用字串隨便指到想要的位置…

目前的作法是直接跳過 @RepositoryRestResource 提供的 URI,自己另外寫一個吐單純 JSON 的 REST controller… 💃

第二個炸點就更麻煩了:「不會處理 @ManyToOne(其他沒測)的 field」。

假設前頭的 Foo 是這樣:

@Entity
public class Foo {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	private String name;
	
	@ManyToOne
	private Wtf wtf;
}

GET 得到的 JSON 不會出現 wtf、POST 也會跳過這個 field 不處理,直接塞 null。

這樣設計有沒有道理?應該有,至少 GET 的傳輸量就可以少很多(都說 many to one 了嘛)。但是完全連轉都沒轉是不是合理?為什麼不像 ORM 時 wtf 會轉換成 WTF_ID column 那樣做 JSON marshal?(還是說有神秘的設定檔可以改這行為?)(沒有試著找過,因為連關鍵字都不知道怎麼下 XD)

總之,沒用到就沒事,只要有用到 ManyToOne 的話,Spring Data REST 大概就是爛給你看。如果是剛出生沒多久的 project 或許還改得動,不然自己手寫 REST controller 可能還比較實在… 😱

沒有意義的結論

  • Spring 真的強,強到「完全不敢想像背後程式是怎麼寫」的那種強
  • 為什麼 HATEOAS / HAL 會設計成這樣子 (之前接觸 FHIR 的時候好像也是這付德性,原本還以為是 FHIR 亂搞…)
  • 目前看起來 GWT 這邊沒有 library 可以對付 HATEOAS / HAL,不知道 JS 那邊… 算了,我不想知道
  • Spring Data REST (至少)還有 validate 等功能要 try… 😭

最後就是… 嗯… 我真的很懷念 GWT RPC。要是有時程壓力,我一定馬上回頭用 GWT RPC… [遠目]