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… [遠目]

2021-02-19

升版後 GetValueProvider 導致 GXT Grid 炸 NPE

經過緩慢且冗長的演進之後,開發環境終於逐步變成 Java 8 + GWT 2.8 + GXT 4.0…

然後昨天手賤去升級 Eclipse,結果重啟不能、抱怨 Java 8 不給用、得換到 Java 11(印象中)以後。把 JAVA_HOME 切到 OpenJDK 14.0.1 之後,換 GWT 罷工,理由是 GWT 2.8 只能用 Java 8。為了都使用同一個 JDK 的 無聊 理由,所以 GWT 就乾脆也升到 2.8.2,也就是 Java 14 + GWT 2.8.2 + GXT 4.0。

前面是前情提要,接下來終於進入到主角了:RQC

RQC (14e5fb7) 還停留在 Java 7 + GWT 2.7 + GXT 3.1 的狀態,而且那時 GoogleSheetToolkit 也還沒抽出去。所以得先處理開發環境升級…

然後就爆炸了… <囧>

目前確認的一個詭異炸點(也許還有其他的 😱 )是 SheetIdGrid.javagenColumnModel(),只要用到 GetValueProvider 就會在 attach 的時候炸 NPE(實際上是炸 AttachDetachException)。關鍵點是 GetValueProvider.getPath() 預設回傳 null,只要不用 GetValueProvider 或是 override getPath() 隨便回傳個空字串都行。

帳面上看起來似乎對症下藥了,但其實整件事很謎:

  • GetValueProvider 已經用 N 年、有 N 個 Grid 在用這玩意
  • 在另外一個 project(LATE)完全無法重現這個問題。
    • 這兩個 project 的差別就只有 RQC 原本是 Java 7 + GWT 2.7 + GXT 3.1,而 LATE 原本是 Java 8 + GWT 2.8 + GXT 4.0,其餘 pom.xml / gwt.xml 都(幾乎)一樣。

目前實在沒什麼想法 (X你X的,都無法重現了還能怎樣) ,只能就先留個紀錄備考。當然改變 GetValueProvider.getPath() 的回傳值是一個萬用解,不過那等到其他 project 也炸了再來考慮吧… 😭

2020-04-23

GoDaddy 無厘頭請款事件

我沒有作電話錄音,以現在的標準來說可能算空口無憑。只能說我會對以下的陳述負所有的法律責任。

TL;DR

  • 在沒有登入的情況下,GoDaddy 莫名產生購物內容並嘗試請款(因為信用卡驗證碼錯誤而請款失敗),GoDaddy 目前無法解釋這個狀況。
  • 若在 GoDaddy 綁定的信用卡,最好開啟刷卡消費通知,以防上述事件發生並請款成功。

事發經過

我在 GoDaddy 有購買兩個 domain,有開啟綁定信用卡自動續訂,上一次自動扣款日是 2019 年 8 月,信用卡應該都是同一張沒有因為過期換過。

2020/4/23 19:3x,手機收到信用卡銀行的兩封簡訊,有一筆 1923 元的請款授權失敗。

20:0x,看到手機簡訊,打電話到信用卡銀行,銀行方說有五次授權失敗的紀錄,請款方是 GoDaddy。

20:3x 上 GoDaddy 的網站查看訂單紀錄,依然是兩個 domain。但是發現購物車內有五筆預計購買的 domain(都是 c 開頭的 .com),估算金額是 18xx 元。

打電話到 GoDaddy 台北客服(02-77039087),開始了漫長的對話與等待… 概要如下:

  • 我最近的登入紀錄依序是 2020/4/23 20:3x 兩筆、再往前就是 2019 年年底
  • GoDaddy 有信用卡嘗試請款失敗的紀錄,但是客服說只有紀錄日期,沒有紀錄時間(喵的,誰相信系統會這樣設計),但是他們願意相信我所提供授權失敗時間。

綜合以上兩點,GoDaddy 無法解釋是誰在進行購物結帳的動作,因為請款時間是 19:3x,在請款時間之前的登入紀錄得回溯到 2019 年底。

然後,**GoDaddy 甚至也無法解釋為什麼會請款失敗。**按照信用卡銀行的說法,都是驗證碼錯誤所以授權失敗。但是按照 GoDaddy 客服人員的說法,因為有設定續訂、有給信用卡資訊(含驗證碼),理論上購物車在結帳請款時會沿用、也就是說不會再要求輸入一次驗證碼,所以理論上會請款成功才對… (至於 GoDaddy 操作流程上是不是真的不需要再次輸入驗證碼,我就沒有自己嘗試了)

無法總結的總結

GoDaddy 承認我所回報的狀況,但是他們目前無法提出任何解釋。唯一能給的建議就是要我換掉各種密碼、加上二階段認證(如果真的是密碼被 try 那事情還簡單一點,問題就不是帳號被駭阿)。客服只能承諾說查明真相會發個 email 告訴我,至於需要多久就不會保證;如果後續我要追問進度,只要告知客服人員我的客戶編號、以及我在哪個時間打過電話就可以。

所以呢… 我覺得還是趕快把信用卡的請款簡訊通知打開,然後換到 Google Domains 比較實在…

附錄

簡單 Google 了一下,發現近期也有人( https://noter.tw/ )遇到類似案例,一併 memo 於此:

2020-02-17

Service Worker 之存貓得狗

最近突然開始在測 Service Worker,理所當然拿教學文件上的那個「第一次看到狗、第二次以後看到貓」的範例自己跑跑看。

為了後面的劇情需要,這裡先講解一下這個範例在幹啥。它是在示範 Service Worker 攔截 request 的能力,程式刻意設計成在第二次(以後)瀏覽,明明網頁要顯示的依然是 dog.svg 這個 resource,但實際上 Service Worker 給的卻是 cat.svg,所以使用者看到的會是貓。

好的,我複製貼上了程式碼、直接抓範例網站的貓狗圖…

為什麼跑出來的完全都是狗狗狗狗狗…

原本還要測一些其他的東西,所以是自己開個 localhost server 試,理所當然沒有 https(我也不會掛… [死]),為了預防萬一所以還丟上 GitHub Pages 試試,一樣還是狗狗狗狗狗… (備註:其實與 https 無關,都可以運作)

◢▆▅▄▃ 崩╰(〒皿〒)╯潰 ▃▄▅▆◣

鬼打牆到已經放棄治療,正準備關機睡覺的時候,無意間發現… 幹,為什麼兩個圖檔開出來都是狗?那不管程式有沒有 bug、Service Worker 有沒有運作都馬一定看到狗阿… [死]

原本以為是自己手賤粗心存檔存錯,打算再抓一次圖檔測試看看,如果成功就自己去跪主機板,結果沒想到…

只要是在範例網站上用「另存圖片」的方式,就會遇到「存貓得狗」的結果

這邏輯感覺很詭異,不過仔細想想可能還是有理可循?以「在新分頁開啟圖片」的結果來說,就會看到 url 依然是 dog.svg 但畫面還是貓,這表示 Service Worker 在不同 tab 一樣會運作。至於「另存圖檔」、「複製圖片」這兩個行為應該是單純拿 url 作 curl 之類的動作,不會觸發 Service Worker,所以依然會得到真正的 dog.svg

(謎之聲:事後諸葛都碼很簡單)

於是我的「範例程式跑不動」歷史又增添了一筆。雖然說 browser 行為的確出乎意料,但是在鬼打牆的時候(明明都有把 catches.match('cat.svg') 印出來看,url 的確是 cat.svg)也沒懷疑到圖檔上頭還是缺失。 於是寫完這篇檢討報告之後來跪個滑鼠墊以作懲處

2020-02-09

Maven Central Repo 強制使用 HTTPS

今天炸了 project 突然無法 build 的問題,mvn 跳了三行 download pom 檔之後就宣告 fail。拿 URL 到 browser 上頭確認是不是 server 掛了,結果得到一個 501,然後給了一句:「More information at https://links.sonatype.com/central/501-https-required 」。

簡單地說,就是從 2020.01.15 開始,兩大預設 central repo 都強制要求要用 https 連線,不然就賞你 501。

好,知道是知道了,但是不知道要去哪裡改,至少 conf/settings.xml 看不出所以然、我也不知道別招了… (艸

狗了一下發現(還)沒啥災情,想到我還是在用 n 年前的 3.2.1 版,於是抓了最新的 3.6.3 版,四海昇平。

幹,那就這樣吧… [逃]