這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
本文為技術翻譯,原文地址(需翻牆):http://www.goinggo.net/2013/12/sample-web-application-using-beego-and.html
簡介
當我發現 beego 架構時感覺非常激動。我只用了大約 4 個小時就將一個現有的 Web 應用程式移植到了該架構上並做了一些端對端測試的調用擴充。我想要與你分享這個基於 beego 的網站。
我構建了一個具有以下功能的樣本 Web 應用程式:
- 實現了 2 個通過 mgo 驅動拉取 MongoDB 資料的 Web 調用。
- 使用 envconfig 配置環境變數作為參數。
- 通過 goconvey 書寫測試案例。
- 結合我的 logging 包。
這個樣本的代碼可以在 GoingGo 帳戶下的 GitHub 倉庫中找到:https://github.com/goinggo/beego-mgo。
你可以拉取並運行它。它使用了我在 MongoLab 建立的公開 MongoDB 資料庫。你會需要安裝 git 和 bazaar 以保證能夠使用 go get 來將它安裝到你系統中。
go get github.com/goinggo/beego-mgo
使用 zscripts 檔案夾裡的腳步可以快速運行或測試 Web 應用程式。
Web 應用程式代碼結構
讓我們來看一下項目結構和不同檔案夾的功能:
- app:包含 business、model 和 service 層。
- app/models:用於 business 和 service 層的資料結構。
- app/services:用於為不同服務提供基本函數。可以是針對資料庫或 Web 調用的函數。
- app/business:被 controller 和處理 business 規則的函數所調用。多種服務的組合調用可用於實現更大層面上的功能。
- controllers:URL 或 Web API 呼叫的切入點。控制器會直接調用 business 層的函數來直接處理請求。
- routes:用於 URL 和控制器代碼的映射。
- static:用於存放指令碼、CSS 和圖片等靜態資源。
- test:可使用 go test 啟動並執行測試案例。
- utilities:用於支援 Web 應用程式的代碼。資料庫操作和 panic 處理的範例與抽象化代碼。
- views:用於存放模板檔案。
- zscripts:協助更方便構建、運行和測試 Web 應用程式的指令碼。
控制器、模型和服務
這些層的程式碼群組成了 Web 應用程式。我的架構背後的理念是儘可能的抽象化代碼為樣板。這就要求我實現一個 base 控制器包和 base 服務包。
base 控制器包
base 控制器包由所有控制器所要求的預設抽象的控制器行為來組成:
type ( BaseController struct { beego.Controller services.Service })func (this *BaseController) Prepare() { this.UserId = "unknown" tracelog.TRACE(this.UserId, "Before", "UserId[%s] Path[%s]", this.UserId, this.Ctx.Request.URL.Path) var err error this.MongoSession, err = mongo.CopyMonotonicSession(this.UserId) if err != nil { tracelog.ERRORf(err, this.UserId, "Before", this.Ctx.Request.URL.Path) this.ServeError(err) }}func (this *BaseController) Finish() { defer func() { if this.MongoSession != nil { mongo.CloseSession(this.UserId, this.MongoSession) this.MongoSession = nil } }() tracelog.COMPLETEDf(this.UserId, "Finish", this.Ctx.Request.URL.Path)}
一個名為 BaseController
的新類型直接由類型 beego.Controller
和 services.Service
嵌入而組成。這樣做就可以直接獲得這兩個類型的所有欄位和方法給 BaseController
,並直接操作這些欄位和方法。
服務包
服務包用於樣板化所有服務所要求的代碼:
type ( Service struct { MongoSession *mgo.Session UserId string })func (this *Service) DBAction(databaseName string, collectionName string, mongoCall mongo.MongoCall) (err error) { return mongo.Execute(this.UserId, this.MongoSession, databaseName, collectionName, mongoCall)}
在 Service
類型中,包含了 Mongo 會話和使用者識別碼。函數 DBAction
提供了運行 MongoDB 命令和查詢的抽象層。
實現一個 Web 調用
基於 base 類型和樣板函數,我們現在可以實現一個 Web 調用了:
Buoy Controller
類型 BuoyController
實現了兩個結合 BaseController
和 business 層 Web API 呼叫。我們主要關注名為 Station
的 Web 調用。
type BuoyController struct { bc.BaseController}func (this *BuoyController) Station() { buoyBusiness.Station(&this.BaseController, this.GetString(":stationId"))}
類型 BuoyController
直接由單獨的 BaseController
組成。通過這種組合方式,BuoyController
自動獲得了已經自訂實現的 Prepare
和 Finish
方法以及所有 beego.Controller
的欄位。
函數 Station
通過下面路由分發。stationId
作為 URL 的最後一項。當這個路由被請求時,一個 BuoyController
對象會被執行個體化並執行函數 Station
。
beego.Router("/station/:stationId", &controllers.BuoyController{}, "get:Station")
函數 Station
會在底層調用 business 層的代碼來處理請求。
Buoy Business
Buoy Business 包實現了 BuoyController
的 business 層。讓我看看在 BuoyController
中的函數 Station
所調用的 business 層的代碼是怎樣的:
func Station(controller *bc.BaseController, stationId string) { defer bc.CatchPanic(controller, "Station") tracelog.STARTEDf(controller.UserId, "Station", "StationId[%s]", stationId) buoyStation, err := buoyService.FindStation(&controller.Service, stationId) if err != nil { ServeError(err) return } controller.Data["json"] = &buoyStation controller.ServeJson() tracelog.COMPLETED(controller.UserId, "Station")}
你可以看到 business 層的函數 Station
處理了整個請求的邏輯。這個函數還使用了Buoy Service 來處理與 MongoDB 的互動。
Buoy Service
Buoy Service 包實現了與 MongoDB 的互動。讓我們來看下被 business 層的函數 Station
所調用的函數 FindStation
的代碼:
func FindStation(service *services.Service, stationId string) (buoyStation *buoyModels.BuoyStation, err error) { defer helper.CatchPanic(&err, service.UserId, "FindStation") tracelog.STARTED(service.UserId, "FindStation") queryMap := bson.M{"station_id": stationId} tracelog.TRACE(service.UserId, "FindStation", "Query : %s", mongo.ToString(queryMap)) buoyStation = &buoyModels.BuoyStation{} err = service.DBAction(Config.Database, "buoy_stations", func(collection *mgo.Collection) error { return collection.Find(queryMap).One(buoyStation) }) if err != nil { tracelog.COMPLETED_ERROR(err, service.UserId, "FindStation") return buoyStation, err } tracelog.COMPLETED(service.UserId, "FindStation") return buoyStation, err}
函數 FindStation
用於準備通過 DBAction
函數進行與 MongoDB 的查詢與執行操作。
端到端測試
我們現在有一個 URL 可以路由到一個控制器以及相應的 business 與 service 層的邏輯,它可以通過以下方式測試:
func TestStation(t *testing.T) { r, _ := http.NewRequest("GET", "/station/42002", nil) w := httptest.NewRecorder() beego.BeeApp.Handlers.ServeHTTP(w, r) err := struct { Error string }{} json.Unmarshal(w.Body.Bytes(), &err) Convey("Subject: Test Station Endpoint\n", t, func() { Convey("Status Code Should Be 200", func() { So(w.Code, ShouldEqual, 200) }) Convey("The Result Should Not Be Empty", func() { So(w.Body.Len(), ShouldBeGreaterThan, 0) }) Convey("The Should Be No Error In The Result", func() { So(len(err.Error), ShouldEqual, 0) }) })}
測試建立了一個某個路由的虛擬調用。這種做法很贊,因為我們不需要真正地去啟動一個 Web 應用程式來測試代碼。使用 goconvey 我們能夠建立非常優雅的輸出且易於閱讀。
以下是一個測試失敗的範例:
Subject: Test Station Endpoint Status Code Should Be 200 ✘ The Result Should Not Be Empty The Should Be No Error In The Result ✘Failures:* /Users/bill/Spaces/Go/Projects/src/github.com/goinggo/beego-mgo/test/endpoints/buoyEndpoints_test.go Line 35:Expected: '200'Actual: '400'(Should be equal)* /Users/bill/Spaces/Go/Projects/src/github.com/goinggo/beego-mgo/test/endpoints/buoyEndpoints_test.go Line 37:Expected: '0'Actual: '9'(Should be equal)3 assertions thus far--- FAIL: TestStation-8 (0.03 seconds)
以下是一個測試成功的範例:
Subject: Test Station Endpoint Status Code Should Be 200 The Result Should Not Be Empty The Should Be No Error In The Result 3 assertions thus far--- PASS: TestStation-8 (0.05 seconds)
結論
花點時間下載這個項目隨便看看。我盡最大努力讓你能夠清晰地看到我所想要展示給你的重點。beego 架構讓你能夠非常輕鬆地實現抽象和樣板代碼,遵照 go 的風格整合 go 測試、運行及部署。