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

原文在此,续……

——–翻译分隔线——–

在 Go 应用中使用简明架构(5)

基础层

就像上面提到的,我们的存储认为“数据库”是一个可以用 SQL 请求发送或接收数据行的抽象。它们不关心基础构建的问题,例如链接到数据库,或使用哪个数据库。这是在 src/infrastructure/sqlitehandler.go 中完成的,高层次的 DbHandler 接口是通过调用低层次的功能来实现的:

<br />
package infrastructure</p>
<p>import (<br />
	&quot;database/sql&quot;<br />
	&quot;fmt&quot;<br />
	_ &quot;github.com/mattn/go-sqlite3&quot;<br />
	&quot;interfaces&quot;<br />
)</p>
<p>type SqliteHandler struct {<br />
	Conn *sql.DB<br />
}</p>
<p>func (handler *SqliteHandler) Execute(statement string) {<br />
	handler.Conn.Exec(statement)<br />
}</p>
<p>func (handler *SqliteHandler) Query(statement string) interfaces.Row {<br />
	//fmt.Println(statement)<br />
	rows, err := handler.Conn.Query(statement)<br />
	if err != nil {<br />
		fmt.Println(err)<br />
		return new(SqliteRow)<br />
	}<br />
	row := new(SqliteRow)<br />
	row.Rows = rows<br />
	return row<br />
}</p>
<p>type SqliteRow struct {<br />
	Rows *sql.Rows<br />
}</p>
<p>func (r SqliteRow) Scan(dest ...interface{}) {<br />
	r.Rows.Scan(dest...)<br />
}</p>
<p>func (r SqliteRow) Next() bool {<br />
	return r.Rows.Next()<br />
}</p>
<p>func NewSqliteHandler(dbfileName string) *SqliteHandler {<br />
	conn, _ := sql.Open(&quot;sqlite3&quot;, dbfileName)<br />
	sqliteHandler := new(SqliteHandler)<br />
	sqliteHandler.Conn = conn<br />
	return sqliteHandler<br />
}<br />

(再次强调,没有错误处理或其他什么东西,这是为了让那些对架构没有贡献的代码不要干扰思路。)

使用 Yasuhiro Matsumoto 的 sqlite3 库,这个基础代码实现了 DbHandler 接口,这让存储可以在不知道底层细节的情况下与数据库通信。

将所有组合到一起

就这样,关于架构的所有建筑模块已经准备好了——让我们将他们在 main.go 中组合到一起:

<br />
package main</p>
<p>import (<br />
	&quot;usecases&quot;<br />
	&quot;interfaces&quot;<br />
	&quot;infrastructure&quot;<br />
	&quot;net/http&quot;<br />
)</p>
<p>func main() {<br />
	dbHandler := infrastructure.NewSqliteHandler(&quot;/var/tmp/production.sqlite&quot;)</p>
<p>	handlers := make(map[string] interfaces.DbHandler)<br />
	handlers[&quot;DbUserRepo&quot;] = dbHandler<br />
	handlers[&quot;DbCustomerRepo&quot;] = dbHandler<br />
	handlers[&quot;DbItemRepo&quot;] = dbHandler<br />
	handlers[&quot;DbOrderRepo&quot;] = dbHandler</p>
<p>	orderInteractor := new(usecases.OrderInteractor)<br />
	orderInteractor.UserRepository = interfaces.NewDbUserRepo(handlers)<br />
	orderInteractor.ItemRepository = interfaces.NewDbItemRepo(handlers)<br />
	orderInteractor.OrderRepository = interfaces.NewDbOrderRepo(handlers)</p>
<p>	webserviceHandler := interfaces.WebserviceHandler{}<br />
	webserviceHandler.OrderInteractor = orderInteractor</p>
<p>	http.HandleFunc(&quot;/orders&quot;, func(res http.ResponseWriter, req *http.Request) {<br />
		webserviceHandler.ShowOrder(res, req)<br />
	})<br />
	http.ListenAndServe(&quot;:8080&quot;, nil)<br />
}<br />

鉴于非常极端的依赖注入的使用,所以非常有必要在运行应用模块之前进行一些构建工作。DbHandler 的实现会注入到存储层,另一方面,存储层也被注入到用例层中。orderInteractor 注入到路由 webserviceHandler 中。最后,启动 HTTP 服务器。

盒子在盒子在盒子里面,每个独立的部件都可以被替换为底层工作原理完全不同的其他东西——只要有相同的 API,就可以工作。

可以用下面的 SQL 在 /var/tmp/production.sqlite 中创建一个最小的数据集:

<br />
CREATE TABLE users (id INTEGER, customer_id INTEGER, is_admin VARCHAR(3));<br />
CREATE TABLE customers (id INTEGER, name VARCHAR(42));<br />
CREATE TABLE orders (id INTEGER, customer_id INTEGER);<br />
CREATE TABLE items (id INTEGER, name VARCHAR(42), value FLOAT, available VARCHAR(3));<br />
CREATE TABLE items2orders (item_id INTEGER, order_id INTEGER);</p>
<p>INSERT INTO users (id, customer_id, is_admin) VALUES (40, 50, &quot;yes&quot;);<br />
INSERT INTO customers (id, name) VALUES (50, &quot;John Doe&quot;);<br />
INSERT INTO orders (id, customer_id) VALUES (60, 50);<br />
INSERT INTO items (id, name, value, available) VALUES (101, &quot;Soap&quot;, 4.99, &quot;yes&quot;);<br />
INSERT INTO items (id, name, value, available) VALUES (102, &quot;Fork&quot;, 2.99, &quot;yes&quot;);<br />
INSERT INTO items (id, name, value, available) VALUES (103, &quot;Bottle&quot;, 6.99, &quot;no&quot;);<br />
INSERT INTO items (id, name, value, available) VALUES (104, &quot;Chair&quot;, 43.00, &quot;yes&quot;);</p>
<p>INSERT INTO items2orders (item_id, order_id) VALUES (101, 60);<br />
INSERT INTO items2orders (item_id, order_id) VALUES (104, 60);<br />

现在可以运行这个应用,然后在浏览器中访问 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 提供了极有帮助的批评和指导。

——–翻译分隔线——–

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

One thought on “[翻译]在 Go 应用中使用简明架构(5)”

Leave a Reply

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