文章列表

Spring Boot 04 - 進入http json api 世代

科技新知
MacauYeah・2024-05-23

本節,我們將會建立一個http服務,提供json api讓程式訪問。 下戴模版 我們跟上節一樣,使用Spring Initializr (Maven) 下載模版,但細節筆者就不再講啦。Dependency主要選擇 Spring Web Spring Boot DevTools 下載後,可以直接運行測試,可以用指令 mvn test 或經IDE運行。Spring會至少測試下能不能成功取用預設的8080端口。 Controller 我們若要實作 http json api,需要在 spring 中加入一個類,附註為 @RestController ,那方便起見,類名我們也命名為 XXXController 吧。作為示範,我們弄一個 HomeController.java ,裏面有最常見的 http GET, POST功能。 // src/main/java/io/github/macauyeah/springboot/tutorial/springbootwebapibasic/controller/HomeController.java import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; // ... other import @RestController @RequestMapping("/api") public class HomeController { @GetMapping("/someRecord/{uuid}") public Map readSomeRecord(@PathVariable String uuid) { return Map.of("ret", "your uuid:" + uuid); } @PostMapping("/someRecord") public Map createSomeRecord(@RequestBody Map requestBody) { HashMap ret = new HashMap(requestBody); ret.put("ret", "got your request"); return ret; } } HomeController裏,完整的URL 其實為: GET http://localhost:8080/api/someRecord/{uuid} POST http://localhost:8080/api/someRecord URL中的api之後的路徑,都是定義在 HomeController 中,而前半的8080及context path,是使用預設值。在正式環境下,可能隨時會被重新定義。但我們做本地測試,只需要驗證預設值就可以了。 我們真的運行起程式mvn clean compile spring-boot:run,再使用最簡測試工具進行測試。Windows的朋友,可以選擇Postman作為測試,它有圖形介面。而linux的朋友,請用curl,預設安裝都會有。下列為方便表示測試參數,筆者選用curl。 測試GET,其中1234會自動對應到spring裏的uuid。 curl http://localhost:8080/api/someRecord/1234 # return {"ret":"your uuid:1234"} 測試 POST,其中的 -d 參數,會對應 spring裏的 @RequestBody, -H 參數則是設定 http header 的意思,我們就使用約定俗成的 json 作為 header 。 curl -X POST http://localhost:8080/api/someRecord -H "Content-Type: application/json" -d '{"requst":"did you get it?"}' # return {"requst":"did you get it?","ret":"got your request"} 上面的兩個操作,都回傳了我們輸入的資訊,這代表了我們成功用spring架起了http json api,而且正常讀入資訊。 Test Case 雖然我們可以正常地架起 api,但每次開發都要 postman / curl這種工具額外試一次,其實也有一些成本。而且 api 數量變大,或經多次修改後,就重複人手執行,就變得相當討厭。 面對這個問題,筆者會建議寫測試用例,即是Test Case,而且用Spring內置的@SpringBootTest來寫。 產生一個空的Test類,vscode中,最簡單可以Source Action => Generate Test,然後加入這次要測試的參數。 // src/test/java/io/github/macauyeah/springboot/tutorial/springbootwebapibasic/controller/HomeControllerTest.java import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @SpringBootTest @AutoConfigureMockMvc public class HomeControllerTest { @Autowired private MockMvc mockMvc; @Test void testGetSomeRecord() throws Exception { RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/api/someRecord/1234") .contentType(MediaType.APPLICATION_JSON); this.mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.jsonPath("$.ret").value("your uuid:1234")) .andDo(MockMvcResultHandlers.print()); } @Test void testPostSomeRecord() throws Exception { String request = """ {"requst":"did you get it?"} """; RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/api/someRecord") .contentType(MediaType.APPLICATION_JSON) .content(request); this.mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.jsonPath("$.requst").value("did you get it?")) .andExpect(MockMvcResultMatchers.jsonPath("$.ret").value("got your request")) .andDo(MockMvcResultHandlers.print()); } } 最後就是執行 mvn test 或經IDE運行,應該都會得到所有測試都通過的結果。 mvn test # other test result ... [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.368 s -- in io.github.macauyeah.springboot.tutorial.springbootwebapibasic.controller.HomeControllerTest # other test result ... 上面的程式碼很多,我們逐一來。 @SpringBootTest 寫在類的外面,代表執行這個測試類時,需要運行起整個Spring程序,當然也包括http的部份。 @AutoConfigureMockMvc 寫在類的外面,代表執行這個測試類時,可以模擬一些發向自己的 http 請求。 @Autowired private MockMvc mockMvc 寫在類的裏面,因為之前有定義了可以模擬 http 的請求,Spring在運行時為大家提供了那個所謂的模擬http client的實例。 MockMvcRequestBuilders,則是建造要測試的URL及Header參數。 MockMvcResultMatchers,則是檢查回傳的結果是否如遇期的一樣。 為何這個http client叫模擬 - Mock ? 因為在測試用例中,可能連Controller 內部依賴組件也需要進一步模擬,這樣才能把測試目標集中在Controller裏,這也是單元測試的原意。只是本次的例子看不出模擬與否的差別。 MockMvcResultMatchers.jsonPath(),這是用來檢測json的結構是否跟預期一樣。有些網路上的其他例子會簡寫成 jsonPath() ,但因為vscode IDE的自動import功能比較差,筆者還是保留傳統的寫法。 如果大家覺得@SpringBootTest很難,想折衷地把其他測試方法,那麼把 postman / curl好好管理起來,每次修改完程式,都完整地執行一次 postman / curl ,也可以達到測試的效果。只不過大家還是要好好學會整合 postman / curl,知道如何檢測json結構,什麼時候有錯,什麼時候叫測試通過,所以也要花一樣功夫來實現。 最後,大家千萬不要因為測試難寫而逃課,因為寫測試絕對地可以減輕日後重執行的工作量。除非你的程式碼即用即棄,否則都建議寫測試。(測試跟寫文檔不一樣,有了測試也不能沒有文檔。好消息的是,文檔現在越來越多自動生成的工具,我們日後再找機會介紹。) Source Code spring boot web api basic

Ubuntu 24.04 試用報告-更新

科技新知
MacauYeah・2024-05-21

上期為大家介紹了一些ubuntu docker, multipass的一些改動。本期再繼續介紹一些其他的更新。 apt中的source.list 的位置更新了,格式也更新了,從/etc/apt/sources.list在指向了/etc/apt/sources.list.d/ubuntu.sources,格式變得更親民,就像如下所示 Types: deb URIs: http://mo.archive.ubuntu.com/ubuntu/ Suites: noble noble-updates noble-backports Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg Types: deb URIs: http://security.ubuntu.com/ubuntu/ Suites: noble-security Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg 承上更新,雖然格式好看了,noble-security的部份卻故意折開了。而且在live-cd初次安裝時,大家若要改mirror(鏡像站點),只能修改noble noble-updates noble-backports的位置,noble-security還是會指定在官方的位置。筆者猜測它的用意是針對安全性更新,大家應該要直接訪問官方網站,不要等mirror慢慢更新。此一更新,不單影響ubuntu 24.04,連ubuntu 22.04也受一併折開了,只是22.04還是使用舊版。如果有需要變回統一的方向,減少日後自動化的修改,可以像以下修改 ubuntu 24.04的修改 Types: deb URIs: http://mo.archive.ubuntu.com/ubuntu/ Suites: noble noble-updates noble-backports noble-security Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg ubuntu 22.04.04的修改,刪除/etc/apt/sources.list,新增/etc/apt/sources.list.d/ubuntu.sources (對齊ubuntu24的位置) Types: deb URIs: http://mo.archive.ubuntu.com/ubuntu/ Suites: jammy jammy-updates jammy -backports jammy-security Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

軟件發行也需要維修基金?

手機‧電玩
MacauYeah・2024-05-13

筆者參與軟件開發,都己經有好一定年期。面對軟件開發週期,最痛苦的並不是研發階段。好多打機的朋友,可能會以為軟件應該跟遊戲差不多吧,開發完就頂多修BUG,然後全心地投入下一個項目的開發。要持續花時間更新?不可能,微軟不也是幾年要求重買一次新版的Office套裝嗎?幾年也要另外花錢升級OS。概然全部都要另外花錢買,不就是一個全新的項目嗎? 其實除了微軟這種夠大夠惡的龍頭公司外,其他都不是這樣運作的。例如我們現在很常用的手機OS,不論Android, iOS,其實只要硬件支緩,就不需要用戶成本就可以升級的。其內的App應用,也因為手機OS的升級,也要持續升級。所以不論你是哪一層的開發者,好大機會都要一直維護已發佈的軟件版本,好讓它可以在不同環境下運作。而這個維護成本,就看你低層的供應商有多進取、有多佛心。現在基本免費的供應商都會大刀闊斧地改功能。大家要留意,是改功能,不是加功能。也就是有些功能過去有,現在使用模式整個有改變,你不得不重寫自己的軟件。 所以筆者現在最頭痛的是,如何為公司維護這些沒法帶來新收入,而又要不斷支付時間和金錢的訂制軟件。 技術上,一定有很多討論,但在於只關心行政的老闆的角度下,根本聽不懂。在於開發者的角度,也需要很長期的實務經驗才能有好一點的佈署。扣除技術,在本質上,若然各利害關系人都曾經考慮過,大家應該都會有更好的預期。 軟件有生命週期,而且這個重複得越來越快。由開發到發佈穩定版本的時間、人力、金錢最高。因為環境變遷,重回開發的機會越來越多,不斷地重複。 需求狠心地下架過氣軟件。過氣軟件,要麼更新,要麼淘汰。但不是所有軟件都受歡迎,值得投放時間。這個在老闆視角下,他很懂。但老闆通常做不了的是,狠心放棄升級不了的軟件。老闆經常覺得,只要軟件放著不更新,就不會有成本。錯,因為老闆只會記得倉庫中曾經有一個軟件可以做到某個功能,可以給賣給某個客戶。但當你拿出來時,才發現不能直接用,還是很焦急地找人更新。 軟件開發,跟很多其他類型工程很像。不是隨時看看圖表,就可以回憶前世今生。舊軟件要救,要花時間先摸索當初的開發工具、環境,追查問題原因,或許最後可只改一句指令就解問題,但總體成本會令人無法接受。 軟件的可複制性不如以前。很多老闆會認為,你之前開發過一次,抄過來做點少改動,不就可以當一款新的應用嗎?因為原來軟件沒有維護,大部份過氣的軟件,即使你有原始碼,你也未必能找到適合的編譯環境來做改動。想要改動?還是老老實實做先更新。 所以,大家對於軟件維護,應該要像物業管理一樣,要預留一部份費用為維修基金。可能還是有老闆會講,怎麼可以預留到這麼多錢去做維修?所以,筆者更加建議,不是要做一個完美的萬能軟件,要鎖定核心功能。沒化更新的,就放棄、止蝕。

測試驅動開發 | 系統邊界Mock

科技新知
MacauYeah・2024-04-23

好一段日子之前,筆者就介紹了一些寫Test Case的大方向 。對於大部份情況來說,有分隔的開發環境,有整個配套,測試起來是順暢的,想做單元測試可以,做整合測試也可以。但如果沒有,我們其實也要想辦法寫Mock。 Mock這個概念,對於寫前端程式的朋友應該比較熟悉,因為前端開發者總不能等後端準備好之後,才開始慢慢設計。前端很早期就要模擬一些情況,做介面設計,做各種思考。而且這個Mock不是指在運行單元測試時,才使用的臨時修改隨機數據。而是針對開發時,自行模擬的後端或外部環境。不過因為前端介面涉及很多主觀設計,很多元素冇辦法做固定的自動測試,所以前端的測試通常要人幫測試。 而後端開發,邊界Mock這一概念也很有用。在外部環境不足的情況下,為自己系統的邊界部份自建一個Stub / Dummy 等的模疑數據,是很有幫助的。不論我們對外部環境的掌控度有多少,我們走測試驅動開發(Test Driven Development),好好地定義這個外部環境的期待行為是很重要的。 例如,你有個功能,需要存入數據,但資料庫未準備好,也沒有所謂的In Memory資料庫可以用。這時,自己架空寫一個什麼都不做或回傳固定結果的函數作為中轉接口,然後在你的Test Case可以規劃你的想要結果。 也許你會說,這個函數就是存下資料,我不會需要它的回傳結果,但我們其實還是可以在Test case 中定義一些錯誤檢測,確保這個函數沒有Throw Exception 。再進一步想,我們主程式是否真的不負任何儲存失敗的責任?要定義其他回傳變數,方便寫Log讓追蹤?或者我們至少要知道成功後的Primary Key ?若然業務上真的不在乎儲存結果的有效性,我們不存入數據也是可以的? 所以歸根究底,我們還是在乎儲存的成功與否。還是有必要去驗證驗寫入是否成功。 上述例子,因為資料庫不存在,開發途中可能Test Case 有好長一段時間也通過不了,但至少當資料庫完備後,可以直接驗證,不用人手手工測試。 舉另外一個例子,我們要從某個地方,例如API或資料庫,讀取數據。我們也可以先寫中轉接口,並為它寫Test Case定義應有的行為。雖然明明就只是讀取,我們沒法控制太多。但在接口做好異常狀態處理,是很重要的。例如Handle exception、檢查某些重要業務值會不會是空、確保後續部份可以正常使用,這是因為我們不能被外部系統的失誤而導致自身系統癱瘓。 其實測試驅動,本質上就是強逼大家想多一點,好好定義預期的行為,不論內部條件怎樣變化,都有一自動的檢收標準。

Spring Boot 03 - 做好Database的模組化及測試用例

科技新知
MacauYeah・2024-04-12

這節,我們將會使用spring-data-jpa,寫一個業務上的資料庫模組,提供資料表的存取,讓你的好同僚可以直接使用。這樣可以在多模組的環境中,減少同一個資料表在不同地方重複又重複地重定義。將來要更新,也可以使用jar檔的方式發佈。 下戴模版 我們跟上節一樣,使用Spring Initializr (Maven) 下載模版,但細節筆者就不再講啦。Dependency主要選擇 H2 Database Spring Data JPA 對pom.xml作一些微調,並把spring-boot-start-data-jpa,h2改為只在測試中生效。 並把Java檔案搬一搬位置 # old location src/main/java/io/github/macauyeah/springboot/tutorial/springbootdatatest/SpringBootDataTestApplication.java src/main/resources/application.properties # new location src/test/java/io/github/macauyeah/springboot/tutorial/springbootdatatest/SpringBootDataTestApplication.java src/test/resources/application.properties 以上的操作,主要是因為我們的目標是提供Schema,或者叫資料表規格。其他用於做連線的操作,我們不需要打包在jar內。所以把那些次要的東西都放在test資料夾中。我們這時可以先用mvn test指令,確保一切功能還是正常。 Entity folder 然後我們入正題,在pom.xml中加入hibernate-core,spring-data-jpa, 然後在main資料夾下加入 Entity、Repository,例如前述用過的Apple和AppleRepo,最後資料夾就像是這樣。 . |-- pom.xml |-- src | |-- main | | `-- java | | `-- io | | `-- github | | `-- macauyeah | | `-- springboot | | `-- tutorial | | `-- springbootdatatest | | |-- Apple.java | | `-- AppleRepo.java | `-- test | |-- java | | `-- io | | `-- github | | `-- macauyeah | | `-- springboot | | `-- tutorial | | `-- springbootdatatest | | |-- SpringBootDataTestApplication.java | | `-- SpringBootDataTestApplicationTests.java | `-- resources | `-- application.properties 然後我們在Test Case中使用AppleRepo @SpringBootTest class SpringBootDataTestApplicationTests { @Autowired AppleRepo appleRepo; @Test void contextLoads() { Apple apple = new Apple(); apple.setUuid(UUID.randomUUID().toString()); apple.setWeight(100.0); apple.setGravity(1000.0); appleRepo.save(apple); } } 這個跟前述02-spring-data-jpa最大的差別,就是我們的main中只有Entity相關的Class,我們發佈jar,別人引用我們的class,別人不會解發其他不相干的商業邏輯。假如發佈02的例子,因為Spring有自動初始化Component的原因,很可能會誤觸發02中的BasicApplicationRunner.java Source Code spring boot data test

Git Worktree

科技新知
MacauYeah・2024-04-09

看了Git 大神的影片 part two,才知道原來切換git分支還是有不同的做法。傳統中,我們使用git checkout BRANCH_NAME_1 來切換到我們想要的分支。通常這樣做,代表我們放棄原來的工作環境,換到另一個工作環境中。 這樣做很好,對不對? 是的。但有些時候,我們只是被逼離開原本的工作環境,跳到一個過去的分支/節點去查一些東西,或者修正一些東西。更什的是我們原本的工作環境都還是混亂狀態下,我們不想做commit(提交),我們只好用git stash,暫時將工作環境存起,然後再git checkout BRANCH_NAME_1。在你想做的事做完後,再git checkout OLD_BRANCH。 看起來其實也沒有很麻煩,是不是? 但其實當你的專案有一定大小,你在不同版本跳來跳去,你的IDE就會不斷地重新編譯。更不幸的是,當你的不同版本中有模組數量的差異,弱一點的IDE,什至會攪死它的cache,之後就會發生鬼打牆。為解決IDE引發的問題,筆者有時會直接cp -r YOUR_PROJECT TEMP_PROJECT,在一個新資料夾下另起爐灶。那就是有兩個不同的資料夾裝載著你的專案。 這樣應該沒有問題了吧,是不是?這次是真的可以了,扣除了筆者個人健忘的問題,就沒什麼問題了。 不知大家有沒有經驗,連續commit了幾次,但最後一次commit卻忘了push(與伺服器同步),然後就跳到其他地方繼續工作。如果我們在同一個git repository下,我們commit了但忘了push,即使我們git checkout去了其他分支,用git GUI畫出commit graph時,也至少可以提醒筆者有一個未與伺服器同步的分支。但如果當初我們用的是cp,那就沒戲唱了,什至乎當初複制了去哪裏都忘了。(當你老闆同時要你跟多個專案,健忘真的很容易發生。) 這問題有解嗎?有的,git在2.5版本以後,就提供了一個git worktree的指令。它有點像cp 指令,更重要的是,它打通了兩個資料夾下的隱藏資料庫.git,當大家在那兩個資料夾底下,都可以看到另一方的存在。大家可以用git branch -a或git log --oneline --graph來看看。 詳細的指令介紹:git worktree git 大神的影片 Part 2

Git: 何謂MONO Repository

科技新知
MacauYeah・2024-04-02

之前看了一位git大神的演講,提及一個叫MONO Repository的使用情況。後期找資料之後,才發現到這是一個公司成長後的一個重大的挑戰。 何謂MONO Repository git的傳統,就是為每一個獨立的專案,建立一個新的Repository (中譯:倉庫)。這個很直觀,獨立專案,獨立管理。從零開始有很多好處,Repo體積通常會小一點,因為其內的東西都是緊密相關。做更新處理時,維護人員也更清楚自己的影響程度。這種架構方式,就叫Multi Repository。基本上,大家預設也是會走這個模式。 但當公司規模一直變大,多個專案可能不再獨立,各個專案或多或少都有一些關聯性。當任一專案更新,都有機會影響到其他人。如果公司使用Micro Service (微服務),就更有機會提早遇到。每次更新時,要跨專案地找出影響範圍原本就已經不容易,現在每個專案獨立地存放在不同的倉庫中,每個倉庫的更新速度不一樣,想要找到合適的地方、合適的時間點推出更新,更是困難。 所以,就有公司就提出,將所有專案都放在同一個Mono Repository中,方便用工具去檢查更新影響。相比Multi Repository,這樣做還可以保證同一個改動可以發生中同一個Commit中,可以讓跨專案的團隊可以即時合作(強逼修改別人的專案)。但這樣使一定會有很技術問題出現。跨專案團隊不可能每個專案都熟悉,因為不熟悉而引起的副作用一定會有,所以Main / Master分支出現有缺陷的機會提高了。亦有人提出,使用Mono架構,還必要使用trunk base分支模式。也就是那些新功能,雖然要創建分支開發,但亦要盡早整合到Main / Master中。這才能讓不同的團隊盡早知道問題,並解決問題。 除了開發模式更具挑戰外,Mono架構對git的效能也有很大影響。因為多專案混合,Repository的大小基本都會很大。每個git指令都會變慢,所以必需做一些週期性的cache,讓git graph, git status這樣日常操作變得暢順。同樣地,持續整合/發佈需要作出調整。不過這些筆者就不在這邊詳述了,有興趣朋友可以到git 大神的Youtube觀看。 So You Think You Know Git - FOSDEM 2024 註:據筆者的資料搜集,很多大公司(Software龍頭)都有使用Mono Repository去做集中管理。只不過筆者不知道如何Fact check,就不在這裏提了。

Docker環境參數化 - Arg VS Env

科技新知
MacauYeah・2024-03-26

Docker Variable control 我們在Docker Image的打包時,最簡單當然就是每個步驟都使用最新版本。例如Docker Base Image,大家可能選用latest tag,安裝linux package (Linux包),也可能就apt install / yum 安裝最新的穩定版本。但如果我們想要更好地做測試,就要使用指定版本,方便追蹤問題。而Docker在打包和運行時,都有不同的方式讓大家定義或覆寫指定參數。 Docker build arg 我們先從打包Image開始。 例如我們需要使用一個Base image為 ubuntu,版本預設為22.04,但有需要時可以經build指令覆寫,可以這樣寫 ARG ubuntu_version=22.04 FROM ubuntu:${ubuntu_version} # default ubuntu_version=22.04 docker image build -t test2204 ./ # or overwrite by --build-arg docker image build -t test2404 --build-arg="ubuntu_version=24.04" 雖然Dockerfile的RUN指令都是使用linux shell,但在Dockerfile中想表達條件控制(if else statment)就不太易看。在外部加入script做控制,是另一個可行的後備選擇,它更可以連image名字也進行參數化。 # in bash script, you also can if [ $beta == true ] then ubuntu_version=24.04 else ubuntu_version=22.04 fi docker image build -t test:${ubuntu_version} --build-arg ubuntu_version=${ubuntu_version} Docker Container Run and Docker Compose 一般來講,Linux Container 在執行時,就等於進入Linux Shell。也就是,我們可以使用Shell中的環境變數。 我們在打包Image前,已經可以在Dockerfile中定義自己的ENV數參(也就是環境變數)。與前面的Build Arg有所不同的是,ENV是定義在Dockerfile中,在Container運行時以環境變數的形式存在,它也可以在運行中被改變。而Arg,則只在打包Image時存在,運行期間就不存在了。(當然,你在打包時,用Arg傳入Env,以運到這個目的。) 另一個更特別的性質是,那怕ENV沒有定義在Dockerfile中,我們運行時也可以加入更多的環境變數,大家就當成是一般Linux操作,隨時在自己的shell中加入變數。 # -e, --env for inline variable # --env-file for file docker container run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash 同樣地Docker compose,也支援環境變數。筆者建議environment可以使用Array格式,日後可以更方便地直接改為env_file。 # docker-compose.yaml services: ubuntu: image: ubuntu:22.04 environment: - RACK_ENV=development - SHOW=true - USER_INPUT 上述的寫法沒有任何問題,不過如果你的docker-compose.yaml是放在git等版本控制中,你更新環境變數就有可能會影響到其他人,這時你就會想轉成env_file。 docker-compose.yaml預設就會讀當前資料夾的.env,就算不存在,也可以正常運行。(當然,大家的Image/Container應該要有預設值) # docker-compose.yaml services: ubuntu: image: ubuntu:22.04 # if env_file is not defined, it will load .env. # or you can load the specific file. # env_file: # - ./a.env env_file內,每一行就是一個變數 # .env or a.env RACK_ENV=development SHOW=true USER_INPUT 使用預設的.env還有一個好處,就是我們可以把docker-compose.yaml也變成受環境變數控制。 # docker-compose.yaml with variable control, only works in default .env services: ubuntu: image: ubuntu:${ubuntu_version} # .env ubuntu_version=22.04

Git - 持續整合策略 02 | Git - Continuous integration strategy 02

科技新知
MacauYeah・2024-03-14

Night Build 實務操作上的注意點 Night build第一個要注意的問題,就是要確保同一個commit,真的可以重複建設。一般來說,大家的目標只在運行測試,而自動測試不具破壞性,就基本可以重複的。而如果測試當中包含發佈測試版本,那就還要考慮重複發佈有沒有生效或造成附作用。 以Java maven為例,重複發佈測試版本需要遵守特定的規則,版本號需要以SNAPSHOT結尾,這是為讓maven每天都會重新下載它們的包。而沒有SNAPSHOT結尾的,就只會做一次性下載,減少重複下載造成的資源浪費。若真遇著不支援重複發佈的情況,就需要以日期時間做版本號,就像vscode的某些插件,就是以時間截結尾以作為區分。 Night build另一個要注意的問題,就是開發圖隊何時進行下一輪開發,這會決定何時有新的版本號。扣除上述因為工具不支援的而引發的副作用,還要考慮沒有更新而發生的問題。 有個尷尬情況是,團隊在發佈現行版本時,release commit與main有機會是同一個commit(也就是未有進行下一輪開發)。若不斷重複發佈,有沒有變相發佈了一些沒有預期的功能?例如Docker image,官方大力建議每日自動發佈。當底層的image更新後,頂層引用它們的image,也可以重新發佈,保持安全性。但這樣做的問題,就是頂層的同一個版本號,昨日與今日的運行結果也可能不一樣。這對追蹤問題,並不友好。 所以大家做分支整合時,要預先對版本號作好規劃。然後還要留意Night build不應與release commit重疊。版本號大家做好語意管理,再加上alpha / beta / SNAPSHOT等區分Night build版本,應該就足夠了。而commit重疊問題,就要留意開發週期,Night build要麼就比release早一個commit(即在release時,不推進Night build),要麼晚一個commit(即馬上規劃下一個版本號進行Night build)。

星穹鐡道:抽角色、組隊簡介

手機‧電玩
MacauYeah・2024-03-13

因為抽卡機率問題,坊間很多建議都基於課金的前題,不是所以有人都可以重複。但這亦不是筆者體驗這遊戲的主要方向,所以筆者集中分享一些主線必定會取得的角色,或盡量以4星的方式組隊。但在說明組隊之前,先講一講基本系統。讓大家知道那些地方有課金機率成份。 基本出戰 本遊戲是團隊戰,最多同時4名角色上場,同一角色不會出現兩次。影響出戰強度的,除了角色本身屬性、技能,還有裝備要求。同一角色可以裝備一款【光錐】,六款【遺器】,達到不同的Buff。 每個角色獨有自己的【命途】技能養成,光錐及遺器則可以交換使用。 在攻略副本時,可以借好友的角色,但依然會限制同一角色不能重疊。而好友的光錐及遺器不能交換。 卡池 在遊戲中有角色【躍遷】,就是抽角色的地方。除主角外,其餘角色都可以經【躍遷】以機率的方式抽取。若抽到重複角色,會轉化為【星魂】,用作提升角色的特殊技能。主角的星魂以遊戲進度獎勵發佈,其他角色暫時都以抽取為主要來源。特殊角色,如【黑塔】有特別支線任務可以取得。 光錐同時在【躍遷】中取得。 遺器則是副本敵人隨機掉落,沒法經抽選擇。而角色命途技能、光錐、遺器養成部份,所需資源都可在遊戲主線或副本取得。所以有限抽取的,就是角色和光錐。 希有度 角色稀有度最低為4星,最高為5星,道具則為有3-5星。 卡池沒有4星角色保底,只有4星結果保底,每十連抽可以得到4星角色或道具。 組隊目標 - 4星非洲隊 主線故事中,一定可以獲得 主角(物理,攻)或(第一章最後獲得,火,盾) 三月七(冰,盾) 艾絲妲(火,輔助Buff) 丹恆(風,單體攻擊) 娜塔莎(第一章中後期獲得,物理,奶) 黑塔(支線模擬宇宙獲得,冰,群體攻擊) 艾絲妲原本筆者也以為是抽角隨機獲得的,但以BiliBili Wiki引證,其實是抽角教學中必定獲得的角色,所以道理上各位也一定會有。但有些版本有活動送角色,但似乎地區不一樣有不一致情況,故筆者沒有列出。 三月七 雖然在取得奶之前,主角(物理)、三月七、丹恒、艾絲妲,就是沒有選擇之下的選擇。但其實三月七的盾有隱藏技能,會增加受擊機率,這是變相指定角色吸仇恨換賺能量的做法。她也是在缺奶時最重要假回血手段,所以是一個有長期培養的角色,用來湊雙冰、雙盾或一奶一盾也不錯。 艾絲妲 筆者一直忽略了的一位重要角色,因為她施放攻擊就有機會蓄能,蓄能全體加攻。終結技有全體加速效能,普偏的裝備方向是為她加速加能量,讓她可以再為其他人加速加攻。也因為第一章後,與主角可以組成雙火隊,主角可以全隊加盾,艾絲妲加速,打火弱點敵人的話,一定不虧。 娜塔莎 奶,就不用說明了。另外,她也作雙物理的組成。也是筆者作為平常無腦開荒的組成,主角(火,盾) 加 三月七 加 娜塔莎 加 弱點輸出。效率可能不高,但勝在無腦。 無腦隊最大的問題是弱點擊破率很低,因為盾和奶都需要經常回復,少了輸出的機會成本。

Spring Boot 02 - 快速接入Database的選擇: Spring Data JPA

科技新知
MacauYeah・2024-03-08

快速下戴模版 使用Spring initializr,可以很容易就建立一個以Spring boot starter為底的java project。大家可以使用Spring 官網又或是vscode plugin 快速地建立一個maven或gradle project。筆者較為熟悉maven,就以maven起一個範例。 在使用Spring initializr有幾件事必需要指定的: Spring boot version: 3.x.y 或以上 Language: java Group Id: 請選擇有意思的域名,如果你用github,可以選 io.github.yourusername artifactId: 這個範例的名字,例如commandline Packaging type: 本次使用jar,日後若開發web 應用,可以使用war Java version: 17或以上 Dependency: Spring Data JPA, Spring Boot DevTools 這次不像過去順利,因為這裏欠缺了Database連線資料,為了方便測試,我們先在pom.xml加入 h2與spring的整合很好。即使用什麼都不設定,直接運行mvn spring-boot:run,都可以成功執行了。但如果可以,在application.properties加入資料庫設定,會方便日後移植到其他常用的資料庫品版牌。 # src/main/resources/application.properties spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:testdb; spring.datasource.usename=random spring.datasource.password=random 然後我們就可以做靠Spring Data JPA去生資料庫的表 (table)。Spring Data JPA預設使用的是Hibernate。假設,我們有一個表叫APPLE。我們就可以開一個class Apple和一個interface AppleRepo去接它。 // src/main/java/io/github/macauyeah/spring/tutorial/springbootdatabasic/Apple.java @Entity public class Apple { @Id String uuid; Double weight; // getter setter } // src/main/java/io/github/macauyeah/spring/tutorial/springbootdatabasic/AppleRepo.java public interface AppleRepo extends JpaRepository{ // no content here } 注意,因為不同需要,AppleRepo可能繼承不同的XXXRepository,它們大部份都是用來觸發寫入資料庫的指令。而這個也晚除了直接存取Hibnerate EntityManager的需要。 亦因為我們現在用的是h2Database,其實資料表並不存在。我們需要在執行Spring Boot時,同步先建立表,所以在application.properties 加入自動建表的設定。 # src/main/resources/application.properties spring.jpa.generate-ddl=true spring.jpa.hibernate.ddl-auto=update 然後在Spring Boot Context的環境下,可以隨時執行寫入的操作。 @Autowired private AppleRepo appleRepo; public void saveApple() { Apple apple = new Apple(); apple.setUuid(UUID.randomUUID().toString()); apple.setWeight(100.0); appleRepo.save(apple); } Source Code spring boot data basic 因為h2Database只是用作測試用,所以spring-boot執行完,資料庫就會被刪除。而上述原始碼當中,還附上了一些dump sql的方法,至少可以讓大家驗證己儲存的結果。

如何衡量課金制遊戲的價值

手機‧電玩
MacauYeah・2024-03-05

筆者因為作息調整,可以花時間花資源去深玩的遊戲越來越少,需要專攻一款價值高的遊戲是一個很重要的課題。隨着年紀增長,家庭環境改變,大家都可能會遇到相同的問題。所以筆者很想探討一下,一年只玩一款遊戲的話,CP值是否有所保證?也就是滿意度和支出的比例是否保持一個高水平? 好多老一派玩家會支持傳統主機遊戲,主要係因為免費課金制,品質很差。初時下載遊戲免費,但遊戲無法通關,過程也很重複無趣,所以滿意度很差。正好筆者最近重回手遊,就來分享一下時間和滿意度比例。 本文為了方便討論單一手遊的價值,先只以「不課金」,只討論時間成本支出。日後再以「課金上限」來對比不同的課金情況或是與主機遊戲對比。 定義 成本:時間 CP值 ⇒ 淨滿意時長 / 淨成本 ⇒ 即成本越高,每單位成本的滿意度越低。 CP值 = sum (分段內容時長 * 分段滿意度) / (時間 ) 註:分段滿意度可能為負,為方便倍數計算,最大為10,最少為-10。 崩壞:星穹鐵道 - 主觀評分 主線序+ 第一章:20小時 * 8 = 160 五角色養成,累積前70等的升級素材:30小時 * 2 = 60 因為很多時候都是內卦刷戰鬥,不怎開心,但還未至於要吐 第二章:15小時 * 8 = 120 筆者有幸以不課金的陣型,以完成主線第二章。主角(火)、娜塔莎(物理)、希露瓦(雷)、景元(雷,劇情指定角色)。除了主角滿級其他都很素。 角色養成,累積70-80(封頂等級)的升級素材:30 * 1 = 30 70-80等級就開始跳躍性質變,借助外援也無法快速囤積資源。 筆者只有主角的等級+存護命途可以練滿,另一角色也只有等級練滿。其他連突破70級的資源都不夠。 模擬宇宙部份挑戰:5小時 * 8 = 40 忘卻之亭部份挑戰:1小時 * 2 = 2 有難度,但沒什麼樂趣 淨滿意度·時長 :160 + 60 + 120 + 30 + 40 + 2 = 412 淨成本:20 + 30 + 15 + 30 + 5 + 1 = 101 CP值 412/101 = 4.08 以上,就是星穹首年來的內容,對應六季的更新。目前遊戲新剛推出2.0更新,筆者也會花一點時間了解一下是否有等級門檻。 不過以長期遊玩的角度,還要考慮如何提升高等級刷素材的滿意度。

Git - 持續整合策略 | Git - Continuous integration strategy

科技新知
MacauYeah・2024-02-23

對於原始碼的管理,平常筆者也有在用gitlab的Continuous integration,針對每次提交(commit),都會有自動編譯和測試。但當一個專案中,有很多關聯庫(dependency library)的引用時,光是專案中每個commit 行auto build就不夠用了。更嚴重的是,若然大家有很多微服務micro service,它們的更新不會反映在commit中。 所以定期重跑動動編譯和測試,是筆者認為可以緩解關聯更新的問題,至少可以提高知道問題所在。 筆者先做了一些功課,參考別人怎樣思考Night build (定期重新編譯)這件事。 每次整合新功能到穩定分支(stable branch)之前,都需要做自動測試。 當專案複雜性越來越大,每次自動測試都把全部測試跑一次,就會遇到效能瓶頸。 所以考慮commit時做單元測試(unit test),然後每個固定的時間問隔做整合測試(integration test)。那個固定的時間間隔就是Night build。 而筆者的問題並不是來自於效能瓶頸,而是涉及關聯性更新問題。這些要麼就有是經code base 層面引發關聯性自動試測,要麼就是Night build重複測試。這兩個功能,gitlab都有提供,只是筆者初步構想下,Night build比較易設定。因為要考慮micro service的於沙盒環境的部署,最簡易的Night build只需要一個共用的環境就夠。但也同樣意味著,Night build需要進行多個不同的分支測試。就需要多個不同的環境。 Night build的測時時機也是一個問題,因為測試當下,並不能百份百對應關聯micro services的提交狀況,大家就更需要做好發佈的版本號語意管理。 不知道看完筆者的策略之後,大家又有何看法?歡迎大家一起加入git筆記的編輯。