Golang - DTOs, Entities and Mapping

前端 未结 2 1449
时光取名叫无心
时光取名叫无心 2021-02-04 04:35

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

Say I am building a REST API that will sit on top o

2条回答
  •  逝去的感伤
    2021-02-04 05:23

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

    • HTTP handler
    • some sort of business logic/use case
    • persistent storage/database interface

    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.

    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.

    HTTP handler

    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)
    }
    

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

    Use case / business logic

    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
    }   
    

    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")
    }
    

    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.

提交回复
热议问题