Project Layout ของ Golang EP.1
Tags: go, programming, layout, structure
Published: 2020-12-12 12:18:28

วันนี้จะมาเล่าเรื่อง Project Layout ครับ เรียกได้ว่าเป็นหนึ่งในสิ่งสำคัญที่จะทำให้เราพัฒนา application ด้วยภาษา Golang ได้ตรงตามที่ผู้พัฒนาต้องการให้เราทำ ( หรือ Idiomatic ที่แปลว่า ลักษณะจำเพาะ ) เพื่อให้โปรเจคเรา ใกล้เคียงความเป็น Gopher project มากที่สุดกันครับ

# ✨ TL;DR อีกแล้ว
Golang ไม่มี layout standard เป็นตัวเป็นตนจาก Go Core Dev team ที่มีอยู่ตอนนี้ก็คือ ตั้งแต่ Go `1.4` เป็นต้นไป folder `internal` จะไม่สามารถถูกเรียกใช้จากนอก package หลักได้ 
> [release note เรื่อง internal folder](https://golang.org/doc/go1.4#internalpackages) 

นอกนั้นก็ตามแล้วแต่ผู้ดูแลโปรเจคจะเป็นคนกำหนด convention ของ layout ตัวเอง

ส่วนทางด้าน community และ open source ก็มีตัวอย่างที่ใช้กันบ่อยๆ ฮิตๆก็ดูได้จาก Repository นี้ (และ open source ของ Golang อื่นๆ)

https://github.com/golang-standards/project-layout

หลักๆคือ
- `cmd` ใช้สำหรับเก็บ package ที่เป็น entry point ของ application (ส่วนใหญ่ก็คือ package main) โดยจะไม่นำ code มาเขียนตรงนี้เยอะ โดย package ใน `cmd` จะไปเรียกใช้ code ที่อยู่ในส่วนของ `interal` และ `pkg`

- `internal` เก็บ package ที่เป็น implementation หลักของ application (หรือ library) ซึ่งไม่ต้องการให้มีการ import จากนอก package

- `pkg` ใช้เก็บ package ที่เหมือนกัน `internal` แต่สามารถ import ไปใช้งานได้นอก application (หรือ library) เหมาะสำหรับใช้ reuse package ไปที่ project อื่นๆ

และนอกจากนี้ เรายังสามารถศึกษาได้จาก Open Source Project อื่นๆที่พัฒนาด้วย Golang (ดูต่อที่ Reference)

แต่ยังไงก็ตาม การทำงานที่มีประสิทธิภาพที่สุด ก็คือการทำงานบน convention ที่เราสามารถสร้าง productivity ได้มากที่สุด เพราะฉะนั้น ก็ไม่จำเป็นจะต้องทำตามสิ่งเหล่านี้ทั้งหมดครับ เราอาจจะใช้ layout ของ NodeJS หรือ Java ก็ไม่เป็นอะไร ถ้าเราสามารถ deliver งานได้ตามที่ business ต้องการ

<hr />

เริ่มเนื้อหาแบบยาว...



# Project Layout คืออะไร?
ในการพัฒนาแอพพลิเคชั่นแต่ละภาษา ก็จะมี Project Layout แตกต่างกันออกไปครับ เพื่อการ organize code ที่ดี แต่ละภาษาก็มี convention แตกต่างกันออกไปตาม design pattern ของภาษา

เช่นเมื่อ New Project ใน ​`Java`, `C#`, `Swift` หรือ `NodeJS` รวมไปถึงระดับ Framework เช่น Laravel หรือ Angular เราก็จะได้ project layout แตกต่างกันออกไป แต่ก็เพื่อให้สามารถจัดการ Code ง่ายที่สุด ตามที่ผู้พัฒนาภาษาคิดมาซึ่งส่วนใหญ่ หนีไม่พ้น ทุกแบบก็จะประกอบไปด้วย

- Dependencies and Information
- Implementation
- Testing
- Build
- Distributed

เช่น `Swift` เราก็จะมี `Podfile`, `xcodeproj` หรือ `NodeJS` เราจะมี `package.json`, `src` folder

# Dependencies and Project Information
สำหรับ Golang แล้ว ก่อนที่จะมี `go mod` เราบันทึก dependencies ไว้ภายในตัว code เอง (ใน import) แต่หลังจากนั้นก็มี tools เพื่อควบคุม version เช่น `glider` หรือ `godeps` 

ปัจจุบัน Golang มี dependency management เองแล้วชื่อ `go mod` ซึ่งประกอบด้วย `go.mod` และ `go.sum` ภายในบ่งบอกถึง dependencies ต่างๆที่เราจะใช้งาน

ส่วน Project Information ต้องบอกก่อนว่า Golang เราทำ document ด้วยการ comment เป็นหลัก (เรื่องการทำ `godoc` จะเอาไว้เขียนบทความหน้า) เพราะฉะนั้น Information ของ package ต่างๆก็เลยถูกเขียนไว้ใน comment ทั้งหมด

# Implementation
ส่วนที่เราเก็บ code ที่เราเขียนไว้จะประกอบด้วย 4 packages หลักๆ (จริงๆมีมากกว่านั้น แต่ผู้เขียนเลือกอันที่ใช้ และเจอบ่อยมาเพื่อความเรียบง่าย ลองดูใน Go Standard Layout เพิ่มเติมครับ)

## 1. cmd Folder
Folder หลักที่ใช้เก็บ Entry point package ต่างๆของ application โดยส่วนมากจะเป็น Package main โดย package ภายใต้ folder นี้ไม่จำเป็นจะต้องมี implementation ที่ซับซ้อนมากมาย ทำแค่เพียงเป็น entry point เรียก implementation อื่นๆจาก package ใน folder `internal`, `web` และ `pkg`

ตัวอย่างจาก [Ethereum Golang](https://github.com/ethereum/go-ethereum/tree/master/cmd/abidump)
![Etereum cmd](https://dev-to-uploads.s3.amazonaws.com/i/eyqahw469qmi1y3kvvrz.png)

ตัวอย่างจาก [Go Pkgsite](https://github.com/golang/pkgsite/blob/master/cmd/frontend/main.go)
![gopkgsite](https://dev-to-uploads.s3.amazonaws.com/i/igwp3u8tsklg09ztavvn.png)
<hr />

## 2. internal Folder
Folder นี้น่าจะเป็น folder ที่มี source code เก็บอยู่มากที่สุด (ถ้าเป็น application) เพราะประกอบด้วย implementation หลักของ Project ที่ไม่สามารถ import ผ่าน package อื่นๆนอกจาก Project นี้ได้ (หรือภายใต้ `go module` เดียวกัน)

ตัวอย่างจาก [Go Pkgsite](https://github.com/golang/pkgsite/tree/master/internal) ซึ่ง package แน่นมาก
![gopkg internal](https://dev-to-uploads.s3.amazonaws.com/i/yamj6oh07jvwquo8hfi3.png)

ถึงใน standard project layout จะบอกว่าเป็น folder ที่อยู่ด้านนอกสุด (workspace root) แต่ก็ไม่จำเป็น เพราะ Golang จะ recognize folder internal ให้ตลอด เพราะฉะนั้นไม่จำเป็นที่จะต้องอยู่ที่ workspace root ก็ได้

เช่นตัวอย่างจาก [Golang source code](https://github.com/golang/go/tree/master/src/internal) เองก็มีการวาง folder internal ไว้ภายใน `src` folder ( ซึ่ง `src` folder ไม่ได้มีใน Standard Project Layout )

![gosource internal](https://dev-to-uploads.s3.amazonaws.com/i/thxfa70r5zo29ol3md1g.png)

## 3. pkg Folder ( หรืออยู่ที่ root ไปเลย )
คล้ายๆกันกับ `internal` (อ่าว พูดเหมือนใน TL;DR เลย) แต่ในนี้จะประกอบด้วย code ที่สามารถนำไป reuse ใช้งานภายนอก package หลักได้

หรือบาง repository ก็ไม่ใช้เลย โดยนำเอา package ต่างๆมาไว้ที่ workspace root (ที่เดียวกับ `go.mod` ไปเลย) โดยมีการเถียงกันที่ issue นี้ 555555
https://github.com/golang-standards/project-layout/issues/10

ถ้าดูตัวอย่าง ส่วนใหญ่จะเป็น Library หรือ Opensource tools ต่างๆ เช่น
ตัวอย่างจาก [Moby (container engine ของ docker)](https://github.com/moby/moby/tree/master/pkg)

![moby pkg](https://dev-to-uploads.s3.amazonaws.com/i/v4gctv9t6zdig1b5dt7k.png)

ตัวอย่างจาก [Kubernetes](https://github.com/kubernetes/kubernetes/tree/master/pkg)
![kube pkg](https://dev-to-uploads.s3.amazonaws.com/i/j837d2hy81ud3unbbzmv.png)

ตัวอย่างจาก [Gorilla Mux](https://github.com/gorilla/mux) ที่ไม่ใส่ folder อะไรเลย เอาไว้มันโต้งๆนี่แหล่ะ

![mux pkg](https://dev-to-uploads.s3.amazonaws.com/i/xlw3ck6f5tyo5546lh7x.png)

## 4. web Folder
Folder นี้จะประกอบด้วย source code ของ frontend ซะส่วนใหญ่ (ถ้าเป็น web base application) โดยเราจะเอา html template, static asset ต่างๆมาเก็บไว้ในนี้ หรือถ้าเป็น Javascript framework อย่าง Angular ก็นำมาเก็บไว้ในนี้ได้ (ในกรณี Monorepository)

ตัวอย่างจาก [traefik (http engine ที่เขียนด้วย golang (คล้ายๆ nginx)](https://github.com/traefik/traefik)
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/abwhkspajn9qrk821j6u.png)


go pkgsite นั้นเอาไว้ใน `content` folder แทน [`go pkgsit repository`](https://github.com/golang/pkgsite/tree/135895829587fb7ebda46246157eefcf9c1b1f1d/content/static/html/pages)


<hr />

จบแล้วสำหรับ part แรก เดี๋ยวส่วนอื่นๆที่เหลือ จะเขียนต่อใน part ต่อไปครับ

Happy Christmas and coding!
🎄 🤖



### Reference
- https://golang.org/doc/code.html
- https://github.com/golang/go
- https://github.com/golang-standards/project-layout
- https://github.com/gorilla/mux
- https://github.com/kubernetes/kubernetes
- https://github.com/golang/pkgsite/commit/4ad1f04766f983b16d42c752c6e12f011f155207
- https://github.com/moby/moby
- https://github.com/traefik/traefik