我是来自C#背景的Go的新手,我对如何构建Go应用程序感到很困惑.
假设我正在构建一个将位于数据库之上的REST API.还要说,即使在完成之后,由于业务的变迁等原因,这个应用程序可能需要经常更改.
在使用Entity Framework和DTO等工具的C#中,我通过从控制器给出的结果中抽象数据库来稍微缓解这个问题.如果我更改数据库中一堆字段的名称,我可能不得不更改我的数据库访问逻辑,但希望我使用AutoMapper映射到我的实体的DTO可以保持不变,所以我不会破坏依赖于它的前端功能给定的DTO结构.
我应该用Go的结构复制这个结构吗?关于这种方法的一些看起来似乎是错误的,因为结构基本上只是DTO,而且我将有相当多的DTO结构与实体结构相同.我还必须设置逻辑以将实体映射到DTO.这一切都只是在某种程度上感觉非常单一,我在网上看到的许多例子都是序列化数据库结构.
简而言之,人们如何避免他们的API与Go中的数据库之间的过度耦合?他们如何广泛地分离出应用程序的不同部分?
如果它有任何区别,我打算用来sqlx
将数据库结果编组到结构体中,如果我不从DTO中分离实体,那么除了JSON之外,这将意味着更多标签.
对于REST API,您通常会处理至少三个不同的实现层:
您可以单独处理和构建这些内容,这不仅可以解耦它,而且可以使它更加可测试.然后通过注入必要的位将这些部分放在一起,因为它们符合您定义的接口.通常这最终会留下main
或单独的配置机制,这是唯一知道组合和注入方式的地方.
应用清洁架构到Go应用程序的文章很好地说明了如何分离各个部分.您应该严格遵循这种方法,这取决于项目的复杂程度.
下面是一个非常基本的细分,将处理程序与逻辑和数据库层分开.
处理程序除了将请求值映射到局部变量或可能的自定义数据结构(如果需要)之外别无其他.除此之外,它只运行用例逻辑并在将结果写入响应之前映射结果.这也是将不同错误映射到不同响应对象的好地方.
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 encoded
// according to your spec
json.NewEncoder(w).Encode(res)
}
Run Code Online (Sandbox Code Playgroud)
单元测试是测试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
}
Run Code Online (Sandbox Code Playgroud)
与数据库通信的部分实现了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")
}
Run Code Online (Sandbox Code Playgroud)
同样,这允许单独测试数据库操作,而无需任何模拟SQL语句.
注意:上面的代码是非常伪代码和不完整的.
小智 5
在用 Go 开发自己的应用程序之前,我有 .NET MVC 经验。我确实错过了 .NET 中 BLL 和 DTO 之间的映射器,但是随着我在 Go 中编写更多代码,我已经习惯了 Go 中没有多少免费午餐的事实。
Go 中几乎没有框架、NHibernate、Entity 和 URI 和视图之间的自动映射,这表明您可能需要做很多其他框架来处理的工作。虽然在某些人看来这可能效率低下,但它无疑是学习以及以效率较低的方式构建高度可定制的应用程序的绝佳机会。当然,您必须编写自己的 SQL 来执行 CRUD 操作、读取查询返回的行、将 sql.Rows 映射到模型、应用一些业务逻辑、保存更改、将模型映射到 DTO,并将响应发送回客户端.
关于DTO和模型的区别:DTO是模型对视图的表示,没有行为(方法)。模型是业务逻辑的抽象,有很多复杂的行为。我永远不会在 DTO 对象上实现诸如“checkProvisioning()”或“applyCouponCode()”之类的方法,因为它们是业务逻辑。
至于用你的模型映射数据库,我不会走任何捷径,因为此时可用的大多数 ORM 都非常原始。如果我必须用 ORM 构建我的数据库,我什至不会尝试使用外键。最好从 SQL 脚本构建数据库,因为您还可以配置索引、触发器和其他功能。是的,如果架构更改,您将不得不更新相当多的代码以反映该更改,但大多数情况下它只会影响数据访问层代码。
我有一个项目使用 C# 的 MVC 设计,但完全是用 Go 编写的,我敢打赌它比您可以从已出版的书籍中找到的任何玩具项目都复杂。如果阅读代码可以帮助您更好地理解结构,那就去吧:https : //github.com/yubing24/das