一尘不染

Golang-DTO,实体和映射

go

我是Go语言的新手,具有C#背景并且对如何构造Go应用程序感到困惑。

假设我正在构建一个REST API,它将位于数据库之上。还要说,即使完成后,鉴于业务的变迁等,此应用程序可能仍需要频繁更改。

在带有诸如Entity
Framework和DTO之类的工具的C#中,我通过从控制器给出的结果中抽象出数据库来缓解此问题。如果更改数据库中一堆字段的名称,则可能必须更改数据库访问逻辑,但是希望我使用AutoMapper映射到实体的DTO可以保持不变,因此我不会破坏依赖于前端的功能给定的DTO结构。

我应该用Go的结构复制此结构吗?鉴于结构基本上只是DTO,而我将拥有相当多的DTO结构与实体结构相同,因此这种方法似乎有些错误。我还必须设置逻辑以将实体映射到DTO。某种程度上,这一切都感觉很特别,我在网上看到的许多示例都只是序列化了数据库结构。

简而言之,人们如何避免在API和Go中的数据库之间过度耦合,以及他们如何广泛地分离出应用程序的不同部分?

如果有什么不同,我计划用来sqlx将数据库结果编组为结构体,如果我不将实体与DTO分开的话,除了JSON标记外,还将意味着更多标记。


阅读 1557

收藏
2020-07-02

共1个答案

一尘不染

对于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 encoded
    // according to your spec
    json.NewEncoder(w).Encode(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语句。

注意: 上面的代码非常伪,而且不完整。

2020-07-02