潮流特區

最新文章

測試驅動開發 | 系統邊界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)。

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的方法,至少可以讓大家驗證己儲存的結果。

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筆記的編輯。

git 分支整合問題

科技新知
MacauYeah・2024-02-20

不知道大家的開發團隊、專案規模有多大,但只要系統或程式已發佈,同時又要做維護更新,git 庫都至少會有兩條分枝: 新功能 - main / feature 最新的穩定發佈版本 - Release / v1.x.x 最好的情況下,在開發完新功能之前,穩定版本都沒有需要緊急修正的地方,開發者可以專心開發新功能(main / feature)。然而這個情況並不能經常維持。 情況1:有Bug要馬上修正 最常見到的情況,就是穩定發佈版本有瑕疵,可以經過小修小改來止血,由v1.x.x ⇒ v1.x.y,這些可能對用戶來說,是沒有太大感覺的改動。不過對於開發流程,就免不了由v1.x.y整合(merge)回main時,出現修改衝突的問題。 建議 若屬於日後不再需要的改動,不需於整合到main中, 當然什麼都不用做。但若屬於必要的更新,就需要早早整合到main中。整合雖然痛苦,但延後整合沒有好處。以筆者的經驗,每次整合時有衝突,而越早整合越有條件知道該取用自動混合的那個版本。以整合工具的語言來說,就是更容易的作出use mine / use theirs / edit。 情況2: 不同功能之間有衝突 上述情況1,已經算是可控的。主要因為穩定發佈版本都只會接受小修小改,大改都會直接在main中開當為新功能開發。當你有多個很重要的功能在不同時期被提出,而有些功能你沒有信心在下個發佈中提出,你就會選擇以獨立分支來實現不同的功能,最後選擇信心度高、權重也比較高的功能來發佈。這樣的好處是你可以有限時間先完成最必要的功能,但問題是多個功能分支之間,更容易地有衝突,後期也需要很廢心力地整合。 建議 少做資料夾層面的改動,因為git rename的功能並不是萬能的,會令很多git自動選擇版變得不可讀。筆者的經驗,就是錯把後端和前端的資料夾混在一起,令後端的一些重命名影響到前端。前端也因為有重寫的需要,對資料夾結構大改。最後結果就是很多看不懂的git自動選擇版。有一些有選對,但有一些就選錯。 可以做一些事前處理,來減經痛苦。在筆者的資料夾問題情境,在把後端將要整合的多個commit中,挑選最早前沒有命名問題的commit先整合一次。然後前端先手動模擬後端的人工命名,自行commit一次,最後再把後端剩餘的commit再做整合。這個做法不是完全解決問題,但至少可以讓use mine / use theirs / edit更新易理解。 而另一個建議是,縮短發佈週期,逼使其他開發中的功能越早做整合,也逼使每個功能不要做太大規模的改動。如果真的做大規模改動,就要有心理準備要多次重要的整合。 情況3: 多個穩定發佈版本需要同時維護 若然大家面對的工作規模真的很大,同時有多個版運行版本,就如gitlab,每一個月都有一個新功能版本(16.0.x, 16.1.x, 16.2.x,… 16.9.x),但它不會強逼大家更新,對於過去一段時間的功能版本,也會推出安全性更新(前述的x會不斷修正問題)。 這是一個很負責任的發佈模式,不過對於開發者來講就一定很地獄。因為16.0.x的安全更新並不能無痛地整合到16.9.x中,可能每個版本重新人工修改還要來得穩健。 建議 各個分支人工修改可能更適合。最後就是取決於商業政策的考量,到底公司願意為已發佈的功能版本提供多久的支援。就以gitlab為例,其實它也只承諾維持兩三個月前的功能版本。是否會backport到多個月之前的版本,就看問題的嚴重性和backport難易度。 也分享一些筆者朋友的經驗,他們開發的是軟件跟硬件整理的軟件庫。但因為硬件有限制,例如庫的大小、算力的差異,所以最後分支多到爆炸。這也是軟硬整合的痛,問題暫時無解。除非老闆肯放棄市場。

龐大的Docker Logs該如何處理? | 傳統的syslog幫到你

科技新知
MacauYeah・2024-02-02

平常大家在做單機app時,寫log有很多選擇,最簡單就是寫在檔案中。但在docker container裏面,寫檔案時要注意怎樣保留log檔,避免因為重建container時不見了。 docker 大部份官方預設image,都把log導向至stdout和stderr。這是方便docker做管理,也方便大家使用統一的docker logs指令來查看,即使到了Swarm mode底下,docker service logs也是同樣原理,使用差異不大,頂多就是不保證log的實時性。 如果網路延遲不計較的話,最大問題也是logs怎樣保存的做法。預設就是container刪走的時候,logs也會一借走。單機模式下,沿用最普遍的方法寫log的做法不是不可行,只是考慮到在極端情況下,同一個node(節點)中,有可能同時運作同一個service(服務)的多個分身(replica),這裏它們寫檔案時就有機會互相搶佔。 筆者認為,比較合理的是外部提供的服務,例如syslog,把寫檔的操作交給節點的Host OS處理。然後就保證好每筆log都會是一條完整的記錄。 以下就以linux Host裏面的syslog,為大家簡介一下設定的步驟。 設定docker 導向 syslog 把該主機的docker daemon (/etc/docker/daemon.json),設定使用syslog driver,並以特定的方式編寫syslog tag。 { "log-driver": "syslog", "log-opts": { "tag": "dockercontainer/{{.ImageName}}/{{.Name}}/{{.ID}}" } } 無腦設定已完成,重啟docker就可以了。 但為了日後管理方便,能把docker log放進獨立的一個檔案中,會更易找問題。所以我們可以進一步設定syslog。我們以Ubuntu 22.04為例,可以在/etc/rsyslog.d/下增加一個設定檔(/etc/rsyslog.d/*.conf),指定看到syslog tag以dockercontainer為首的記錄,都要獨立抽出來。 # file: /etc/rsyslog.d/51-docker.conf :syslogtag,startswith,"dockercontainer" -/var/log/dockercontainer.log 為免有檔案權限問題,手動指定檔案的所有權後,才正式重啟syslog。然後所有相關記錄都會寫在/var/log/dockercontainer.log 滾滾滾滾滾動的log檔 檔案一天一天地長大,如果可以,還是自動清掉太舊的記錄為妙。Linux Syslog,通常也會配著logrotate使用。 筆者亦以Ubuntu 22.04為例子,做了個最簡單的自動滾Log功能。目標就是當log檔案大於1M後,就要重開log檔。舊的log檔最多保留7份,多了就刪掉最舊的。 # file: /etc/logrotate.d/rsyslog-dockercontainer /var/log/dockercontainer.log { rotate 7 size 1M missingok notifempty compress delaycompress sharedscripts postrotate /usr/lib/rsyslog/rsyslog-rotate endscript } 加了設定後,什麼都不用重啟,因為它是Ubuntu 的排程動作,到執行時就會以最新的設定檔執行,詳見/etc/cron.daily/logrotate. 有需要手動測試的話,需要手動呼叫/usr/sbin/logrotate。加入-d參數後,會被視為debug mode,這是官方的說法,但因為debug mode沒有執行效果,更加像是linux中常見的dry run mode。

Steam Deck 也可以作為文字創作

科技新知
MacauYeah・2024-01-23

之前筆者就介紹了,如何使用Steam Deck作為程式開發機使用。這可能對於一般讀者來講不太常用,更常用的是做一些文書處理。筆者最近也拿著Steam Deck,也一步步地補充文書處理所缺少的軟件,正式踏入Steam Deck日常之路。 如果你沒有對系統做過任何更改,在桌面模式中,只要打開「Discover」,輸入後逐的軟件的唯一package name,就可以找到相關軟件。 但如果你像筆者之前一樣,加了homebrew等第三方系統,可能所有軟件都需要在terminal中,經過指令sudo flatpak install PACKAGE_NAME。 Chrome 唯一碼: com.google.Chrome 系統預設瀏覽器只有Firefox,不習慣的話可以另外下載Chrome。有了Chrome,至少所有的雲端文書軟件都可以用,想用Google Doc也沒有問題。 中文輸入法:Fcitx5 + Rime 唯一碼:org.fcitx.Fcitx5 唯一碼:org.fcitx.Fcitx5.Addon.Rime Steam Deck原本有自帶的輸入法,但只適用於螢幕虛擬鍵盤使用(即使用Steam key + X,打開虛擬鍵盤),而實體鍵盤就無法轉輸入法了。這時就需要Linux上的Fcitx5和Rime了。安裝很簡單,之後還要設定一下。 首次安裝後,在啟動器(桌面左下角)搜㝷及啟動 fcitx5,然後在右下角就會見到有個新的鍵盤圖示出現。 按鍵盤圖示,滑鼠右鍵,點選configure,把Rime 加入Fcitx裏面,然後Apply → Close 然後按鍵盤圖示,滑鼠左鍵,應該就會切會成中文輸入法了。這時原本的鍵盤圖示會變成中文輸入法的圖示(或者你經Ctrl-Space也可以) 最後對著中文輸入法圖示,再滑鼠右鍵,可以選擇不同的中文輸入法,例如拼音、注意、倉頡等。 有了輸入法,有了瀏覽器,世界已經都是你的了。 下載器 JDownloader 唯一碼:org.jdownloader.JDownloader 它可以用來下載大部份隱藏文件,例如YouTube video / audio 。但需要注要,首次下載JDownloader 後,還要經過軟件內部更新,否則不能使用。(就像很多手遊,下了主程式後還要下更新檔) 其他 如果你不是長期有網絡,還需要真離線版文書處理器,還可以看看LibreOffice,WPS Office。但這些都不能保證跟windows office 百分百轉換,可能還是使用雲版的Microsoft office 365還要實際。

Spring Boot 01 - 萬物始於Spring boot context

科技新知
MacauYeah・2024-01-16

Spring Boot 01 - 萬物始於Spring boot context 筆者早些時候向一位朋友討論,為何Java那麼不受歡迎。朋友一句就回答,Java煩爆,沒有人會喜歡。 老實講,Java在句法上,實在囉唆。但以筆者的經驗,即使使用其他語言和開發框架,在實戰到一定複雜程度下,其實也一樣煩爆。 而現在的Java框架中,就以Spring boot的入門門檻低。筆者從Spring boot 1.x用到現在的3.x,也真的感受到更多的簡化,所以筆者也加入一起推廣Spring boot的行列。筆者將會通過一系列最小的可執行程式,為大家講解Spring在Web和資料庫上的應用。 所以現在就不廢話,馬上開壇作法 快速下戴模版 使用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或以上 之後就不用選了。若你經官網起範例,你會得到一個zip檔,下載後解壓縮。若你使用vscode插件,最後插件會叫有一個位置儲存。它們都是最後也是會得到同一樣範例Java project。 你使用Vscode,Intellij打開,IDE都會自動辨識到它是java maven project,同時會顯示java和maven結構。道理上你用Intellij 應該可以無腦開始編譯(Community 或Ultimate版都可以), Vscode有安裝Extension Pack for Java也會開始自動編譯。不想麻煩,也可以試用Github Codespaces - java。Github Codespaces其實就是一個雲上的vscode,經網頁可以連到Github VM內的vscode,所以它也會有齊Extension Pack for Java等插件。 筆者最後也會上載已完成的範例,它也可以在Github Codespaces上以Java執行或繼續開發。 打開project中的pom.xml,它為我們添加了兩個很重要的lib org.springframework.boot spring-boot-starter ... ... org.springframework.boot spring-boot-maven-plugin spring-boot-starter是重中之重,它定義了怎樣動態地設定日後的其他lib,它是讓我們可以無腦設定的一個關鍵。(但若大家有很多客制化的設定,就要返撲歸真地逐個lib叫起)。 maven在預設情況下,只會負責編譯和打包目前的project原始碼。所有相關依賴(就是xml中的dependency),並不會自動包起。而spring-boot-maven-plugin,就是幫我們把相關依據都包在一起,讓你的jar可以獨立行起來。 註: 若大家在開發lib jar,並不是一個獨立執行的jar,也就是原始碼上沒有main函數,大家就不應該引用spring-boot-starter和spring-boot-maven-plugin。 我們繼續看其他原始碼,整個資料夾就像以下那樣。 . |-- HELP.md |-- pom.xml `-- src |-- main | |-- java | | `-- io | | `-- github | | `-- macauyeah | | `-- springboot | | `-- tutorial | | `-- commandline | | `-- CommandlineApplication.java | `-- resources | `-- application.properties `-- test `-- java `-- io `-- github `-- macauyeah `-- springboot `-- tutorial `-- commandline `-- CommandlineApplicationTests.java CommandlineApplication是我們有main函數的java class。我像可以經過IDE運行main又或者下指令mvn spring-boot:run來執行。 正式開始我們的Commandline開發 我們在CommandlineApplication.class中,加入新的程式碼,實現ApplicationRunner和它的函數run。 package io.github.macauyeah.springboot.tutorial.commandline; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; // other import @SpringBootApplication public class CommandlineApplication implements ApplicationRunner { static Logger LOG = LoggerFactory.getLogger(CommandlineApplication.class); public static void main(String[] args) { SpringApplication.run(CommandlineApplication.class, args); } @Override public void run(ApplicationArguments args) throws Exception { args.getOptionNames().stream().forEach(optionName -> { LOG.debug("option name:" + optionName); args.getOptionValues(optionName).forEach(optionValue -> { LOG.debug("option values:" + optionValue); }); }); LOG.debug("program end."); } // ... 這個run函數很直白,就是更好地演譯main中的String[] args。 但大家還要看清楚,這個main並沒有直接執行run。其實它是靠SpringApplication.run及@SpringBootApplication,跑一堆自動設定,最後因為傳入CommandlineApplication.class是一個Spring 可以處理的ApplicationRunner,所以才呼叫它的CommandlineApplication.run。 換個講法,如果今天做的是web應用,傳入去的就會是SpringBootServletInitializer,這個SpringBootServletInitializer也不一定跟main是同一個class。 如果大家有興趣,可以經過反編譯器,點入@SpringBootApplication看它的原始碼,你就可以看到它其實代表了很多自動化的東西。如果我們只做一些在同一個模組下生效的事情,《自動化》極大地降低了大家入門門檻。一般來講,如果大家不在意程式碼的複用度,比較少機會自行設定,自動化已經很有用。而隨著系統規模增加,多模組就慢慢地顯得重要,在大家了解完基本的Spring後,著者再從測試用途test case入手,為大家介紹如何手動設定。 Source Code Commandline Application