go 项目结构:美化你的 golang 项目 | go优质外文翻译 | go 技术论坛-380玩彩网官网入口

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

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

如果你不熟悉我之前的文章, 你可以通过以下链接了解功能。 乍一看不难,如果你之前已经了解关于构建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 包含你新创建文件的错误。详情参见这里 .

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 而不是控制器,因为380玩彩网官网入口的服务只有一个具有 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文件中提供的环境变量中的数据库凭证。我们还将所有路线保留在此处,但您可以将其隔离。

示例项目

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


我也鼓励您添加测试文件。同样,任何设计都不像圣经,我们必须根据我们的项目定义,业务逻辑等来实施它。

结论

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

  • 您可以观看bob叔叔的视频,很有趣。

bilibili 国内搬运视频地址:

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

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

原文地址:

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

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

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