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玩彩网官网入口。
原文地址: