REST API服务器设计的最佳实践

huangapple go评论71阅读模式
英文:

go rest api server design good practice

问题

我是你的中文翻译助手,以下是翻译好的内容:

我刚开始学习golang,想制作一个小的待办事项清单的Web应用程序来提升我的go技能。我想知道如何良好地组织代码的做法。

目前,我为这个项目定义了两个包:ticketserverticket包涉及数据库,server包涉及HTTP处理程序。

我的数据有两种类型:Ticket和Todo。一个Ticket可以包含多个Todo。它们在ticket包中定义。

type Ticket struct {                                                               
    Id          int64      `db:"id" json:"id"`                                     
    Label       string     `db:"label" json:"label"`                               
    Description string     `db:"description" json:"description"`                   
    StartTime   time.Time  `db:"start_time" json:"start_time"`                     
    EndTime     *time.Time `db:"end_time" json:"end_time"`                         
    Priority    bool       `db:"priority" json:"priority"`                         
}                                                                                  
type Todo struct {                                                                 
    Id       int64 `db:"id" json:"id"`                                             
    Item     int64 `db:"item" json:"item"`                                         
    TicketId int64 `db:"ticket_id" json:"ticket_id"`                               
    Active   bool  `db:"active" json:"active"`                                     
}

ticket包中,我还定义了

type AppDB struct {                                                                
    db *sqlx.DB                                                                    
}

这个对*sqlx.DB的封装允许我将所有的数据库访问函数放在ticket包中,例如:

func (adb *AppDB) MustInit() 
func (adb *AppDB) AddTicket(i *Ticket) (int64, error)

server包中,我定义了

type Application struct {                                                          
    db ticket.AppDB                                                                
}

HTTP处理程序函数被定义为Application的方法,例如:

func (app *Application) CreateTicket(w http.ResponseWriter, req *http.Request)

main.go中,我注册了处理函数。

func main() {                                                                      
    app := server.NewApplication()                                                 

    fmt.Println("now listening...")                                                
    router := mux.NewRouter()                                                      
    router.HandleFunc("/", app.Hello).Methods("GET")                               
    router.HandleFunc("/get", app.Get).Methods("GET")                              
    log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), router))                  
}

我有以下问题:

  1. 这种设计是一个好的实践吗?如果不是,正确的方式是什么?
  2. 在Donovan和Kernighan的Go编程书的第194页,他们给出了一个例子,其中HTTP处理程序直接定义为数据库的方法。这样做是否是一个更好的设计?这是有道理的,因为我的server包只适用于ticket中的数据类型。这样写代码也更简洁。另一方面,它将HTTP和数据库混合在一起,我不确定这是否是一个好事情。
  3. 如何处理数据库表的创建?我应该创建另一个main.go,只创建数据库表并将其构建为可执行文件,这样我就可以在服务器上运行它一次吗?
英文:

I am new to golang and would like to make a small web app of to-do list to polish my go skills. I would like to know what is a good practice to organize the code.

Right now, I define two packages for this project: ticket and server. ticket is about database and server is about http handlers.

My data has two types: Ticket and Todo. One Ticket can have multiple Todo in it. They are defined in the ticket package.

type Ticket struct {                                                               
    Id          int64      `db:"id" json:"id"`                                     
    Label       string     `db:"label" json:"label"`                               
    Description string     `db:"description" json:"description"`                   
    StartTime   time.Time  `db:"start_time" json:"start_time"`                     
    EndTime     *time.Time `db:"end_time" json:"end_time"`                         
    Priority    bool       `db:"priority" json:"priority"`                         
}                                                                                  
type Todo struct {                                                                 
    Id       int64 `db:"id" json:"id"`                                             
    Item     int64 `db:"item" json:"item"`                                         
    TicketId int64 `db:"ticket_id" json:"ticket_id"`                               
    Active   bool  `db:"active" json:"active"`                                     
}  

In the ticket package, I also define

type AppDB struct {                                                                
    db *sqlx.DB                                                                    
}   

This wrapping around *sqlx.DB allows me to put all the database access functions inside ticket package, e.g.,

func (adb *AppDB) MustInit() 
func (adb *AppDB) AddTicket(i *Ticket) (int64, error)

In the server package, I define

type Application struct {                                                          
    db ticket.AppDB                                                                
}   

And the http handler functions are defined as methods of Application, e.g.,

func (app *Application) CreateTicket(w http.ResponseWriter, req *http.Request)

In the main.go, I register the handle functions.

func main() {                                                                      
    app := server.NewApplication()                                                 
                                                                                   
    fmt.Println("now listening...")                                                
    router := mux.NewRouter()                                                      
    router.HandleFunc("/", app.Hello).Methods("GET")                               
    router.HandleFunc("/get", app.Get).Methods("GET")                              
    log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), router))                  
}  

I have the following questions:

  1. Is this design a good practice? If not, what is the right way?
  2. In Donovan and Kernighan's Go programming book p.194, they give an example where the http handlers are defined as the database's method directly. Would that be a better design? It makes sense because my server package only works for data types in ticket. It also makes the code a little cleaner to write. On the other hand it mixes http and database, which I am not sure if it is a good thing.
  3. How to deal with database table creation? Shall I create another main.go which only creates the database tables and build it into executable such that I can run it on the server just once?

答案1

得分: 0

  1. 根据你描述的情况,我认为制作两个软件包是一个不好的决定。如果你只是制作一个“小型 Web 应用程序”,那么使用多个软件包只会增加复杂性而没有任何好处。如果你不确定,总是选择更简单的解决方案。

  2. 我认为拥有两个软件包并不会更清晰,也不会更少混淆。你可以将软件包的内容分散在多个不同的文件中,并将函数作为不同结构体的方法。

  3. 这样怎么样?以幂等的方式定义你的数据库初始化(例如,CREATE SCHEMA IF NOT EXISTS,CREATE TABLE IF NOT EXISTS),并在 main.go 的开头,在 db.Ping() 之后立即运行它。大部分情况下只需要几毫秒的时间,而且你可以确保数据库始终具有你期望的结构。

英文:
  1. I think making 2 packages is a bad decision based on what you've described. If you're making a "small web app", there's nothing to gain but extra complexity in having more than one package. If you're not sure, always choose the simpler solution.

  2. I don't think having 2 packages is cleaner or that it mixes less. You can have your package stuff on several different files, and functions as methods of different structs.

  3. How about this? Define your db initialisation in an idempotent way (e.g. CREATE SCHEMA IF NOT EXISTS, CREATE TABLE IF NOT EXISTS) and run it every single time at the beginning of your main.go, right after you db.Ping(). Should be milliseconds most of the time, and you ensure that the db always has the structure you expect.

huangapple
  • 本文由 发表于 2016年12月23日 23:30:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/41304058.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定