Go 项目结构:美化你的 Golang 项目

美化你的 Golang 项目

伴随着你的代码一同构建你的 golang 项目

我最近的文章中, 我创建了一个无组织的服务,它只针对初学者。我的动机是提供一个创建 REST 服务的基本概念,并且它能够一步一步的迭代前进。

Go

如果你不熟悉我之前的文章, 你可以通过以下链接了解功能。 乍一看不难,如果你之前已经了解关于构建web API服务的知识那么你可以进行以下内容的学习。

https://itnext.io/building-restful-web-api...

在本教程中,我将告诉你如何构建一个项目。组织的代码很重要是因为当您希望更改以前代码的某些逻辑时,那么结构良好的项目可以很容易地调整这些变化并节省时间. 尽管在项目的结构上会有种种矛盾之处,但我更喜欢4层的结构。您可以从这里替换或修改任何内容。

project/
    model/
    repository/
    handler/
    driver/
    main.go

models

这一层将会存放 model 的 struct 并且它能够被其他层使用到。现在我们把 post(文章)model struct 移动到 models/post.go 文件。如果我们有另一个 model 像 author(作者) 那么这些模型也会存放到这一层。我们也会增加 error.go 文件。

error.go 包含你新创建文件的错误。详情参见这里 here.

repository

repository 目录负责与数据库有关的工作,例如查询,插入/存储或删除。
此处未实现任何业务逻辑。

首先, 我在 repository 目录下创建了 repository.go 文件 。此文件用于保存您所有与 repository 相关的接口. 如果我们为 author 实例或其他内容设置了其他域,则此处包含接口中的所有 author 方法。

如果您注意到上图的左侧, 我已经创建了一个名叫 post 的文件夹。我们将在post / post_mysql.go文件中实现我们的接口。 为什么后缀为MySQL?如果我们使用另一个数据库,例如 mongo 或 redis ,那么我们将创建 post_mongo.go(无论您叫什么),并实现与 mongo 相关的查询。

package post

import (
    "context"
    "database/sql"

    models "github.com/s1s1ty/go-mysql-crud/models"
    pRepo "github.com/s1s1ty/go-mysql-crud/repository"
)

// NewSQLPostRepo retunrs implement of post repository interface
func NewSQLPostRepo(Conn *sql.DB) pRepo.PostRepo {
    return &mysqlPostRepo{
        Conn: Conn,
    }
}

type mysqlPostRepo struct {
    Conn *sql.DB
}

func (m *mysqlPostRepo) fetch(ctx context.Context, query string, args ...interface{}) ([]*models.Post, error) {
    rows, err := m.Conn.QueryContext(ctx, query, args...)
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    payload := make([]*models.Post, 0)
    for rows.Next() {
        data := new(models.Post)

        err := rows.Scan(
            &data.ID,
            &data.Title,
            &data.Content,
        )
        if err != nil {
            return nil, err
        }
        payload = append(payload, data)
    }
    return payload, nil
}

func (m *mysqlPostRepo) Fetch(ctx context.Context, num int64) ([]*models.Post, error) {
    query := "Select id, title, content From posts limit ?"

    return m.fetch(ctx, query, num)
}

func (m *mysqlPostRepo) GetByID(ctx context.Context, id int64) (*models.Post, error) {
    query := "Select id, title, content From posts where id=?"

    rows, err := m.fetch(ctx, query, id)
    if err != nil {
        return nil, err
    }

    payload := &models.Post{}
    if len(rows) > 0 {
        payload = rows[0]
    } else {
        return nil, models.ErrNotFound
    }

    return payload, nil
}

func (m *mysqlPostRepo) Create(ctx context.Context, p *models.Post) (int64, error) {
    query := "Insert posts SET title=?, content=?"

    stmt, err := m.Conn.PrepareContext(ctx, query)
    if err != nil {
        return -1, err
    }

    res, err := stmt.ExecContext(ctx, p.Title, p.Content)
    defer stmt.Close()

    if err != nil {
        return -1, err
    }

    return res.LastInsertId()
}

func (m *mysqlPostRepo) Update(ctx context.Context, p *models.Post) (*models.Post, error) {
    query := "Update posts set title=?, content=? where id=?"

    stmt, err := m.Conn.PrepareContext(ctx, query)
    if err != nil {
        return nil, err
    }
    _, err = stmt.ExecContext(
        ctx,
        p.Title,
        p.Content,
        p.ID,
    )
    if err != nil {
        return nil, err
    }
    defer stmt.Close()

    return p, nil
}

func (m *mysqlPostRepo) Delete(ctx context.Context, id int64) (bool, error) {
    query := "Delete From posts Where id=?"

    stmt, err := m.Conn.PrepareContext(ctx, query)
    if err != nil {
        return false, err
    }
    _, err = stmt.ExecContext(ctx, id)
    if err != nil {
        return false, err
    }
    return true, nil
}

post_mysql.go必须满足PostRepo接口。如果要添加任何其他方法,则必须将其包括在接口中,并在post_mysql.go文件上实现。

处理程序

基本上,处理程序文件夹可以委托给存储库,因为这一层决定了将使用哪个存储库层。任何过程都在这里处理。该层接受请求,调用存储库层并满足业务流程并发送响应。

package handler

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"

    "github.com/go-chi/chi"
    "github.com/s1s1ty/go-mysql-crud/driver"
    models "github.com/s1s1ty/go-mysql-crud/models"
    repository "github.com/s1s1ty/go-mysql-crud/repository"
    post "github.com/s1s1ty/go-mysql-crud/repository/post"
)

// NewPostHandler ...
func NewPostHandler(db *driver.DB) *Post {
    return &Post{
        repo: post.NewSQLPostRepo(db.SQL),
    }
}

// Post ...
type Post struct {
    repo repository.PostRepo
}

// Fetch all post data
func (p *Post) Fetch(w http.ResponseWriter, r *http.Request) {
    payload, _ := p.repo.Fetch(r.Context(), 5)

    respondwithJSON(w, http.StatusOK, payload)
}

// Create a new post
func (p *Post) Create(w http.ResponseWriter, r *http.Request) {
    post := models.Post{}
    json.NewDecoder(r.Body).Decode(&post)

    newID, err := p.repo.Create(r.Context(), &post)
    fmt.Println(newID)
    if err != nil {
        respondWithError(w, http.StatusInternalServerError, "Server Error")
    }

    respondwithJSON(w, http.StatusCreated, map[string]string{"message": "Successfully Created"})
}

// Update a post by id
func (p *Post) Update(w http.ResponseWriter, r *http.Request) {
    id, _ := strconv.Atoi(chi.URLParam(r, "id"))
    data := models.Post{ID: int64(id)}
    json.NewDecoder(r.Body).Decode(&data)
    payload, err := p.repo.Update(r.Context(), &data)

    if err != nil {
        respondWithError(w, http.StatusInternalServerError, "Server Error")
    }

    respondwithJSON(w, http.StatusOK, payload)
}

// GetByID returns a post details
func (p *Post) GetByID(w http.ResponseWriter, r *http.Request) {
    id, _ := strconv.Atoi(chi.URLParam(r, "id"))
    payload, err := p.repo.GetByID(r.Context(), int64(id))

    if err != nil {
        respondWithError(w, http.StatusNoContent, "Content not found")
    }

    respondwithJSON(w, http.StatusOK, payload)
}

// Delete a post
func (p *Post) Delete(w http.ResponseWriter, r *http.Request) {
    id, _ := strconv.Atoi(chi.URLParam(r, "id"))
    _, err := p.repo.Delete(r.Context(), int64(id))

    if err != nil {
        respondWithError(w, http.StatusInternalServerError, "Server Error")
    }

    respondwithJSON(w, http.StatusMovedPermanently, map[string]string{"message": "Delete Successfully"})
}

// respondwithJSON write json response format
func respondwithJSON(w http.ResponseWriter, code int, payload interface{}) {
    response, _ := json.Marshal(payload)

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(code)
    w.Write(response)
}

// respondwithError return error message
func respondWithError(w http.ResponseWriter, code int, msg string) {
    respondwithJSON(w, code, map[string]string{"message": msg})
}

处理程序层还包含几种协议实现,例如 RPC,REST 等,并与其他文件夹隔离。

驱动

在我们的结构中,驱动程序层由我们所有的数据库连接描述。该层负责与数据库连接,并将连接对象发送到控制器。

package driver

import (
    "database/sql"
    "fmt"

    _ "github.com/go-sql-driver/mysql"
)

// DB ...
type DB struct {
    SQL *sql.DB
    // Mgo *mgo.database
}

// DBConn ...
var dbConn = &DB{}

// ConnectSQL ...
func ConnectSQL(host, port, uname, pass, dbname string) (*DB, error) {

    dbSource := fmt.Sprintf(
        "root:%s@tcp(%s:%s)/%s?charset=utf8",
        pass,
        host,
        port,
        dbname,
    )
    d, err := sql.Open("mysql", dbSource)
    if err != nil {
        panic(err)
    }
    dbConn.SQL = d
    return dbConn, err
}

// ConnectMgo ....
func ConnectMgo(host, port, uname, pass string) error {

    return nil
}

在这里,我们将使用 main.go 而不是控制器,因为我们的服务只有一个具有 CRUD 操作的域。

package main

import (
    "fmt"
    "net/http"
    "os"

    "github.com/go-chi/chi"
    "github.com/go-chi/chi/middleware"
    "github.com/s1s1ty/go-mysql-crud/driver"
    ph "github.com/s1s1ty/go-mysql-crud/handler/http"
)

func main() {
    dbName := os.Getenv("DB_NAME")
    dbPass := os.Getenv("DB_PASS")
    dbHost := os.Getenv("DB_HOST")
    dbPort := os.Getenv("DB_PORT")

    println("this is db", dbName, dbHost, dbPass, dbPort)

    connection, err := driver.ConnectSQL(dbHost, dbPort, "root", dbPass, dbName)
    if err != nil {
        fmt.Println(err)
        os.Exit(-1)
    }

    r := chi.NewRouter()
    r.Use(middleware.Recoverer)
    r.Use(middleware.Logger)

    pHandler := ph.NewPostHandler(connection)
    r.Get("/posts", pHandler.Fetch)
    r.Get("/posts/{id}", pHandler.GetByID)
    r.Post("/posts/create", pHandler.Create)
    r.Put("/posts/update/{id}", pHandler.Update)
    r.Delete("/posts/{id}", pHandler.Delete)

    fmt.Println("Server listen at :8005")
    http.ListenAndServe(":8005", r)
}

从main.go,我们将提供所有数据库凭据并与数据库连接。我们阅读了docker-compose文件中提供的环境变量中的数据库凭证。我们还将所有路线保留在此处,但您可以将其隔离。

示例项目

您可以在此处查看示例项目。 here

https://github.com/s1s1ty/go-mysql-crud
我也鼓励您添加测试文件。同样,任何设计都不像圣经,我们必须根据我们的项目定义,业务逻辑等来实施它。

结论

您可以通过下面的链接了解有关此主题的更多信息。

Bilibili 国内搬运视频地址:https://www.bilibili.com/video/av68574027?...

最后,如果您发现我的任何错误,可以在下面的评论部分给我开枪。您也可以在我的 GitHub 仓库上向我发送PR。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://itnext.io/beautify-your-golang-p...

译文地址:https://learnku.com/go/t/39426

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!