這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
原文在此,續前……
——–翻譯分隔線——–
在 Go 應用中使用簡明架構(5)
基礎層
就像上面提到的,我們的儲存認為“資料庫”是一個可以用 SQL 請求發送或接收資料行的抽象。它們不關心基礎構建的問題,例如連結到資料庫,或使用哪個資料庫。這是在 src/infrastructure/sqlitehandler.go 中完成的,高層次的 DbHandler 介面是通過調用低層次的功能來實現的:
package infrastructureimport ("database/sql""fmt"_ "github.com/mattn/go-sqlite3""interfaces")type SqliteHandler struct {Conn *sql.DB}func (handler *SqliteHandler) Execute(statement string) {handler.Conn.Exec(statement)}func (handler *SqliteHandler) Query(statement string) interfaces.Row {//fmt.Println(statement)rows, err := handler.Conn.Query(statement)if err != nil {fmt.Println(err)return new(SqliteRow)}row := new(SqliteRow)row.Rows = rowsreturn row}type SqliteRow struct {Rows *sql.Rows}func (r SqliteRow) Scan(dest ...interface{}) {r.Rows.Scan(dest...)}func (r SqliteRow) Next() bool {return r.Rows.Next()}func NewSqliteHandler(dbfileName string) *SqliteHandler {conn, _ := sql.Open("sqlite3", dbfileName)sqliteHandler := new(SqliteHandler)sqliteHandler.Conn = connreturn sqliteHandler}
(再次強調,沒有錯誤處理或其他什麼東西,這是為了讓那些對架構沒有貢獻的代碼不要幹擾思路。)
使用 Yasuhiro Matsumoto 的 sqlite3 庫,這個基礎代碼實現了 DbHandler 介面,這讓儲存可以在不知道底層細節的情況下與資料庫通訊。
將所有組合到一起
就這樣,關於架構的所有建築模組已經準備好了——讓我們將他們在 main.go 中組合到一起:
package mainimport ("usecases""interfaces""infrastructure""net/http")func main() {dbHandler := infrastructure.NewSqliteHandler("/var/tmp/production.sqlite")handlers := make(map[string] interfaces.DbHandler)handlers["DbUserRepo"] = dbHandlerhandlers["DbCustomerRepo"] = dbHandlerhandlers["DbItemRepo"] = dbHandlerhandlers["DbOrderRepo"] = dbHandlerorderInteractor := new(usecases.OrderInteractor)orderInteractor.UserRepository = interfaces.NewDbUserRepo(handlers)orderInteractor.ItemRepository = interfaces.NewDbItemRepo(handlers)orderInteractor.OrderRepository = interfaces.NewDbOrderRepo(handlers)webserviceHandler := interfaces.WebserviceHandler{}webserviceHandler.OrderInteractor = orderInteractorhttp.HandleFunc("/orders", func(res http.ResponseWriter, req *http.Request) {webserviceHandler.ShowOrder(res, req)})http.ListenAndServe(":8080", nil)}
鑒於非常極端的依賴注入的使用,所以非常有必要在運行應用模組之前進行一些構建工作。DbHandler 的實現會注入到儲存層,另一方面,儲存層也被注入到用例層中。orderInteractor 注入到路由 webserviceHandler 中。最後,啟動 HTTP 伺服器。
盒子在盒子在盒子裡面,每個獨立的組件都可以被替換為底層工作原理完全不同的其他東西——只要有相同的 API,就可以工作。
可以用下面的 SQL 在 /var/tmp/production.sqlite 中建立一個最小的資料集:
CREATE TABLE users (id INTEGER, customer_id INTEGER, is_admin VARCHAR(3));CREATE TABLE customers (id INTEGER, name VARCHAR(42));CREATE TABLE orders (id INTEGER, customer_id INTEGER);CREATE TABLE items (id INTEGER, name VARCHAR(42), value FLOAT, available VARCHAR(3));CREATE TABLE items2orders (item_id INTEGER, order_id INTEGER);INSERT INTO users (id, customer_id, is_admin) VALUES (40, 50, "yes");INSERT INTO customers (id, name) VALUES (50, "John Doe");INSERT INTO orders (id, customer_id) VALUES (60, 50);INSERT INTO items (id, name, value, available) VALUES (101, "Soap", 4.99, "yes");INSERT INTO items (id, name, value, available) VALUES (102, "Fork", 2.99, "yes");INSERT INTO items (id, name, value, available) VALUES (103, "Bottle", 6.99, "no");INSERT INTO items (id, name, value, available) VALUES (104, "Chair", 43.00, "yes");INSERT INTO items2orders (item_id, order_id) VALUES (101, 60);INSERT INTO items2orders (item_id, order_id) VALUES (104, 60);
現在可以運行這個應用,然後在瀏覽器中訪問 http://localhost:8080/orders?userId=40&orderId=60。結果應該是:
item id: 101
item name: Soap
item value: 4.990000
item id: 104
item name: Chair
item value: 43.000000
And with this, it’s time to pat ourselves on the shoulder.
反思
這個應用並不是不能進一步改進了。例如,由於所有的儲存都必須是 DbHandler,使用到其他儲存的儲存層現在是無法實現的;或者當決定將產品儲存在 MongoDB 同時將訂單儲存在關聯式資料庫,而 DbOrderRepo 不能用這個方式建立 DbItemRepo;可以建立一個註冊表或依賴注入容器提供所有的儲存,而不是 DbHandler,來解決這個問題。
不過,我們已經建立了一個可以很容易實施這些變化的架構。應用只有特定的部分會需要修改,而不會對用例或領域邏輯帶來破壞的風險。這就是漂亮的簡明架構。
感謝
如果沒有 Bob Martin “大叔”不厭其煩的向我們講授如何進行軟體開發和軟體架構設計,也就不會有這個指南。
來自 golang-nuts 郵件清單的諸位提供了非常有協助的反饋(無特定順序):Gheorghe Postelnicu, Hannes Baldursson, Francesc Campoy Flores, Christoph Hack, Gaurav Garg, Paddy Foran, Sanjay Menakuru, Larry Clapp, Steven Degutis, Sanjay, Jesse McNelis, Mateusz Czapliński, 和 Rob Pike。Jon Jagger 提供了極有協助的批評和指導。
——–翻譯分隔線——–
總算是搞掂了……到底是拖到了年後……