# Baa 工程化
所謂工程化,不是baa的功能,而是在使用baa的過程中總結出的一種姿勢,姑且稱之為:最佳實踐。
按照一般MVC的開發規范,一個程序通常會有 數據模型、模板視圖、業務控制,輔助的還要有 前端資源文件,應用配置文件,可能還要記錄日志文件。
## 目錄結構
```
project
|-- conf
|-- app.ini
|-- controller
|-- index.go
|-- article.go
|-- user.go
|-- data
|-- log
|-- model
|-- base
|-- base.go
|-- cache.go
|-- base.go
|-- article.go
|-- user.go
|-- module
|-- util
|-- util.go
|-- template
|-- template.go
|-- public
|-- assets
|-- css
|-- images
|-- js
|-- upload
|-- robots.txt
|-- router
|-- init.go
|-- router.go
|-- template
|-- share
|-- header.html
|-- footer.html
|-- article
|-- index.html
|-- show.html
|-- user
|-- show.html
|-- index.html
|-- main.go
|-- README.md
```
結構說明
| 路徑 | 說明 | 備注 |
|-----------------------------|--------------|--------|
| conf | 配置文件目錄 | -- |
| conf/app.ini | 應用配置文件 | [setting](https://github.com/go-baa/setting) 配置庫要求的配置文件路由
| controller | 業務控制器目錄 | -- |
| controller/*.go | 具體控制器 | 建議每個功能一個控制器文件 |
| data | 數據目錄 | -- |
| data/log | 日志目錄 | 建議路徑,可在配置文件中指定 [log](https://github.com/go-baa/log) 輸出路徑 |
| model | 數據模型目錄 | -- |
| model/base | 數據模型基類 | 提供對于數據庫連接,緩存連接等基礎操作 |
| model/base.go | 模型基類 | 導入 `model/base` 初始化數據庫,是其他模型的基礎 |
| model/article.go | 業務模型 | 具體的業務模型,建議每個表對應一個模型文件,命名和表名一致 |
| module | 擴展功能模塊 | -- |
| module/util | 助手模塊 | 一些常用的功能函數,文件操作,URL操作,加解密等 |
| module/util/util.go | 助手函數 | 常用函數庫 |
| module/template | 模板擴展 | -- |
| module/template/template.go | 模板函數庫 | 結合 [render](https://github.com/go-baa/render) 模板引擎,可以擴展模板函數 |
| public | 靜態資源目錄 | -- |
| public/assets | 前端資源目錄 | -- |
| public/assets/css,images,js | 前端文件目錄 | -- |
| public/uplaod | 上傳文件目錄 | -- |
| public/robots.txt | 靜態文件 | 其他靜態文件,可以放在資源目錄下 |
| router | 路由設定目錄 | -- |
| router/init.go | baa初始化 | 初始化baa,加載中間件,模板組件,緩存組件等 |
| router/router.go | 路由配置 | 獨立了路由配置在一個文件中,結構更清晰 |
| template | 模板目錄 | -- |
| template/share | 共享目錄 | 存儲共享的模板片段 |
| template/article | 業務模板 | 具體的業務模板,建議和控制一一對應,每個控制一個目錄,每個方法一個文件 |
| template/index.html | 首頁模板 | 應用的首頁文件 |
| main.go | 應用入口 | -- |
| README.md | 應用說明 | -- |
完整結構,參見示例 [blog](https://github.com/go-baa/example/tree/master/blog)
## 控制器
控制器中按業務劃分成了不同的文件,不同的操作還應該有不同的方法對應,在實現上有兩種考慮:
- 一個控制器中所有方法都是函數,使用控制器的名字作為函數名前置防止多個控制中的命名沖突。
- 將一個控制器視為一個類,所有方法都是類的方法,雖然Go中沒有明確的類,但也可以實現面向對象編程。
兩種聲音都有支持,你可以根據自己喜歡來做,我們選擇了第二種姿勢,看起來更舒服一些。
最終,一個控制文件可能是這樣的:
```
// api/controller/index.go
package controller
import (
"github.com/go-baa/example/api/model"
"github.com/go-baa/log"
"gopkg.in/baa.v1"
)
type index struct{}
// IndexController ...
var IndexController = index{}
// Index list articles
func (index) Index(c *baa.Context) {
page := c.ParamInt("page")
pagesize := 10
rows, total, err := model.ArticleModel.Search(page, pagesize)
if err != nil {
output(c, 1, err.Error(), nil)
return
}
log.Debugf("rows: %#v, total: %d\n", rows, total)
output(c, 0, "", map[string]interface{}{
"total": total,
"items": rows,
})
}
....
```
> 該文件來自示例程序 [api](http://github.com/go-baa/example/tree/master/api)
為了實現面向對象,創建了一個空的結構體作為方法的承載,所有方法都注冊給這個結構體。
**需要解釋的一句是,為什么還要聲明一個 `IndexController` 呢?**
路由注冊時需要將每一個URL對應到具體的方法上來,結構體的方法是不能直接用的,需要先聲明一個結構體實例才能使用。
在哪兒聲明呢?一個是路由注冊的時候,一個是控制器定義的時候,我們選擇了在控制器定義的時候聲明,作為控制器開發的一個規范,路由定義時引入包就可以用了。
## 數據模型
baa本身不提供數據模型的處理,在 [api](http://github.com/go-baa/example/tree/master/api) 示例中使用的是 [grom](http://jinzhu.me/gorm/) 來操作MySQL。
[xorm](http://xorm.io/) 和 [grom](http://jinzhu.me/gorm/) 有什么區別呢?論功能 `xorm` 可能更強大一些,我們覺得 `grom` 使用更舒服一些。
雖然他們都做了很好的封裝,但一個項目畢竟還要配置數據庫信息,數據庫連接,還要各種包調用,顯然我們還是要做個簡單的封裝才好。
具體的代碼不列出,請參考 [api/model](http://github.com/go-baa/example/tree/master/api/model) 中的base處理。
在這個基礎上,一個數據模型可能長這個樣子:
```
// api/model/user.go
package model
// User user data scheme
type User struct {
ID int `json:"id" gorm:"primary_key; type:int(10) UNSIGNED NOT NULL AUTO_INCREMENT;"`
Name string `json:"name" gorm:"type:varchar(50) NOT NULL DEFAULT '';"`
Email string `json:"email" gorm:"type:varchar(100) NOT NULL DEFAULT '';"`
}
type userModel struct{}
// UserModel single model instance
var UserModel = new(userModel)
// Get find a user info
func (t *userModel) Get(id int) (*User, error) {
row := new(User)
err := db.Where("id = ?", id).First(row).Error
return row, err
}
// Create create a user
func (t *userModel) Create(name, email string) (int, error) {
row := new(User)
row.Name = name
row.Email = email
err := db.Create(row).Error
if err != nil {
return 0, err
}
return row.ID, nil
}
```
> 該文件來自示例程序 [api](http://github.com/go-baa/example/tree/master/api)
基本思想和控制器是一樣的,先按照表結構聲明一個結構體。然后創建一個空結構體將模型的方法進行封裝,最后聲明了一個 `UserModel`使得在控制器中無需聲明就可以直接使用模型。
**需要注意的是,在模型中每個方法的最后一個參數一定是`error`,表示操作是否出錯,不要問為什么,規范,還是規范,這里講的都是規范。**
## 配置文件
應用配置文件,只能是 `conf/app.ini`,這個由項目 [setting](https://github.com/go-baa/setting) 決定,為什么把路徑寫死了呢,為了省事,無論在哪兒引入包就能用,無需配置和傳遞。
更多的配置文件也建議放在 `conf` 目錄中,自己去讀取。
配置示例:
```
// conf/app.ini
[default]
# app
app.name = baaBlog
app.version = 0.1
app.url = ""
debug = false
# http
http.address = 0.0.0.0
http.port = 80
http.access_open = off
# output log to os.Stderr
log.file = os.Stderr
# 0 off, 1 fatal, 2 panic, 5 error, 6 warn, 10 info, 11 debug
log.level = 11
# development mode overwrite default config
[development]
debug = true
# production mode overwrite default config
[production]
debug = false
```
> 再次說明,這個配置文件依賴 [setting](https://github.com/go-baa/setting) 項目。
## 模板
模板最簡單,按著結構放就好了。
模板的初始化和使用,參考:
* [模板渲染](https://github.com/go-baa/doc/tree/master/zh-CN/context.md#模板渲染)
* [模板語法](https://github.com/go-baa/doc/tree/master/zh-CN/context.md#模板語法)
* [模板接口](https://github.com/go-baa/doc/tree/master/zh-CN/context.md#模板接口)
* [render](https://github.com/go-baa/doc/tree/master/zh-CN/component/render.md)
* [pongo2](https://github.com/go-baa/doc/tree/master/zh-CN/component/pongo2.md)
項目示例,參考:
* [blog](https://github.com/go-baa/example/tree/master/blog)
## 靜態資源
如果是一個API項目,可能沒有靜態資源,忽略就行。
一般的靜態資源,放在那里就好了,然后注冊靜態資源目錄:
```
// router/router.go
app.Static("/assets", "public/assets", false, nil)
app.StaticFile("/robots.txt", "public/robots.txt")
```
如果你的項目采用了前端構建的姿勢,那么你就構建吧,和baa也沒什么關系,也不影響,
就是建議把構建后的資源放置到 `public`下面,比如:`public/assets/build` 然后注冊靜態目錄,開發過程中的文件不建議放在 `public`下,因為是不可訪問資源。
## 打包發布
Go程序的一個好處就是,`go build`然后生成一個二進制文件,Copy到服務上就行了。
不過需要注意的是,按照以上介紹的姿勢,你還要帶上 `配置文件`,`模板`,`靜態資源`,最后運行的目錄應該是這樣的:
```
project
|-- conf
|-- app.ini
|-- public
|-- assets
|-- build
|-- css
|-- images
|-- js
|-- robots.txt
|-- template
|- share
|-- article
|-- show.html
|-- index.html
|-- project // 二進制文件
```
至于你的發布姿勢,是什么發布系統都沒關系,要注意,打包的環境和運行的系統環境要一致,mac下編譯出來的,linux可不一定能運行。
> PS:我們發布時采用 gitlab + jenkins 構建 Docker鏡像的方式。
### 運行
**運行?**
就是 `./project` 就可以了。哦,別忘了設置環境變量:
```
BAA_ENV=production
```
**優雅重啟?**
Go1.8就要提供了,之前無論用什么方案都不可避免的要損失一些正在執行的連接。
我們目前采用的是多機部署,上線時,分批進行,保證服務有損但不下線。
### 依賴管理
依賴管理的工具有很多,我們目前使用的是 [godep](https://github.com/tools/godep),我們將產生的`Godeps`目錄上傳到了git中,確保構建時的環境一致。