Go 项目结构:美化你的 Golang 项目
美化你的 Golang 项目
伴随着你的代码一同构建你的 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 包含你新创建文件的错误。详情参见这里 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
我也鼓励您添加测试文件。同样,任何设计都不像圣经,我们必须根据我们的项目定义,业务逻辑等来实施它。
结论
您可以通过下面的链接了解有关此主题的更多信息。
- https://8thlight.com/blog/uncle-bob/2012/0...
- http://blog.ralch.com/tutorial/design-patt...
- https://hackernoon.com/golang-clean-archit...
- 您可以观看bob叔叔的视频,很有趣。
Bilibili 国内搬运视频地址:https://www.bilibili.com/video/av68574027?...
最后,如果您发现我的任何错误,可以在下面的评论部分给我开枪。您也可以在我的 GitHub 仓库上向我发送PR。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。