——–翻译分隔线——–
在 Go 应用中使用简明架构(5)
基础层
就像上面提到的,我们的存储认为“数据库”是一个可以用 SQL 请求发送或接收数据行的抽象。它们不关心基础构建的问题,例如链接到数据库,或使用哪个数据库。这是在 src/infrastructure/sqlitehandler.go 中完成的,高层次的 DbHandler 接口是通过调用低层次的功能来实现的:
package infrastructure import ( "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 = rows return 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 = conn return sqliteHandler }
(再次强调,没有错误处理或其他什么东西,这是为了让那些对架构没有贡献的代码不要干扰思路。)
使用 Yasuhiro Matsumoto 的 sqlite3 库,这个基础代码实现了 DbHandler 接口,这让存储可以在不知道底层细节的情况下与数据库通信。
将所有组合到一起
就这样,关于架构的所有建筑模块已经准备好了——让我们将他们在 main.go 中组合到一起:
package main import ( "usecases" "interfaces" "infrastructure" "net/http" ) func main() { dbHandler := infrastructure.NewSqliteHandler("/var/tmp/production.sqlite") handlers := make(map[string] interfaces.DbHandler) handlers["DbUserRepo"] = dbHandler handlers["DbCustomerRepo"] = dbHandler handlers["DbItemRepo"] = dbHandler handlers["DbOrderRepo"] = dbHandler orderInteractor := new(usecases.OrderInteractor) orderInteractor.UserRepository = interfaces.NewDbUserRepo(handlers) orderInteractor.ItemRepository = interfaces.NewDbItemRepo(handlers) orderInteractor.OrderRepository = interfaces.NewDbOrderRepo(handlers) webserviceHandler := interfaces.WebserviceHandler{} webserviceHandler.OrderInteractor = orderInteractor http.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 提供了极有帮助的批评和指导。
——–翻译分隔线——–
总算是搞掂了……到底是拖到了年后……
有疑问加站长微信联系(非本文作者)