Golang-DTO,实体和映射 [英] Golang - DTOs, Entities and Mapping

查看:157
本文介绍了Golang-DTO,实体和映射的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am new to Go having come from a C# background and I am just plain confused about how to structure a Go application.

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

Say I am building a REST API that will sit on top of a database. Also say that, even after it is complete, this application could need to change somewhat frequently given the vicissitudes of the business etc.

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

In C# with tools like Entity Framework and DTOs I somewhat alleviate this problem by abstracting the database from the results given by the controllers. If I change the name of a bunch of fields in the database, I might have to change my database access logic but hopefully my DTOs that I map to my entities using AutoMapper can remain the same so I don't break frontend functionality that relies on a given DTO structure.

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

Should I replicate this structure with Go's structs? Something about such an approach seems wrong given that structs basically are just DTOs and I will have quite a few DTO structs that are identical to the entity structs. I also have to setup logic to map entities to DTOs. This all just feels very unidiomatic somehow and many of the examples I see on the web just serialise the database structs.

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

In short, how do people avoid excessive coupling between their API and the database in Go and how would they broadly go about separating out the different parts of the application?

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

If it makes any difference, I am planning to use sqlx to marshal database results into structs which will mean more tags in addition to the JSON ones if I don't separate entities from DTOs.

推荐答案

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

In the case of a REST API, you'll typically deal with at least three different implementation layers:

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

您可以分别处理和构建每个组件,这不仅使它解耦,而且还使其更具可测试性.这些部分然后通过注入必要的位组合在一起,因为它们符合您定义的接口.通常,这最终会留下 main 或一个单独的配置机制,这是唯一知道 what 的地方被组合并注入了 how .

You can treat and build each of these separately which does not only decouple it but makes it just a lot more testable, too. These parts then are put together by injecting the necessary bits since they conform to interfaces you define. Usually this ends up leaving the main or a separate configuration mechanism the only place that's aware of what is combined and injected how.

文章应用Clean ArchitectureGo应用程序很好地说明了如何分离各个部分.您应该严格遵循这种方法的程度在一定程度上取决于项目的复杂性.

The article Applying The Clean Architecture to Go applications illustrates very well how the various parts can be separated. How strictly you should follow this approach depends a little on the complexity of your project.

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

Below is a very basic breakdown, separating the handler from logic and database layer.

处理程序除了将请求值映射到局部变量或可能的情况下自定义数据结构之外,什么也不做.除此之外,它只运行用例逻辑并映射结果,然后再将其写入响应.这也是将不同的错误映射到不同的响应对象的好地方.

The handler does nothing else than mapping the request values into local variables or possibly custom data structures if needed. In addition to that it just runs the use case logic and maps the result before writing it to the response. This is also a good place to map different errors to different response objects.

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响应是否包含针对不同结果和错误的正确数据的好方法.

Unit tests are a great way to test that the HTTP response contains the correct data for different results and errors.

由于仅将存储库指定为接口,因此很容易为业务逻辑创建单元测试,并通过模拟存储库实现返回的不同结果也符合 DataRepository .

Since the repository is just specified as an interface it's very easy to create unit tests for the business logic with different results returned by a mock repository implementation that also conforms to 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 接口,但在其他方面完全独立于如何将数据转换为预期的类型.

Database interface

The part talking to the database implements the DataRepository interface but is otherwise totally independent on how it turns the data into the expected types.

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语句的情况下分别测试数据库操作.

Again, this allows testing the database operations separately without the need of any mock SQL statements.

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

Note: The code above is very much pseudo code and incomplete.

这篇关于Golang-DTO,实体和映射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆