<!-- .slide: style="font-size: 30px;" --> # Golang 入門 & pos-service 架構分享 --- <!-- .slide: style="font-size: 30px;" --> ## Agenda - Golang 超級入門 - Why Golang - 一點基本概念 - Good parts & Weird parts - 學習資源 - pos-service 架構分享 - 專案架構 & 導覽 - Dependency Injection - how to develop / review a package - how to test a package Note: 目標,可以看懂 go code,可能可以進行 code review 內部分享,都可以打斷討論或補充 --- <!-- .slide: style="font-size: 30px;" --> ## Golang 入門 ![](https://hackmd.io/_uploads/ryka5kSYh.jpg) - 由C語言編寫而成,長得像 C,具有指標的概念 - 需要經過編譯 - 強型別程式語言 Note: gopher --- <!-- .slide: style="font-size: 30px;" --> ### Why Golang ? ![](https://hackmd.io/_uploads/HyfaiLNY3.png) - 有 Goolge 支持,很潮、開源 - 易學(吧),適合大專案使用(有明確偏好的開發風格) - 內建併發,易開發多線程程式。有結實的 [standard library](https://pkg.go.dev/std) - 生態系多元 - 編譯速度快,執行效能高,~~公司說要用~~ --- <!-- .slide: style="font-size: 30px;" --> ### 基本概念 - package - Every Go program is made up of packages - Entry point: package main 的 main func. - package 內互相可見,外部 package 只能使用其他 package 中有 export 的變數/func - 大寫開頭的 var / func 代表 exported ```language=golang package main import "fmt" func swap(x, y string) (string, string) { return y, x } func main() { a, b := swap("hello", "world") fmt.Println(a, b) } ``` Note: 若 import 其他 package,一定是呼叫他的 exported func,所以是大寫開頭 ---- <!-- .slide: style="font-size: 30px;" --> ### 基本概念 - variable - 用 var 宣告 package / function level 的變數 - 用 := 宣告且賦值 function level 的變數,只活在該 func scope 中,又稱為 short assignment statement - 只在必要時使用 var,其餘使用 := ```language=golang package main import "fmt" var i int // 宣告 package level 變數,沒有賦值 var j int = 2 // 宣告且賦值 package level 變數 func main() { var k bool = false // 宣告且賦值 func level 變數 fmt.Println(i, j, k) a := 3 // 宣告且賦值 func level 變數 b, c := "true", false // 宣告且賦值多個 func level 變數 fmt.Println(a, b, c) printVar() } func printVar() { fmt.Println(i, j) // fmt.Println(k) // undefined: k // fmt.Println(a, b, c) // undefined: a, b, c } // 0 2 false // 3 true false // 0 2 ``` ---- <!-- .slide: style="font-size: 30px;" --> ### 基本概念 - Zero values - 當我們創造一個變數但沒有賦值時,值會自動為該 type 的 Zero values (不像 js 是 undefined) - var i int // type 為 int, value 為 0 - 當我們創造一個變數且賦值時,編譯器會知道其 type - i := 42 // type 為 int, value 為 42 ``` 0 for numeric types, 0.0 for floats false for the boolean type, “” (the empty string) for strings. nil for pointers, functions, interfaces, slices, channels, and maps ``` ---- <!-- .slide: style="font-size: 30px;" --> ### 基本概念 - For & If - for: golang 裡只有 for,沒有 while,因為 for 可以做到 while ```language=golang func main() { sum := 0 for i := 0; i < 10; i++ { // init statement; condition; post statement sum += i } fmt.Println(sum) } ``` - if: 也可以使用 init statement (optional) ```language=golang func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { // init statement; condition return v } else { fmt.Println("exceed limit") } // can't use v here ! return lim } func main() { fmt.Println(pow(3, 2, 10)) // 9 fmt.Println(pow(3, 3, 20)) // exceed limit, 20 } ``` ---- <!-- .slide: style="font-size: 30px;" --> ### 基本概念 - Struct, field, method - golang 裡沒有 classes,只有 struct - struct = collection of fields - method = 定義在 struct (或其他自定義的 type)上的 function ```language=golang type Vertex struct { X float64 // filed X Y float64 // filed Y } // func func Abs(v Vertex) float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } // method func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(Abs(v)) fmt.Println(v.Abs()) // method 更明確地寫出要使用在哪個 receiver type 上 } ``` ---- <!-- .slide: style="font-size: 30px;" --> ### 基本概念 - Interface - 在 interface 中定義需要滿足的 method,但不需要寫出如何實作 - 一個 type 若有實作 interface 中定義的 method ,該 type 就 **隱式的實現** 該 interface ```language=golang type Abser interface { Abs() float64 } type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { var a Abser // 變數 a 的 type 是 Abser, 值是 nil v := Vertex{3, 4} // 宣告一個 vertex 的實例 a = v // 因為 vertex 實現 Abser,所以可以將 v assign 給 a,現在 a 值為 v fmt.Println(a.Abs()) } ``` ---- <!-- .slide: style="font-size: 30px;" --> ### 基本概念 - 沒有講到的部分 - 常用型別:slice, map - 理解 pointers: pass by value / pass by pointer - 泛型 generics - Concurrency: Goroutines & channels --- <!-- .slide: style="font-size: 30px;" --> ### Good parts - Go 中不能有宣告但未使用的變數,編譯時會直接報錯 - debug 時有點麻煩,但提昇可讀性 - 有內建 gofmt 及好用的 linter - 統一程式碼風格 - A minimal set of language constructs - 捨棄作者認為「花俏」、「降低可讀性」的語法 (有好有壞) - 例如:沒有三元判斷式可以用 QQ - Values vs Pointers - 可以做到 pass by values & pointers,自由度高 ---- <!-- .slide: style="font-size: 30px;" --> ### Weird parts - error handling - Error Checking & Ok checking everywhere: 因為 golang 沒有 throw...catch 的語法 ```language=golang func (service *Service) UpsertStaffToken( accessToken string, refreshToken string, userProfile *sloauth.UserProfile, ) (*StaffToken, error) { encryptedAccessToken, err := service.encryptTokenService.Encrypt(accessToken) if err != nil { return nil, err } encryptedRefreshToken, err := service.encryptTokenService.Encrypt(refreshToken) if err != nil { return nil, err } staffToken, err := service.staffTokenModel.UpsertStaffToken(encryptedAccessToken, encryptedRefreshToken, userProfile) if err != nil { return nil, err } return staffToken, nil } ``` ---- <!-- .slide: style="font-size: 30px;" --> ### Weird parts - time format ![](https://hackmd.io/_uploads/HJkRDZHt2.png) ---- ![](https://hackmd.io/_uploads/S1kCDbrt3.png) ``` 2006 = YYYY, 01 = MM, 02 = dd, 15 = HH, 04 = mm, 05 = ss ``` ![](https://hackmd.io/_uploads/rkVW_ZBK2.gif) --- <!-- .slide: style="font-size: 30px;" --> ### 學習資源 & Best practice - A Tour of go (互動式):https://go.dev/tour/welcome/1 - official tutorials:https://go.dev/doc/tutorial/ - online playground: https://go.dev/play/ - go 101: https://go101.org - 中文版: https://gfw.go101.org - Learn to become a Go developer: https://roadmap.sh/golang - standard library & third-party packages: https://pkg.go.dev - Effective Go (稍微過時,仍有參考價值): https://go.dev/doc/effective_go - Go Code Review Comments: https://github.com/golang/go/wiki/CodeReviewComments - Go styleguide: https://google.github.io/styleguide/go/decisions --- <!-- .slide: style="font-size: 30px;" --> ## pos-service 架構分享 - repo: [pos-service](https://bitbucket.org/starlinglabs/pos-service/src/master/) --- <!-- .slide: style="font-size: 30px;" --> ### 專案架構 & 導覽 - pos-service 是個包含前後端的 monorepo - 後端 part 在 packages > api 底下,使用 [golang-standards/project-layout](https://github.com/golang-standards/project-layout) 架構 - 專案內使用的 package 放在 internal 底下,每個 package 都是高內聚的單位 - 可以獨立抽出 library 的 package 放在 pkg 底下 - entry point 在 cmd > api / cronjob / sqs-worker > main.go > func main --- <!-- .slide: style="font-size: 30px;" --> ### Dependency Injection ![](https://hackmd.io/_uploads/B1dbIMHtn.png) - 直接在 ClassA 裡引用 / 產生出 services,會導致高耦合、測試不易 - 讓 services 成為 classA 的依賴,並透過 interface 層降低耦合 ---- <!-- .slide: style="font-size: 30px;" --> ### Dependency Injection ![](https://hackmd.io/_uploads/B1dbIMHtn.png =400x150) ```language=golang // 定義 target (ClassA) 及其依賴的 interface (Iservice) type ClassA struct { serviceA IServiceA serviceB IServiceB } type IServiceA interface { methodA() } type IServiceB interface { methodB() } // Provider / Builder: 產生 target (ClassA) 的 function func ProvideClassA (serviceA IServiceA, serviceB IServiceB) ClassA { return ClassA{serviceA, serviceB} } // 在實際 app 運作時 classA = ProvideClassA(realServiceA, realServiceB) // 在測試時 classAToBeTest = ProvideClassA(mockServiceA, mockServiceB) ``` ---- <!-- .slide: style="font-size: 30px;" --> ### DI in pos-service ![](https://hackmd.io/_uploads/ByCxd4Bt2.png =600x500) --- <!-- .slide: style="font-size: 30px;" --> ### how to develop / review a package - example: add a new api endpoint, for getting merchant's product-category - 註冊 router & endpoint 的關係 - 實作 product-category 的 controller, service - 實現 oa 拿 product-category 的方法 ![](https://hackmd.io/_uploads/SygNzMHY3.png) ---- <!-- .slide: style="font-size: 30px;" --> ### how to develop / review a package ![](https://hackmd.io/_uploads/BJE58QrY3.png) ![](https://hackmd.io/_uploads/rJpz3zBF3.png) --- <!-- .slide: style="font-size: 30px;" --> ### how to test a package ![](https://hackmd.io/_uploads/B1oQhfBY3.png) --- <!-- .slide: style="font-size: 30px;" --> ## List of packages useses - [gin-gonic](https://github.com/gin-gonic/gin#gin-web-framework): Web Framework - [mgm](https://github.com/Kamva/mgm): mongo go models - [go-i18n](https://github.com/nicksnyder/go-i18n): i18n client - [ginkgo](https://github.com/onsi/ginkgo) & [gomega](https://github.com/onsi/gomega) & [testify](https://github.com/stretchr/testify): test framework and related - [oso](https://github.com/osohq/oso): authorization framework - [liquid](https://github.com/osteele/liquid): template framework 詳情請看 `go.mod` --- <!-- .slide: style="font-size: 30px;" --> ## Q&A ![](https://hackmd.io/_uploads/SyblPXBYh.png)
{"title":"Golang 入門 & pos-service 架構分享","description":"Golang 超級入門","contributors":"[{\"id\":\"877c0604-9ea2-40ff-8834-5ea8a760ce56\",\"add\":12556,\"del\":3339}]"}
    328 views