英文:
Does golang foment no file structure?
问题
我一直在研究使用Golang构建Web应用程序,我喜欢这门语言和一切,但是我对Golang中的结构概念感到困惑。似乎它几乎强制我不使用文件结构、文件夹、划分和关注点分离。有没有办法以我没有看到的方式组织.go文件?到目前为止,文件结构一直是个头疼的问题,这是我使用这门语言唯一的不好体验。谢谢!
英文:
I've been looking into golang in order to build a web app, I like the language and everything, but I'm having trouble wrapping my head around the concept of structure in golang. It seems it pretty much forces me to have no file structure, no folders, no division, no separation of concerns. Is there any way to organize the .go files in a way that I'm not seeing? So far file structuring has been a headache and it's the only bad experience I've had with the language. Thank you!
答案1
得分: 2
你说得部分正确。Go语言在文件和包结构方面没有强制要求,除了禁止循环依赖。在我看来,这是一件好事,因为你可以自由选择最适合你的方式。
然而,这也让你需要决定什么是最好的方式。我尝试过几种方法,根据我所做的事情(例如库、命令行工具、服务),我认为不同的方法最好。
如果你只是创建命令行工具,让根包(仓库的根目录)为main
。如果它是一个小工具,那就是你所需要的。可能会出现命令行工具变得越来越大的情况,所以你可能想要将一些东西分离出来,它们可以放在同一个仓库中,但不一定非要这样。
如果你正在创建库,做法是一样的,只是包名将是你的库的名称,而不是main
。
如果你需要组合(既可以作为库,又可以作为命令行工具的东西),我建议将库代码(库的所有公共部分)放在版本控制系统的根目录中,可以有潜在的子包和cmd/toolname
用于存放二进制文件。
当涉及到Web服务时,我发现遵循这些准则是最实用的。最好阅读整篇博文,但简而言之 - 在版本控制系统的根目录中定义你的领域,创建cmd/app
(或多个)作为命令行入口点,并为每个依赖项创建一个包(例如memcache、数据库、http等)。你的子包从不直接依赖于彼此,它们只共享根目录中的领域定义。这需要一些时间来适应,我仍在根据我的用例进行调整,但到目前为止看起来很有前途。
英文:
You are partially right. Go does not enforce anything regarding file and package structure, except that it forbids circular dependencies. IMHO, this is a good thing, since you have freedom to choose what best suites you.
However, it puts burden on you to decide what is the best. I have tried few approaches and depending on what I am doing (e.g. library, command line tool, service) I believe different approaches are best.
If you are creating only command line tool, let root package (root of your repository) be main
. If it is small tool, that is all you need. It might happen that you command line tool grows, so you might want to separate some stuff to their own that can, but does not have to be, in same repository.
If you are creating library, do the same, except that package name will be name of your library, not main
.
If you need combination (something that is useful both as the library and command line tool), I would go with putting library code (everything public for the library) in VCS root, with potential sub-packages and cmd/toolname
for your binary.
When it comes to web services, I found it is most practical to follow these guidelines. It is best to read entire blog post, but in short - define your domain in VCS root, create cmd/app
(or multiple) as command line entry point and create one package per dependency (e.g. memcache, database, http, etc). Your sub-packages never depend on each other explicitly, they only share domain definitions from root. It takes some getting used to and I am still adapting it to my use case, but so far it looks promising.
答案2
得分: 1
根据@del-boy的说法,这取决于你想做什么。在开发golang web应用程序时,我多次遇到这个问题,但对我来说更适合的是按依赖关系划分包。
- myproject
-- cmd
--- main.go
-- http
--- http.go
-- postgres
--- postgres.go
-- mongodb
--- mongodb.go
myproject.go
myproject.go将包含包含主要领域或业务模型的接口和结构体
例如,在myproject.go中可以有以下内容
type User struct {
MongoID bson.ObjectId `bson:"_id,omitempty"`
PostgresID string
Username string
}
以及如下的接口
type UserService interface {
GetUser(username string) (*User, error)
}
现在,在你的http包中,你将处理暴露你的API端点
//Handler代表我们应用程序的HTTP API接口。
type Handler struct {
Router *chi.Mux // 你可以使用任何你喜欢的路由器
UserService myproject.UserService
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *Request){
//这只是一个Router ServeHTTP的包装器
h.Router.ServeHTTP(w,r)
}
func (h *Handler) someHandler(w http.ResponseWriter, r *Request){
//从请求中获取用户名
//
user := h.UserService.GetUser(username)
}
在你的postgres.go中,你可以有一个实现了UserService的结构体
type PostgresUserService struct {
DB *sql.DB
}
然后你实现服务
func (s *PostgresUserService) GetUser(username string) {
//实现方法
}
使用mongodb也可以做同样的事情
type MongoUserService struct {
Session *mgo.Session
}
func (s *MongoUserService) GetUser(username string) {
//实现方法
}
现在,在你的cmd/main.go中,你可以有类似以下的内容
func main(){
postgresDB, err := postgres.Connect()
mongoSession, err := mongo.Connect()
postgresService := postgres.PostgresUserService{DB: postgresDB}
mongoService := mongo.MongoUserService{Session: mongoSession}
//然后将你的服务传递给你的http处理程序
//根据底层服务,你的API将根据你传递的底层服务进行操作
myHandler := http.Handler{}
myHandler.UserService = postgresService
}
假设你更改了底层存储,你只需要在这里进行更改,而不会改变任何其他内容。
这个设计受到这篇博客文章的启发,希望对你有帮助。
英文:
As @del-boy said it depends on what you want to do, I went over this problem multiple times but what suited me more when developing a golang web app is to divide your packages by dependencies
- myproject
-- cmd
--- main.go
-- http
--- http.go
-- postgres
--- postgres.go
-- mongodb
--- mongodb.go
myproject.go
myproject.go will contain the Interfaces and Structs that contain the main domain or business models
For example you can have inside myproject.go
type User struct {
MongoID bson.ObjectId `bson:"_id,omitempty"`
PostgresID string
Username string
}
and an Interface like this
type UserService interface {
GetUser(username string) (*User, error)
}
Now in your http package you will handle exposing your api endpoints
//Handler represents an HTTP API interface for our app.
type Handler struct {
Router *chi.Mux // you can use whatever router you like
UserService myproject.UserService
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *Request){
//this just a wrapper for the Router ServeHTTP
h.Router.ServeHTTP(w,r)
}
func (h *Handler) someHandler(w http.ResponseWriter, r *Request){
//get the username from the request
//
user := h.UserService.GetUser(username)
}
in your postgres.go you can have a struct that implements UserService
type PostgresUserService struct {
DB *sql.DB
}
and then you implement the service
func (s *PostgresUserService) GetUser(username string) {
//implement the method
}
and the same thing can be done with mongodb
type MongoUserService struct {
Session *mgo.Session
}
func (s *MongoUserService) GetUser(username string) {
//implement the method
}
Now in your cmd/main.go you can have something like this
func main(){
postgresDB, err := postgres.Connect()
mongoSession, err := mongo.Connect()
postgresService := postgres.PostgresUserService{DB: postgresDB}
mongoService := mongo.MongoUserService{Session: mongoSession}
//then pass your services to your http handler
// based on the underlying service your api will act based on the underlying service you passed
myHandler := http.Handler{}
myHandler.UserService = postgresService
}
assuming you changed your underlying store you only have to change it in here and you will not change anything
This design is heavily inspired from this blog, I hope you find it helpful
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论