对于REST API,通常将处理至少三个不同的实现层:
- HTTP处理程序
- 某种业务逻辑/用例
- 持久性存储/数据库接口
您可以分别处理和构建每个组件,这不仅可以使它们解耦,而且还使其更具可测试性。这些部分然后通过注入必要的位组合在一起,因为它们符合您定义的接口。通常这最终离开
main或单独配置机制,这是认识的唯一的地方
是什么 被合并并注入 如何 。
文章“ 将Clean Architecture to
Go应用程序应用”很好地说明了如何分离各个部分。您应该严格遵循这种方法的程度在一定程度上取决于项目的复杂性。
下面是一个非常基本的细分,将处理程序与逻辑和数据库层分开。
HTTP处理程序
处理程序只需要将请求值映射到局部变量或可能的自定义数据结构中就可以了。除此之外,它只运行用例逻辑并映射结果,然后再将其写入响应。这也是将不同的错误映射到不同的响应对象的好地方。
type Interactor interface { Bar(foo string) ([]usecases.Bar, error)}type MyHandler struct { Interactor Interactor}func (handler MyHandler) Bar(w http.ResponseWriter, r *http.Request) { foo := r.FormValue("foo") res, _ := handler.Interactor.Bar(foo) // you may want to map/cast res to a different type that is enpred // according to your spec json.NewEnprer(w).Enpre(res)}单元测试是测试HTTP响应是否包含针对不同结果和错误的正确数据的好方法。
用例/业务逻辑
由于只是将存储库指定为接口,因此很容易为业务逻辑创建单元测试,并通过模拟存储库实现返回的不同结果返回结果,该结果也符合
DataRepository。
type DataRepository interface { Find(f string) (Bar, error)}type Bar struct { Identifier string FooBar int}type Interactor struct { DataRepository DataRepository}func (interactor *Interactor) Bar(f string) (Bar, error) { b := interactor.DataRepository.Find(f) // ... custom logic return b}数据库界面
与数据库对话的部分实现了
DataRepository接口,但在其他方面完全独立于如何将数据转换为预期的类型。
type Repo { db sql.DB}func NewDatabaseRepo(db sql.DB) *Repo { // config if necessary... return &Repo{db: db}}func (r Repo)Find(f string) (usecases.Bar, error) { rows, err := db.Query("SELECt id, foo_bar FROM bar WHERe foo=?", f) if err != nil { log.Fatal(err) } defer rows.Close() for rows.Next() { var id string, fooBar int if err := rows.Scan(&id, &fooBar); err != nil { log.Fatal(err) } // map row value to desired structure return usecases.Bar{Identifier: id, FooBar: fooBar} } return errors.New("not found")}同样,这允许单独测试数据库操作,而无需任何模拟SQL语句。
注意: 上面的代码非常伪,而且不完整。



