無需花時間試圖弄清楚如何將程式碼分解爲軟體包,而是採用扁平結構的應用程式會將所有.go
檔案放置在一個軟體包中。
myapp/ main.go server.go user.go lesson.go course.go
進入Go時,幾乎每個人都從一個平面應用程式結構開始。 Go tour中的每個程式,Gophercises中的大多數練習以及許多其他早期的Go程式都沒有被分解成任何包裝。取而代之的是,我們只建立幾個.go
檔案,然後將所有程式碼放入相同的(通常是main
)包中。
起初,這聽起來很糟糕。程式碼會很快變得笨拙嗎?如何將業務邏輯與UI渲染程式碼分開?我如何找到正確的原始檔?畢竟,我們使用軟體包的很大一部分原因是要分離關注點,同時使更容易快速地導航到正確的原始檔。
相關學習推薦:
使用平面結構時,您仍應嘗試遵守編碼最佳實踐。您將需要使用不同的.go
檔案分隔應用程式的不同部分:
myapp / main.go#閱讀設定並在此處啓動您的應用 server.go#總體HTTP處理邏輯在這裏 user_handler.go#使用者http處理程式邏輯在這裏 user_store.go#用戶數據庫邏輯在這裏 # 等等...
全域性變數仍然可能成爲問題,因此您應考慮將型別與方法配合使用,以使它們脫離程式碼:
type Server struct { apiClient *someapi.Client router *some.Router } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.router.ServeHTTP(w, r) }
而且您的main()
函數可能仍應在設定應用程式之外刪除大多數邏輯:
//警告:此範例非常人爲設計,甚至可能無法編譯。 type Config struct { SomeAPIKey string Port string EnableTheThing bool } func main() { var config Config config.SomeAPIKey = os.Getenv("SOMEAPI_KEY") config.Port = os.Getenv("MYAPP_PORT") if config.Port == "" { config.Port = "3000" } config.EnableTheThing = true if err := run(config); err != nil { log.Fatal(err) } } func run(config Config) error { server := myapp.Server{ APIClient: someapi.NewClient(config.SomeAPIKey), } return http.ListenAndServe(":" + config.Port, server) }
實際上,您實際上可以將基本上是平面結構的程式碼全部使用在一個軟體包中,並在單獨的main
軟體包中定義命令。這將允許您使用常見的cmd
子目錄模式:
myapp/ cmd/ web/ # package main main.go cli/ # package main main.go # package myapp server.go user_handler.go user_store.go ...
在此範例中,您的應用程式基本上仍然是平坦的,但是您拔出了main
軟體包是因爲您有需要-例如可能需要使用同一核心應用程式來支援兩個命令。
扁平結構的主要好處不是將所有程式碼都儲存在一個目錄中,也不是那樣愚蠢的東西。這種結構的核心好處是您可以不必擔心如何組織事物,而可以繼續解決您打算通過應用程式解決的問題。
我絕對喜歡這個應用程式結構讓我回想起PHP的日子。當我第一次學習編碼時,我開始使用隨機PHP檔案,其邏輯與各種HTML混合在一起,這真是一團糟。我並不是在建議我們以大型應用程式的方式構建-那樣會很糟糕-但是我並不擔心一切都應該放在哪裏,而是更加關注學習如何編寫程式碼和解決我的特定問題。無論您是要瞭解應用程式的需求,您的域還是一般的編碼方式,使用扁平結構都可以使您更輕鬆地專注於學習和構建。
這是正確的,因爲我們可以不再擔心諸如「這種邏輯應該去哪裏?」之類的問題。因爲如果我們犯了一個錯誤,很容易解決。如果它是一個函數,我們可以將其移動到包中的任何新原始檔中。如果它是錯誤型別的方法,我們可以建立兩個新型別並將邏輯與原始型別分開。有了這些,我們就不必擔心會遇到奇怪的週期性依賴問題,因爲我們只有一個軟體包。
考慮平面結構的另一個重要原因是,隨着應用程式複雜性的提高,結構的發展變得容易得多。當您明顯可以從將程式碼拆分到一個單獨的程式包中受益時,您通常需要做的就是將一些原始檔移到一個子目錄中,更改其程式包,並更新任何參照以使用新的程式包字首。例如,如果我們有SqlUser
並決定從一個單獨的sql
包中處理所有與數據庫相關的邏輯中受益,我們將更新所有參照以現在使用sql.User將型別移動到新軟體包後
。我發現,像MVC這樣的結構在重構方面更具挑戰性,儘管並非沒有其他程式語言那樣困難或困難。
扁平結構對於通常太快無法建立包的初學者特別有用。我真的不能說爲什麼會發生這種現象,但是Go的新手喜歡建立大量的軟體包,這幾乎總是導致口吃(user.User
),週期性依賴關係或其他一些問題。
在下一篇有關MVC的文章中,我們將探討這種建立過多包的現象如何使MVC在Go中顯得不可能的方法,儘管事實並非如此。
通過推遲建立新程式包的決定,直到我們的應用程式增長一點並更好地瞭解它,發芽的Gophers犯此錯誤的可能性就大大降低了。
這也是爲什麼很多人會鼓勵開發人員避免過早將其程式碼分解到微服務中的原因-您通常沒有足夠的知識來真正知道應該和不應該將哪些內容分解爲微服務以及搶先式微服務( I kinda希望能成爲一句俗語)只會在將來帶來更多工作。
假裝使用扁平結構沒有任何不利之處,這對我來說是不誠實的,所以我們也應該討論這些。
對於初學者來說,扁平的結構只能使您受益匪淺。它會工作一段時間(可能比您想象的更長),但是到某個時候,您的應用程式將變得足夠複雜,您需要開始分解它。使用平面結構的好處是您可以推遲使用它,並且在分解時可能會更好地理解您的程式碼。缺點是,您將需要花一些時間進行重構,並且您可能(也許-但這很麻煩)發現自己已經重構爲您想從任何地方開始的結構。
使用平面結構時,命名衝突有時也會很尷尬。例如,假設您想要在應用程式中使用Course
型別,但是在數據庫中表示課程的方式與在JSON中呈現課程的方式不同。一個快速的解決方案是建立兩種型別,但是由於它們都在同一個包中,因此每種型別都需要使用不同的名稱,並且可能最終以類似以下內容的形式出現:SqlCourse
和JsonCourse
。這確實沒什麼大不了的,但是有點令人遺憾的是我們最終得到了零型別,簡單地稱爲Course
。
將程式碼重構爲新程式包也不總是那麼簡單。是的,這通常很容易,但是由於所有程式碼都在一個包中,因此您有時可能會遇到天生具有週期性的程式碼。例如,假設我們的課程是否具有在JSON響應中始終以crs_
開頭的ID,並且我們想以各種貨幣返回價格。我們可以建立一個JsonCourse
來處理:
輸入JsonCourse struct { ID字串`json:「 id」` 價格結構{ USD字串`json:「 usd」` }`json:「價格」` }
同時,SqlCourse
僅需要儲存一個整數ID和一個以美分爲單位的單一價格,我們可以使用各種貨幣對其進行格式化。
type SqlCourse struct { ID int Price int }
現在我們需要一種將SqlCourse
轉換爲JsonCourse
的方法,因此我們可以將其作爲SqlCourse
型別的方法:
func (sc SqlCourse) ToJson() (JsonCourse, error) { jsonCourse := JsonCourse{ ID: fmt.Sprintf("crs_%v", sc.ID), } jsonCourse.Price.USD = Price: fmt.Sprintf("%d.%2d", sc.Price/100, sc.Price%100) return jsonCourse, nil }
然後稍後我們可能需要一種方法來解析傳入的JSON並將其轉換爲SQL等效項,因此我們將其新增到JsonCourse
型別中作爲另一種方法:
func (jc JsonCourse) ToSql() (SqlCourse, error) { var sqlCourse SqlCourse // JSON ID is "crs_123" and we convert to "123" // for SQL IDs id, err := strconv.Atoi(strings.TrimPrefix(jc.ID, "crs_")) if err != nil { // Note: %w is a Go 1.13 thing that I haven't really // tested out, so let me know if I'm using it wrong
相關學習推薦:
以上就是瞭解Go 扁平化專案結構的詳細內容,更多請關注php中文網其它相關文章!