天天看點

go| beego 速覽

本來想做一期 beego源碼解讀 的, 不過作為 go 初學者, 代碼量還沒提上來前, 正應當多看多寫, 做源碼解讀實在火候不夠. 這裡綜合自己學習 beego 的一些感受:

融入到自己對 Web 應用架構 開發知識的積累中, 以期可以應用到自己參與的

開源項目 - Swoft

中.

web framework 知識圖譜: http://naotu.baidu.com/file/a045802b858c57c56c73dd2e7bda50b5?token=fccd16c1458c78bd Modern High performance AOP and Coroutine PHP Framework - Swoft: https://github.com/swoft-cloud/swoft

架構

說架構的時, 往往會 面向業務 進行抽象, 是以經常看到的架構, 大抵都是 分層/分子系統, 一個又一個 豆腐塊. web 應用其實相當 成熟 了, 需要哪些功能已經十厘清楚. 這個時候, 也許更應該來關注代碼.

go| beego 速覽

從圖中可以看到, beego 架構很簡單, 一言以蔽之: 元件化(或者說 子產品化). 一個 web 應用, 其實就是 一組元件協同工作 的結果.

Swoft1.0

的一大重要重構, 就是實作元件化. 目前在 PHP 架構中, 元件化實作最好的要算 laravel 和 symfony 了.

PS: Swoft 的現狀是一個元件一個 git 倉庫. 更正規的方式是一個中央倉庫(例如叫 framework), 所有的修改都是 fork/push 到這個倉庫, 每次發版時, 中央倉庫執行一個 build 腳本(使用 [

https://github.com/dflydev/

git-subsplit](

https://github.com/dflydev/git-subsplit))

, 将 framework 下的代碼推到不同的子項目倉庫, 并且給每個元件倉庫打一個統一的 tag. 最終所有的修改都在 framework 倉庫, 元件倉庫隻讀狀态, 不接受 pr/push, 隻接受 build 腳本的 split. 可以參考的 laravel 的 build 腳本:

https://github.com/laravel/framework/blob/5.1/build/illuminate-split-full.sh

元件化後, 可以明顯減少重複造輪子, 更好的代碼複用. 元件化其實很好的回答下面 2 個問題:

  • 依賴如何管理
  • 如何分享代碼

生命周期

快速上手一個架構, 優先關注生命周期, 了解代碼的執行邏輯.

go| beego 速覽

beego 架構生命周期: 啟動應用(app) -> 一次請求進來 -> 路由(route) -> 過濾器(filter, 有的架構使用 Middleware, 作用相同) -> 控制器(Controller) -> 和各子產品互動 -> 傳回(response)

go| beego 速覽

一次請求的生命周期: context(請求上下文) -> 路由(Route, 靜态檔案/動态) -> 路由比對(fixed/regex/auto) -> filter -> Controller(和各子產品互動) -> filter -> 傳回(response)

PS: beego 中 module 意為 元件, 而在 yii 架構中, module 意為 子產品, 包含一個完整的 MVC 應用, 而元件用的單詞為 component. yii 的項目中是可以存在多個 module 的, 即 一個項目多個應用. 可想而知, yii 這樣的功能, 最終會導緻一個 超大規模的項目.

app & context

app 通常作為整個應用的抽象. PHP架構中的常見劃分是 web應用 和 console應用. 不過和傳統 PHP 架構最大的不同是, 傳統 fpm 多程序模式下, 每次請求都需要初始化一次應用. 而在 beego 和 Swoft 下, 應用是常駐記憶體的, 隻需要啟動時初始化一次 app 即可.

這也就造成了很多使用 fpm 的 PHPer 不知道 context 這個概念. context, 請求上下文, 包含一次請求的 request/response. 因為 fpm 是多程序的, 每次請求都是獨立在每個程序中并且重新初始化一次應用. beego 或 Swoft 隻初始化一次 app, 是以需要 context 來作為每次請求的抽象. 這也是為什麼傳統架構中, PHPer 依然可以使用

$_GET/$_POST

等超全局變量, 而在 Swoft 這樣基于 Swoole 的架構中, 必須使用架構提供的方法. 其實質就是對 context 封裝.

app 和 context 的分離, 其實可說是必然, 2 者有着不同的生命周期.

圍繞 web 請求

Model-View-Controller 這樣的架構可以說是 深入人心 了, 以至于不這樣的 web應用, 還會不習慣. 現實中的 web應用, 當然不止 MVC 這三個子產品.

url 的解析

url 的解析, 包含 2 個方向:

  • 從 url 到業務代碼 -> 路由子產品(route)
  • 反向生成 url -> URL build 子產品

路由子產品相對複雜, 功能會多一些:

  • 在形式上, 支援 fixed/regex/auto 方式進行比對, 比對順序 fixed -> regex -> auto
  • 支援 http method, 比如某個請求隻允許 GET 請求
  • 支援 restful
  • namespace, 命名空間, 比如版本控制, 一部分 url 都使用

    v1/

    字首, 另一部分使用

    v2/

    字首
  • 注解(annotation) 和 自動參數比對(auto parameter)

路由注解和自動參數比對

先看代碼:

// @router /tasks/:id [get]
func (c *TaskController) MyMethod(id int, field string) (map[string]interface{}, error) {
    if u, err := getObjectField(id, field); err == nil {
        return u, nil
    } else {
        return nil, context.NotFound
    }
}

// 省去的路由代碼
beego.Router("/task/:id", &TaskController{}, "get:MyMethod")

// 省去的擷取參數(路由參數/請求參數)的代碼
id := c.GetInt("id")
field := c.GetString("field")           

使用注解後, 就省去了在路由中的代碼, 如果全局都使用注解, 就可以省去路由檔案, Swoft 就是如此做的. 至此, 我們可以對比一下路由解析形式的 4 種方式:

  • fixed: 所見即所得, 缺點是 url 太多了就需要寫很多
  • regex: 正則比對, 不常用, 大部分也隻用在路由參數的地方, 比如

    /user/[\d]+/task/[\d]+

  • auto: 自動比對, yii 中就是使用這種方式, 約定 url 優先比對

    Controller + Action

    , laravel 中的也支援 Controller 級别的自動路由
  • annotation: 注解, 既能保持代碼的簡潔, 又可以保留路由子產品的靈活性
是以我理想中的路由形式: 大部分可以直接自動比對, 少部分的特殊需求使用注解, 獨立的路由檔案可有可無.

路由中使用注解, 要遵守 約定大于配置 的思想. 比如上面的示例改成這樣:

// @router /mission/:id [get]
func (c *TaskController) MyMethod(id int, field string) (map[string]interface{}, error) {
    if u, err := getObjectField(id, field); err == nil {
        return u, nil
    } else {
        return nil, context.NotFound
    }
}           

原來我隻需要使用 IDE 的檔案查找功能, 查找類似

TaskController

的檔案, 現在就得先全局搜尋

/mission

, 再根據搜尋結果定位到代碼.

自動參數比對比較适合在參數比較少的時候使用, 這個就看個人編碼習慣了, 加一個參數可以少寫一行代碼, 這個功能還是有必要支援的.

restful

restful 是現在比較流行的一種軟體架構風格/設計風格. 以資源為核心, 配合 http method(GET/POST/PUT/DELETE) 進行資源的管理.

不過現實是大部分情況下, 大家隻用 GET/POST. 面對業務進行設計的時, 也很少會 以資源為核心, 怎麼簡單怎麼來或者說怎麼快怎麼來. 而且要發起 PUT 請求, 你需要這樣的表單:

<form method="post" ...>
  <input type="hidden" name="_method" value="put" />
  ...
</form>           

雖然現在 restful 很流行, 很多架構也都标榜 輕松 就可以實作. 是否需要用, 還是自己判斷.

URL Building

beego 中使用 URL Building 來反向生成 url, 比如:

// route
beego.Router("/api/list", &TestController{}, "*:List")
beego.Router("/person/:last/:first", &TestController{})
beego.AutoRouter(&TestController{})

// URL Building
URLFor("TestController.List") // Output /api/list
URLFor("TestController.Get", ":last", "xie", ":first", "asta") // Output /person/xie/asta
URLFor("TestController.Myext") // Output /Test/Myext
URLFor("TestController.GetUrl") // Output /Test/GetUrl           

其他架構可能會使用

url() route()

, 一個表示 url, 一個表示 route(路由), 不過我傾向于 簡單點, 隻用 url. 雖然有些時候用 route 來表達(比如 restful) 會簡單一些.

其他 web 相關服務一覽

  • Form validation: 參數校驗, 這個子產品主要解決一些常用的參數校驗, 比如 e-mail, 金額等
  • response format: 傳回值格式, 常見的有 html(view) json xml jsonp. Swoft 中推薦 傳回的格式類型, 不應該由服務端指定, 而是根據用戶端請求時的 Header 裡面的 Accept 決定, 控制器中隻用傳回資料, 架構自動進行格式轉換
  • view: 視圖, beego 中包含 2 個功能, 模闆引擎(temple) 和 分頁(pagination). 模闆引擎友善組織前端代碼, 這樣 Controller 中隻要傳回資料即可; 分頁也是常見的功能, yii/laravel 在這方面做得很極緻, 不僅有分頁, 還有其他頁面相關的元件
  • Filters: 過濾器, 其他架構中可能使用 Middleware(中間件), 功能類似, 其實就是将一部分功能從業務中隔離, 比如網站開啟維護狀态, user authentication, log visiting, compatibility switching.
  • Flash Messages: 一次性通知消息, 其實是一次性的 消息 - 訂閱 功能, 比如頁面上通知操作成功
  • Error Handling: 錯誤處理, 在 web 請求中, 通常配合

    http code + redirect + error page

  • Session: session/cookie 其實很簡單, 無論什麼架構, 變來變去就那麼幾個方法, 更重要的是知道 -- 有什麼用; 有哪些方法; 使用不同的驅動

Model

簡單說: 如何和資料打交道. 其實上面的

session / cookie / flash message

等, 都在不同場合下和資料打交道, 這裡講講資料更原始的地方:

  • 和關系型資料庫打交道, 如 mysql
  • 和文檔型資料庫打交道, 如 MongoDB
  • 和鍵值型資料庫打交道, 如 redis, 通常用作緩存, 也可用作 session

主要說一下和關系型資料庫打交道, 通常涉及一下幾個方面:

  • 對資料庫連接配接進行抽象, 這樣可以友善 切換資料庫, 切換主從 等
  • 對資料的抽象, 比如一個 Model 類對一個張資料表, 進而友善的實作 CRUD 功能
  • 對 sql 語句的抽象: raw, 支援原生語句的執行; QuerySeter, 對 sql 原語的抽象, 比如

    > = < like

    等操作符; queryBuilder, 拼接 sql 語句
  • 支援資料庫事務(transaction), 要注意事務的寫法, 如果發生嵌套如何寫
// beego 中的 transaction
o := NewOrm()
err := o.Begin()
// transaction process
...
...
// All the query which are using o Ormer in this process are in the transaction
if SomeError {
    err = o.Rollback()
} else {
    err = o.Commit()
}           
  • 支援表的關聯(relation), 比如在 yii 中, 隻要定義一個方法, 就可以在查詢目前表時, 将關聯表的資料, 自動指派給目前表對象的屬性中
// beego 中的 relation
type Post struct {
    Id    int    `orm:"auto"`
    Title string `orm:"size(100)"`
    User  *User  `orm:"rel(fk)"`
}

var posts []*Post
qs := o.QueryTable("post")
num, err := qs.Filter("User__Name", "slene").All(&posts           

更多 more

想要做好一個架構, 還需要在很多地方下功夫.

配置管理

beego 的配置子產品非常簡單易用:

  • 單層配置, 沒有複雜的嵌套, 如果希望嵌套, 也推薦

    db.user / db.password

    這樣用統一分隔符的寫法
  • 對不同環境支援
  • 通過

    include

    關鍵字, 加載其他配置檔案
  • 不同格式(format)支援: ini / xml / yaml / json

配置的劃分: basic/app/web/http/session/log. 配置的劃分, 在一定程度上提現了架構的設計和子產品的劃分. 一個的好的架構, 隻需要簡單的修改配置, 就可以實作不同的功能. 推薦好好看一下配置.

安全

安全也是架構必須提供的功能之一, 一個原因是很多人可能沒有積累這一塊的知識. 不過安全相關的功能, 通常是散落在架構之中:

  • csrf(xsrf): 非 GET 請求要注意是否開啟 XSRF 防護. 開啟則需要給表單或者 ajax 請求中添加 csrf token
  • sql 注入: 使用參數綁定即可解決, 要注意在寫 原生 sql 時是否可能導緻 sql 注入

還有很業務關聯性比較強的:

  • 圖檔驗證碼
  • 手機驗證碼防刷

其他更多安全的内容, 可以參考 laravel/yii 架構的官方文檔, 平時也要注意積累這方面的知識.

日志

日志也是必備功能, 做好日志, 既可以友善了解系統運作狀态, 也友善在出問題的時候還原問題發生的軌迹.

  • 用法: 日志通常是全局可用, 一般簡單的

    Log::info($message)

    即可
  • 日志級别: log level, 标準給日志劃分了很多級别, 但實際中, 通常開始都使用簡單的劃分, info 記錄資訊, error 需要馬上處理
  • 日志輸入: output, 可以将日志輸出到不同地方

yii 架構中日志功能更加精細, 包含

logger - dispatch - target

三個角色, 同時日志在 level 的基礎上, 還可以細分 category / tag 等. 另外日志可能還需要有的功能:

  • flush, 重新整理, 比如 1000 條後再輸出(落地). 緩沖(buffer) 的思想可以說在系統設計中比比皆是.
  • 切片, 比如說日志按照時間日切, 或者按照大小 10m 一切

其他

  • Live Monitor: 實時監控
  • 熱更新: 一方面是開發過程中, 如果修改後就需要去喝杯咖啡, 效率就可想而知了, 在傳統 fpm 下, 不用擔心這個問題, 但是在應用常駐記憶體情況下, 或者像 go 這樣需要編譯後運作, 就很有必要做好熱更新了; 另一方面是上線, 傳統 fpm 是程序逐漸重新開機, Swoft 基于 Swoole, 也支援這樣的機制 , beego 這部分還在開發中
  • 文檔: 可以編輯(支援 github 更好); 支援上一頁/下一頁; 支援文檔總目錄, 支援目前文檔目錄(TOC); 支援搜尋功能. 當然, 功能之外還需要美觀, beego 目前支援的功能不多, Swoole 和 Swoft 文檔功能支援還不錯
  • api doc: 自動生成 api 文檔, beego 使用的 swagger , 簡單且功能滿足大部分場景
  • 輔助工具(指令行工具): beego 提供了

    bee

    工具, 可以用來快速生成空項目, 生成代碼. 類似工具 laravel/yii 架構都有支援, Swoft 則正在完善這部分
  • i18n: 國際化, 使用和實作上都簡單
  • CI, 目前 github 上的開源項目, 基本都在使用 travis CI, 可以參考項目下的

    .travis.yml

    檔案, 也可以使用 gitlab Jenkins 等開源工具進行內建
  • deploy: 釋出, go 程式複制編譯的可執行檔案即可. 不過推薦還是使用 docker

其他可以做的, 還有很多, 比如:

  • 添加 demo, 學習完文檔後可以練手
  • toolbox / util 等更多輔助功能
  • 第三方(third-part)功能內建