這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
很多人推薦MEAN來開發網站。MEAN就是M:mongodb,E:expressjs、A:angular最後的N:nodejs。 但是如果你親身的體會到了js的嵌套回調的話你就會想換換別的辦法了。雖然說可以用promise等架構 破解深深的嵌套,但畢竟不是語言本身支援的。
為什麼說用Golang呢?因為Golang是一個有著動態語言的特點的靜態語言。文法簡單,文法糖就是盡量減少文法。 編譯的時候就可以把錯誤排查掉很多。不用像動態語言那樣啟動並執行時候才掉進坑裡。
在Golang眾多的Web架構中發現了Martini。 他後來覺得這個架構用了太多反射影響了效率,就有寫了另一個架構或者按照作者的說法“中介軟體”, 叫做Negroni。具體的開發原因看這裡。 可能是這個作者愛喝酒把他寫的架構都叫了個酒水的名字。Margini這個架構是作者受了Expressjs的啟發寫出來的。 所以在很多使用上的東西非常類似,比如route的綁定文法,支援類中介軟體的綁定文法等。也看過Golang的其他架構比如Revel, 有很多的酷炫的功能,和一站式的很多的支援,但是因為Martini的靈活決定選用Margini。
資料庫MongoDB
資料庫方面,對於事物和資料的一致性要求沒有那麼強的話,還是選用比較簡單的NoSQL資料庫比較合適。所以,就用MongoDB了。 也許在嚴謹的工作中不應該這麼用。但是MongoDB可以簡單到,只要把Http POST過來的json資料直接寫入資料庫中。 如果json比較簡單的話,完全不用做什麼設計!當然,實際工作的時候是不可以這樣的。我們討論的只是這樣的一個可行性。
那麼我們就開始用Martini(golang)和MongoDB來打造一個web APP。這個app的名稱就叫做navigator(領航者)。 是用來接受、儲存和分析使用者反饋以及使用者的各種問題的,比如各種客戶回函回來的使用者問題。這些問題的上下文資訊十分有限。 所以我們讓使用者通過手機用戶端來調用navigator的介面來同時提交相關的上下文資訊。 關於Golang的一些基礎的東西就不在這裡探討了。 go get擷取Martini,下載MongoDB。之後使用命令run起來MongoDB。給DB起一個名字:NDB。然後在裡面填充一個document。 MongoDB如果沒有insert一個Document的話,那麼這個資料庫其實是沒有建立的。這個Document就是一個Json。格式為:
{"feedbackcode" : "123","username" : "","phonenum" : "","deviceinfo" : {"systemtype" : "","systemver" : "","appver" : ""},"imagelist" : [ ],"desc" : "testing","feedbacktype" : "","other" : ""}
使用feedbackCode來標示一個唯一的feedback。資料庫的問題就徹底解決了。
網站的目錄結構
下面開始我們的網站部分。首先瞭解一下目錄結構:
navigator
|———conf
|-————–Conf.go # 配置資訊
|—————–ConfigError.go # 錯誤名稱
|———controllers
|—————–FeedbackController.go
|—————–IndexController.go
|–––models
|––––––FeedbackModel.go
server.go
這個目錄結構式典型的REST API的MVC模式體現。我們這裡不需要處理View,也就是Html的東西。但是, 還是顯示的區分了Model和Controller。view其實還是有的,只不過是最後render後的json。
RUN起來
首先,讓我們的web app跑起來。 給server.go添加如下的代碼:
package mainimport "github.com/go-martini/martini"func main() { m := martini.Classic() m.Get("/", func() string { return "Hello world!" }) m.Run()}
go run server.go命令就可以讓這個web app運行起來了。預設的是運行在3000連接埠上的。 在頁面上也只能看到光禿禿的幾個字Hello world! martini.Classic()調用之後,Martini會為你預設提供一些工具。其中最重要的一個就是路由。 在這之後就可以使用m.Get("/", func()string{return "Hello, world!"}來指定什麼路徑映射到什麼處理方法上。 最後調用m.Run()方法在3000連接埠上運行整個app。
現在開始要真正的開發這個app了。首先我們需要有一個簡單的需求分析,我們到底是要做什麼。 如前所述,使用者可以用這個API發送feedback上來,然後在另外一個介面裡把這些內容以列表的形式展示出來。 需求就是這麼簡單。我們下面來一步一步實現這些功能。
Controllers
由於insert一個feedback,需要的資訊量還是很大的。我們用POST得方法發送所需要的feedback資料。 所以,我們需要一個POST的路徑和處理函數Handler(以後都叫做Handler)。
m.Post("/", func(req *http.Request) string{ // 從req中取出參數,插入到資料庫})
現實feedback的列表的介面只需要響應一個GET請求。所以是一個GET的路徑和Handler。 看起來應該是這樣的:
m.Get("/", func() string {// 假設最後的字典會被轉換成Json串return []map[string]string{map[string]string{"feedbackcode": "1", "username": "John"}, map[string]string{"feedbackcode": "2", "username": "Tony"}}}
以上列出的Handler都是對於API添加資料和資料列表的一些類比。下面開始開始正式寫Controller。
列表Controller
先看代碼,然後再來詳細說明:
package controllersimport (// "fmt""fmt""github.com/martini-contrib/render""navigator/conf""navigator/models""net/http")type FeedbackController struct {}func (c *FeedbackController) FeedbackList(req *http.Request, r render.Render) {f := new(models.FeedbackModel)r.JSON(200, map[string]interface{}{"data": f.ModelList(nil)})}
引入github.com/martini-contrib/render, render是一個渲染的包。 如果有html的視圖的話,可以把資料轉換成html模板可用的資料。我們這裡用這個包來把字典轉換成json資料。 r.JSON(200, map[string]interface{}{"data": f.ModelList(nil)})中200,是狀態代碼。 需要轉換的字典是一個string為key,Model列表為值的字典。
這裡可以先瞭解一部分feedback的Model類:
type DeviceInfo struct {SystemType string `json: "systemType"` // ios or androidSystemVer string `json: "systemVersion"` // system version eg, ios 9.1AppVer string `json: "appVersion"` // app version eg, 2.9.0}type FeedbackModel struct {FeedbackCode string `json: "feedbackCode"`UserName string `json: "userName"`PhoneNum string `json: "phoneNum"`DeviceInfo DeviceInfo `json: "deviceInfo"`ImageList []string `json: "imageList"`Desc string `json: "description"`FeedbackType string `json: "feedbackType"` // normal feedback or a problem in useOther string `json: "other"` // other infomation}
在最開始的json資料定義,在Golang的Model類定義中可以定義為上面的代碼。 用"``"標示出來的,是在Model轉換為json字串的過程中Model欄位和json串的key值得對應關係。 最後Controller的功能是需要處理請求過來的資料的, 也就是Controller的方法會作為Get或者Post的Handler的。這兩者之間需要關聯起來:
package main import ( // other packages "navigator/controllers")func main() { var m = martini.Classic() var r = martini.NewRouter() feedbackController := new(controllers.FeedbackController) r.Get("/nav/feedback", feedbackController.FeedbackList) r.Post("/nav/feedback", feedbackController.Feedback) m.Action(r.Handle) m.RunOnAddr(":9090")}
這裡r.Get("/nav/feedback", feedbackController.FeedbackList),修改了之前的訪問路徑。/nav/feedback是在路徑上加了一個項目的名稱簡寫作為限定。因為直接叫做feedback有可能會和其他的同名介面混了。 也就是介面的汙染。所以加一個項目名稱來避免這個問題。feedbackController := new(controllers.FeedbackController)初始化FeedbackController。 把feedbackController的方法FeedbackList和訪問的路徑綁定到一起。 GET的Http訪問和FeedbackList方法綁定,POST的Http訪問和Feedback方法綁定。 最後m.RunOnAddr(":9090")代替了之前使用的m.Run()。之前app是運行在3000連接埠上的。 現在通過m.RunOnAddr(":9090")則app將運行在9090連接埠上。也就是可以用這一語句來制定app的運行連接埠。
Models和MongoDB
處理MongoDB我們用mgo這個庫。Golang雖然這麼多年了已經。還出了這麼多的明星產品。 但是,處理MongoDB的成熟的庫還是可以說沒有。完全沒有想python、nodejs等語言的庫那麼豐富。 有標準的,還有很多github上的其他開發人員開發的庫。Golang裡也就是mgo相對來說這個庫比較可用的了。
當POST請求的feedback的json字串傳上來的時候,會被轉換為一個FeedbackModel的對象。 Golang的json庫會協助我們做到這一點。 json.Unmarshal([]byte(dict), &f)這裡的Unmarshal方法會把一個json字串轉化為FeedbackModel對象。 這裡需要用到一個Factory 方法。產生一個Model對象,並且這個對象應該是一個富Model對象。 後面這個Model對象將具有可以操作操作資料庫,增、刪、改、查的方法。代碼:
func NewFeedbackModel(dict string) *FeedbackModel { var f FeedbackModel err := json.Unmarshal([]byte(dict), &f) if err != nil { return nil } return &f}
以上代碼會用到庫"encoding/json"。當收到feedback的json串的時候就用這個方法將其轉換為FeedbackModel對象。 上文說過,會給這個Model增加資料庫操作的方法。那麼我們就可以通過json串得到的Model來執行資料的操作。 代碼:
func (m *FeedbackModel) ModelList(conditions map[string]string) []FeedbackModel { confInstance := conf.ConfigInstance() mgoUri := conf.ConfigInstance().MongoDBConnectionString() // var f FeedbackModel session, err := mgo.Dial(mgoUri) if err != nil { fmt.Printf("can not connect to server %v\n", err) panic(err) } defer session.Close() var feedbackModelList []FeedbackModel collection := session.DB(confInstance.DBName).C("feedback") err = collection.Find(conditions).All(&feedbackModelList) return feedbackModelList}func (m *FeedbackModel) GetModel(modelId string) (*FeedbackModel, error) { //此處省略一部分代碼 collection := session.DB(confInstance.DBName).C("feedback") err = collection.Find(bson.M{"feedbackcode": modelId}).One(&f) return &f, nil}func (m *FeedbackModel) InsertModel(model *FeedbackModel) error { //此處省略一部分代碼 f := session.DB(confInstance.DBName).C("feedback") err = f.Insert(model) return err}
這裡需要稍微的提到一部分MongoDB的知識。在MongoDB中,庫還是庫,但是表不叫做表,叫做Collection。 關鍵字不翻譯。翻譯出來的看著太爛,還不能清楚表達意思。每一條記錄也不叫記錄,叫做Document。 一個Document的大小不能超過2M。
在MongoDB裡尋找是這樣的:
db.feedback.find({"feedbackCode": "1"}) //尋找條件就是{"feedbackCode":"1"}) //尋找條件就是
如果在find裡什麼都不寫,那麼就是無條件的全部尋找。插入是這樣的:
db.feedback.insert({"feedbackCode": "2"})
Golang的mgo當然在具體操作資料庫方面和MongoDB本身是不會差太多了,除了這是Go文法的封裝。
collection := session.DB(confInstance.DBName).C("feedback")
這樣就得到了一個Collection。尋找、插入、修改等動作都是在Collection上執行的。所以,尋找就是這樣的:
collection.Find(conditions).All(&feedbackModelList)
尋找到的列表會存放在一個事先聲明好的feedback的Model列表裡。尋找一個可以這樣:
collection.Find(bson.M{"feedbackcode": modelId}).One(&f)
插入:
collection.Insert(model)
insert的時候,這個model需要是指標類型的,也就是:var model *FeedbackModel。
到這裡就可以把整個的feedback從請求到返回的過程都串聯起來了。
全部串聯起來
在整個的app入口處。請求的路徑和具體的Controller處理函數綁定在了一起。每一個請求來了之後 ,如果路徑和綁定的路徑是對應好了的。就會被發送給Controller的某個綁定好的函數裡處理。
這個負責處理http請求的Controller方法會把http請求發送上來的json字串發送給Model模組處理。 Model模組會把json字串轉換為一個Model執行個體。這個Model是一個富Model, 可以調用其函數把Model本身的資料插入到Mongo資料庫中,當然也可以從資料庫中查詢這些資料。
到這裡
到這裡你已經熟悉了如何用Martini這個Golang架構開發一個簡單地網站,好吧,其實是一個REST Api。 使用我們文中提到的Render架構,你可以非常非常容易的把Model資料轉換之後並顯示在html的template中。 而且你已經熟悉了MongoDB資料庫。一個非常簡單易用的資料庫。無論你是要開發一個REST Api還是要開發一個html的網站。 需要儲存的東西都可以存如MongoDB資料庫中。到這裡你已經可以順利的繼續下面的功能的開發了。