英文:
Clean and generic project structure for GO applications and mongodb
问题
我想使用GO和MongoDB构建一个基于API的应用程序。我来自Asp.net MVC背景。如果我使用MVC Web应用程序的架构,需要考虑以下几点:
-
关注点分离(SoC)
- 数据模型(DataModel)
- 业务实体(BusinessEntities)
- 业务服务(BusinessServices)
- 控制器(Controllers)
-
依赖注入和工作单元(Unity of Work)
-
单元测试
- MoQ或nUnit
-
与UI框架的集成
- AngularJS或其他框架
-
支持RESTful的URL,以实现SEO
下面的架构图可能是基于MVC的应用程序的解决方案:
在网上有很多关于构建基于Asp.Net或Java的应用程序的资源,但我还没有找到关于Golang应用程序架构的解决方案。
是的,GO与C#或Java不同,但仍然有结构体(Structs)、接口(Interfaces)可以创建可重用的代码和通用的应用程序架构。
考虑到上述要点,我们如何在GO应用程序中创建一个清晰、可重用的项目结构,并为数据库(MongoDB)事务创建通用的存储库。任何网络资源也是一个很好的起点。
英文:
I want to build an API based application using GO and MongoDB. I'm from Asp.net MVC background. Probably if I make an architecture with MVC web application things to be consider are
- Separation of concerns(SoC)
- DataModel
- BusinessEntities
- BusinessServices
- Controllers
- Dependeny Injection and Unity of Work
- Unit Testing
- MoQ or nUnit
- Integration with UI frameworks
- Angularjs or others
- RESTful urls that enables SEO
Below architecture could be a solution for my need in MVC based appications
There are resources around the web to build Asp.Net or Java based applications, but I have not find solution to Golang application architecture.
Yes GO is different to C# or Java, but still there are Structs, Interfaces to create reusable code and a generic application architecture.
Consider above points in mind, how we can make a clean and reusable project structure in GO applications and a generic repositories for DB(Mongodb) transactions. Any web resources also a great point to start.
答案1
得分: 12
这取决于你自己的风格和规则,在我们公司,我们以以下方式开发项目:
- 配置由环境变量确定,因此我们有一个
company/envs/project.sh
文件,在服务(图像外的项目)之前必须对其进行评估。 - 我们添加了一个
zscripts
文件夹,其中包含所有额外的脚本,例如添加用户或发布帖子。仅用于调试目的。 - 数据模型(实体)位于名为
project/models
的包中。 - 所有控制器和视图(HTML 模板)被分类为“应用程序”或“模块”。我们使用 REST 路径作为主要分组分隔符,因此路径
/dogs
转到包project/apps/dogs
,/cats
转到project/apps/cats
。 - 管理器位于项目根目录的单独包中
project/manager
。 - 静态文件(.css、.png、.js 等)位于
project/static/[app/]
。有时需要使用可选的[app/]
文件夹,但只有当两个应用程序具有仪表板或冲突的文件名时才会发生。大多数情况下,您不需要为静态资源使用[app/]
。
管理器
我们将管理器称为包含纯函数的包,这些函数帮助应用程序执行其任务,例如数据库、缓存、S3 存储等。我们在开始监听之前调用 package.Startup()
来初始化每个管理器,并在程序中断时调用 package.Finalize()
进行清理。
一个管理器的示例可以是 project/cache/cache.go
:
type Config struct {
RedisURL string `envconfig:"redis_url"`
}
var config Config
var client *redis.Client
func Startup(c Config) error {
config = c
client, err := redis.Dial(c.RedisURL)
return err
}
func Set(k,v string) error {
return client.Set(k, v)
}
在 main.go(或 your_thing_test.go)中:
var spec cache.Config
envconfig.Process("project", &spec)
cache.Startup(spec)
在一个应用程序(或模块)中:
func SetCacheHandler(_ http.ResponseWriter, _ *http.Request){
cache.Set("this", "rocks")
}
模块
模块是包含与其他模块隔离的视图和控制器的容器,根据我们的配置,我建议不要在模块之间创建依赖关系。模块也被称为应用程序。
每个模块使用路由器、子路由器或您的框架提供的其他方式配置其路由,例如(文件 project/apps/dogs/configure.go
):
func Configure(e *echo.Echo) {
e.Get("/dogs", List)
}
然后,所有处理程序都位于 project/apps/dogs/handlers.go
中:
// List outputs a dog list of all stored specimen.
func List(c *echo.Context) error {
// 注意使用 models.Xyz
var res := make([]models.Dog, 0) // 一个小技巧,避免返回 nil。
err := store.FindAll("dogs", nil, &res) // 调用管理器查找所有狗。
// 处理错误 ...
return c.JSON(200, res) // 输出狗。
}
最后,在 main 中配置应用程序(或在测试中):
e := echo.New()
dogs.Configure(e)
// 更多应用程序
e.Run(":8080")
注意:对于视图,您可以将它们添加到 project/apps/<name>/views
文件夹中,并使用相同的函数进行配置。
其他
有时我们还会添加一个 project/constants
和一个 project/utils
包。
以下是它的外观:
请注意,在上面的示例中,templates
与应用程序分开,这是因为它只是一个占位符,目录为空。
希望对你有用。来自墨西哥的问候 :D。
英文:
It depends on your own style and rules, in my company, we develop our projects this way:
- Configuration is determined by environment variables, so we have a
company/envs/project.sh
file which has to be evaluated before service (outside the project in the image). - We add a
zscripts
folder that contains all extra scripts, like adding users or publishing a post. Intended to be used only for debug proposes. - Data models (entities) are in a package called
project/models
. - All controllers and views (HTML templates) are categorized into "apps" or "modules". We use the REST path as main group delimiter, so path
/dogs
goes to packageproject/apps/dogs
and/cats
toproject/apps/cats
. - Managers are in their separated package at project's root
project/manager
. - Static files (.css, .png, .js, etc.) are located at
project/static/[app/]
. Sometimes is required to have the optional[app/]
folder, but it only happens when two apps have dashboards or conflicting file names. Most of cases you won't need to use[app/]
for static resources.
Managers
We call a manager, a package that contains pure functions which helps apps to perform its task, for example, databases, cache, S3 storage, etc. We initialize each manager calling package.Startup()
before we start to listen, and finalize calling package.Finalize()
when program is interrupted.
An example of a manager could be project/cache/cache.go
:
type Config struct {
RedisURL string `envconfig:"redis_url"`
}
var config Config
var client *redis.Client
func Startup(c Config) error {
config = c
client, err := redis.Dial(c.RedisURL)
return err
}
func Set(k,v string) error {
return client.Set(k, v)
}
in main.go (or your_thing_test.go):
var spec cache.Config
envconfig.Process("project", &spec)
cache.Startup(spec)
And in a app (or module):
func SetCacheHandler(_ http.ResponseWriter, _ *http.Request){
cache.Set("this", "rocks")
}
Modules
A module is a container of views and controllers that are isolated from other modules, using our configuration I would recommend to not create dependencies between modules. Modules are also called apps.
Each module configures its routes using a router, sub-router or what your framework provides, for example (file project/apps/dogs/configure.go
):
func Configure(e *echo.Echo) {
e.Get("/dogs", List)
}
Then, all handlers live in project/apps/dogs/handlers.go
:
// List outputs a dog list of all stored specimen.
func List(c *echo.Context) error {
// Note the use of models.Xyz
var res := make([]models.Dog, 0) // A little trick to not return nil.
err := store.FindAll("dogs", nil, &res) // Call manager to find all dogs.
// handle error ...
return c.JSON(200, res) // Output the dogs.
}
Finally you configure the app in main (or in a test):
e := echo.New()
dogs.Configure(e)
// more apps
e.Run(":8080")
Note: for views, you can add them to project/apps/<name>/views
folder and configure them the using the same function.
Other
Sometimes we also add a project/constants
and a project/utils
package.
Here is what it looks like:
Note that in above sample, templates
are separated from apps, thats because its a placeholder, directory is empty.
Hope it was useful. Greetings from México :D.
答案2
得分: 4
我之前也曾为如何构建Go Web API而苦恼过,并且不知道有哪些网络资源可以告诉你如何编写Go Web API。
我所做的是查看GitHub上的其他项目,并尝试了解它们是如何组织代码的,例如,Docker仓库中有非常符合Go语言习惯的API代码。
此外,Beego是一个按照MVC方式生成项目结构的RESTful框架,根据他们的文档,它也可以用于API。
英文:
I've also struggled about how to structure my Go web APIs in the past and don't know any web resources that tell you exactly how to write a Go web API.
What I did was just check out other projects on Github and try out how they structured their code, for example, the Docker repo has very idomatic Go code on it's API.
Also, Beego is a RESTful framework that generates the project structure for you in a MVC way and according to their docs, it can also be used for APIs.
答案3
得分: 4
我已经在使用Go语言构建Web API一段时间了。
你需要进行一些研究,但我可以给你一些起点:
- 使用Go构建Web应用 -- 电子书
- github.com/julienschmidt/httprouter -- 用于路由地址
- github.com/unrolled/render/ -- 用于渲染各种形式的响应(JSON、HTML等)
- github.com/dgrijalva/jwt-go -- JSON Web令牌
- www.gorillatoolkit.org/pkg/sessions -- 会话管理
关于一些东西如何一起工作的参考:
Go Web API Repo -- 个人项目
英文:
I've been building a web APIs in golang for a little while now.
You'll have to do some research but I can give you some starting points:
- Building Web Apps with Go -- ebook
- github.com/julienschmidt/httprouter -- for routing addresses
- github.com/unrolled/render/ -- for rendering various forms of responses(JSON, HTML, etc..)
- github.com/dgrijalva/jwt-go -- JSON Web Tokens
- www.gorillatoolkit.org/pkg/sessions -- Session Management
And for reference on how some things work together in the end:
Go Web API Repo -- personal project
答案4
得分: 4
1. 关注点分离(SoC)
我没有直接使用过SoC,但我有自己的模式。你可以根据自己的需要选择适合的模式(如MVC、自定义等)。
在我的代码中,我将代码分成不同的包:
- myprojectname(main包):包含基本的设置、配置和项目常量
- handlers(handlers包):包含处理原始HTTP请求的代码
- models(models包):包含模型
- apis(非包)
- redis(redis包):包含包装
sync.Pool
的代码 - twilio(twilio包):处理外部API的示例层
- redis(redis包):包含包装
注意:
- 除了main包之外的每个包中,我都有一个
Setup()
函数(带有相关参数),由main包调用。 - 在
apis
文件夹下的包通常只是用于初始化外部Go库。你也可以直接在handlers/models中导入现有的库,而不需要apis
包。 - 我在
handlers
包中将mux设置为导出的全局变量,如下所示:
Router := mux.NewRouter()
然后为每个URL创建一个文件(具有相同URL的不同方法在同一个文件中)。在每个文件中,我使用Go的init()
函数,在全局变量初始化后运行(因此可以安全地使用路由器),但在main()
运行之前(因此可以确保一切都已设置好)。init()
的好处是你可以在单个包中拥有任意多个这样的方法,因此它们在导入包时自动运行。
然后,main导入myprojectname/handlers
,并在main中提供handlers.Router
。
2. 依赖注入和工作单元
我没有使用过工作单元,所以对于可能的Go实现没有任何了解。
对于依赖注入,我会构建一个接口,真实对象和模拟对象都将实现该接口。
在包中,我在根目录添加了以下代码:
var DatabaseController DatabaseControllerInterface = DefaultController
然后,在每个测试的第一行,我可以将DatabaseController
更改为测试所需的任何内容。当不进行测试时,单元测试不应该运行,并且默认为DefaultController
。
3. 单元测试
Go提供了内置的测试功能,可以使用go test package
命令进行测试。你可以使用go test --cover
命令来生成覆盖率报告。你甚至可以**在浏览器中显示覆盖率报告,突出显示已覆盖/未覆盖的部分**。
我使用**testify/assert**包来帮助我进行测试,它可以弥补标准库的不足:
// something_test.go
//
// _test表示它只会被编译为测试代码
// 文件名可以随意命名,但如果是单个文件中的测试代码,我喜欢使用filename_test.go。
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMath(t *testing.T) {
assert.Equal(t, 3+1, 4)
}
4. 与UI框架的集成
我没有看到针对Angular的集成。虽然我没有使用过,但Go内置了一个很好的**模板引擎**。
5. 支持SEO的RESTful URL
对于这个问题,我无法提供帮助。这取决于你自己:发送适当的状态码(不要发送404页面的200状态码,否则会被扣分),不要重复页面(注意google.com/something
与google.com/something/
的区别,希望你的框架不会搞砸),不要试图欺骗搜索引擎等等。
英文:
1. Separation of concerns (SoC)
I haven't worked with SoC directly, but I have my own pattern. You can adapt to whatever pattern (MVC, your own, etc.).
In my code, I separate my code into different packages:
myprojectname (package main) — Holds the very basic setup and configuration/project consts
* handlers (package handlers) — Holds the code that does the raw HTTP work
* models (package models) — Holds the models
* apis (NOT a package)
- redis (package redis) — Holds the code that wraps a `sync.Pool`
- twilio (package twilio) — Example of layer to deal with external API
Notes:
-
In each package other than main, I have a
Setup()
function (with relevant arguments) that is called by the main package. -
For the packages under the
apis
folder, they are often just to initialize external Go libraries. You also can directly import existing libraries into the handlers/models without anapis
package. -
I setup my mux as an exported global in the
handlers
package like this...Router := mux.NewRouter()
...and then create a file for each URL (different methods with the same URL are in the same file). In each file, I use Go's
init()
function, which is ran after global variables are initialized (so it's safe to use the router) but beforemain()
is ran (so it's safe for main to assume everything has been setup). The great thing aboutinit()
is that you can have as many of those methods as you want in a single package, so they automatically get ran when the package is imported.Main then imports
myprojectname/handlers
and then serves thehandlers.Router
in main.
2. Dependency Injection and Unity of Work
I haven't worked with Unity of Work, so I have no idea of possible Go implementations.
For DI, I build an interface that both the real object and the mock objects will implement.
In the package, I add this to the root:
var DatabaseController DatabaseControllerInterface = DefaultController
Then, the first line of each test I can change DatabaseController
to whatever that test needs. When not testing, the unit tests should not be ran, and it defaults to DefaultController
.
3. Unit Testing
Go provides a built in testing with the go test package
command. You can use go test --cover
to also emit a coverage percent. You can even have coverage displayed in your browser, highlighting the parts that are/aren't covered.
I use the testify/assert package to help me with testing where the standard library falls short:
// something_test.go
//
// The _test signifies it should only be compiled into a test
// Name the file whatever you want, but if it's testing code
// in a single file, I like to do filename_test.go.
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMath(t *testing.T) {
assert.Equal(t, 3+1, 4)
}
4. Integration with UI frameworks
I haven't seen any for Angular. Although I haven't used it, Go has a good template engine built into the standard lib.
5. RESTful URLs that enables SEO
Again, I can't help you here. It's up to you: send proper status codes (don't send a 200 with a 404 page as you'll get docked for duplicate pages), don't duplicate pages (pay attention to google.com/something
vs google.com/something/
; hopefully your framework will not mess this up), don't try to trick the search engine, and so on.
答案5
得分: 3
在我看来,生产服务器上的Go Web应用项目文件夹可以像你的图片一样简单。在资产结构中没有什么特别之处 - 静态文件夹、模板文件夹、内容文件夹、样式文件夹、图片文件夹、JS库文件夹、数据库脚本文件夹等等,都是常见的文件夹。在WebAPI中也没有什么特别之处 - 通常你设计哪个URI将响应所需的功能,并相应地将请求路由到处理程序。有一些特殊之处 - 许多Go开发者不相信MVC架构,这完全取决于你。而且你只需部署一个静态链接的可执行文件,没有依赖项。在开发环境中,你可以像标准库一样在$GOPATH中组织你自己的源文件和导入/供应的源文件,但在生产环境中只部署一个可执行文件,当然还需要静态资产。你可以看看标准库中如何组织Go源代码包。只有一个可执行文件,你会在生产环境中如何组织结构呢?
英文:
To my mind Go webapp project folder on production server can looks like on your picture just much simpler. Nothing special in assets structure - Static, Templates, Content, Styles, Img, JSlibs, DBscripts etc. usual folders. Nothing special in WebAPI - as usual you design which URI will respond required functionality and route requests to handlers accordingly. Some specifics - many gophers don't believe in MVC architecture, it's up to you surely. And you deploy one staticaly linked executable without dependencies. In your development environment you structure yours and imported/vendored sorce files in $GOPATH as in stdlib done but deploy only one executable in production environment, sure with static assets needed. You can see how to orginize Go source packages just in stdlib. Having just one executable what would you structure on production?
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论