Golang - DTO,实体和地图

时间:2017-05-02 23:24:46

标签: c# go

我是Go的新手来自C#背景,我对如何构建Go应用程序感到很困惑。

假设我正在构建一个位于数据库之上的REST API。还要说,即使在完成之后,考虑到业务的变迁,这个应用程序可能需要经常改变。

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

我应该用Go的结构复制这个结构吗?关于这种方法的一些看起来似乎是错误的,因为结构基本上只是DTO,而且我将有相当多的DTO结构与实体结构相同。我还必须设置逻辑以将实体映射到DTO。这一切都只是感觉非常单一,我在网上看到的许多例子只是序列化数据库结构。

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

如果它有任何区别,我打算使用sqlx将数据库结果编组到结构中,如果我不将实体与DTO分开,那么除了JSON之外,这将意味着更多标签。

2 个答案:

答案 0 :(得分:9)

对于REST API,您通常会处理至少三个不同的实现层:

  • HTTP处理程序
  • 某种业务逻辑/用例
  • 持久存储/数据库接口

您可以单独处理和构建这些内容,这不仅可以解耦它,而且可以使它更加可测试。然后通过注入必要的位将这些部分放在一起,因为它们符合您定义的接口。通常这会导致main或单独的配置机制留下唯一知道 组合并注入如何的地方。

文章Applying The Clean Architecture to Go applications非常清楚地说明了如何分离各个部分。您应该严格遵循这种方法,这取决于项目的复杂程度。

下面是一个非常基本的细分,将处理程序与逻辑和数据库层分开。

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语句。

  

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

答案 1 :(得分:4)

在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