[翻译]在 Go 应用中使用简明架构(5)

原文在此,续……

——–翻译分隔线——–

在 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 提供了极有帮助的批评和指导。

——–翻译分隔线——–

总算是搞掂了……到底是拖到了年后……

Join the Conversation

1 Comment

Leave a comment

Your email address will not be published. Required fields are marked *