英文:
How to separate routes, handlers, 3rd-party interfaces and business logic in real world Go project
问题
阅读了关于如何构建项目的官方指南并查看了各种示例和项目(例如1,2,3等),我不禁想知道我构建REST API服务器应用程序的方式是否正确。
API的目标是什么?
POST /auth/sign-in
接受username
和password
,并发放一个JWT(JSON Web Token)。
GET /auth/sign-out
将JWT添加到黑名单以使认证会话失效。
GET /resources
检索所有资源的列表。
POST /resources
(需要有效的JWT身份验证)
接受JSON主体,创建新资源,并向所有人发送电子邮件和通知,告知新资源的情况。
我的项目是什么样子的
目前,我没有创建任何库。一切都在主包中,主要的服务器设置和路由等都在main.go
中完成。我没有选择Rails或Django中的MVC模式,以避免为了简单而过度复杂化。而且我的印象是,这并不符合上面提到的指南中描述的命令和库的推荐结构。
auth.go # 生成、验证JWT等
auth-handler.go # 处理登录/登出请求;包括所需身份验证的中间件
mailer.go # 提供发送事务性电子邮件的方法,加载正确的模板等
main.go # 设置路由,运行服务器;为请求上下文初始化邮件和通知实例
models.go # User、Resource的结构定义
notifications.go # 提供发布推送通知的方法
resource-handler.go # 处理资源请求,使用邮件和通知实例处理POST请求
应该是什么样子的?
路由应该分离吗?中间件呢?如何处理与第三方代码的接口 - 想象一下在上述示例应用程序中,mailer.go
与Mandrill通信,notifications.go
与Amazon AWS SNS通信。
英文:
After reading the official guide on how to structure projects and going through various (1, 2, 3 to name a few) examples and projects I can't help wondering whether my approach of structuring my REST-API server app is structured properly.
What is the API meant to do?
POST /auth/sign-in
Accepts a username
and password
and issues a JWT (JSON Web Token).
GET /auth/sign-out
Adds the JWT to a blacklist to invalidate the auth session.
GET /resources
Retrieves a list of all resources.
POST /resources
(requires valid JWT authentication)
Accepts a JSON body, creates a new resource and sends out an email and notification to everyone about the new resource.
What my project looks like
Currently I'm not creating any libraries. Everything is sitting in the main package, the overarching server setup with routes etc. all done in main.go
. I didn't go for the MVC pattern found à la Rails or Django to avoid overcomplicating things just for the sake of it. Also my impression was it doesn't really comply with the recommended structure for commands and libraries as described in the guide already mentioned above.
auth.go # generates, validates JWT, etc
auth-handler.go # Handles sign-in/out requests; includes middleware for required authentication
mailer.go # Provides methods to send out transactional email, loads correct template etc.
main.go # set up routes, runs server; inits mailer and notification instance for the request context
models.go # struct definition for User, Resource
notifications.go # Provides methods to publish push notifications
resource-handler.go # Handles request for resources, uses mailer and notifications instances for the POST request
What should it look like?
Should routes be separated? What about middleware? And how do you deal with interfaces to 3rd party code – imagine mailer.go
in the outlined sample app talking to Mandrill and notifications.go
to Amazon AWS SNS?
答案1
得分: 3
我可以分享一些我自己的经验。
-
在应用程序代码中:
-
与库代码相反,将代码分离为包和子包并不那么重要,只要你的代码不太复杂。我通常将应用程序设计为集成独立的库,所以应用程序代码本身通常很小。总的来说,如果你不真正需要,尽量避免包的分离。但也不要将大量代码放在一个包中,那样也不好。
-
但不要有像"util"这样的通用包,它们很快就会积累问题并变得糟糕。我有一个单独的存储库,用于存放在项目中可重用的通用工具,每个实用程序 API 都是一个子包。例如,
github.com/me/myutils/countrycodes
,github.com/me/myutils/set
,github.com/me/myutils/whatevs
。
-
-
无论包的结构如何,最重要的是将内部 API 与处理程序代码分离。处理程序代码应该是一个非常薄的层,负责处理输入,并调用一个内部的、自包含的 API,可以在没有处理程序的情况下进行测试,或者与其他处理程序进行关联。看起来你正在这样做。然后,你可以将内部 API 分离到另一个包中,也可以不分离,这并不重要。
-
当你决定将代码的哪些部分分离成库时,要考虑代码重用。如果这段代码只会被你的应用程序使用,那就没有必要分离成库。
-
我喜欢在一个次要的包中使用接口来封装与第三方 API 的集成。例如,如果你有类似使用 AWS SES 发送电子邮件的功能,我会创建一个名为
github.com/my_org/mailer
的包,其中包含一个抽象接口,然后在其下创建一个github.com/my_org/mailer/ses
的包来实现 SES 的集成。应用程序代码导入mailer
包及其接口,在main
函数中将 SES 的使用方式注入并将各个部分整合在一起。 -
关于中间件 - 我通常将其与 API 本身放在同一个包中。
英文:
I can share a bit from my own experience.
- In application code:
-
as opposed to library code, separating into packages and sub-packages is less important - as long as you don't have too much complexity in your code. I mostly design apps as integrating self contained libraries, so the app code itself is usually rather small. In general, try to avoid package separation if you don't really need it. But don't just slap tons of code in one package - that's also bad.
-
but don't have general packages like "util", they soon start to accumulate baggage and suck. I have a separate repo for generic utils reusable across projects, and under it each utility API is a sub package. e.g.
github.com/me/myutils/countrycodes
,github.com/me/myutils/set
,github.com/me/myutils/whatevs
.
-
Regardless of the package structure, the most important thing is to separate internal APIs from handler code. The handlers code should be a very thin layer that handles input, and calls an internal, self contained API, that can be tested without handlers, or tied to other handlers. Looks like you're doing this. Then you can separate your internal API into another package or not, it doesn't really matter.
-
When you're deciding what parts of the code should be separated into libraries, think in terms of code reuse. If this code will be used only by your app, there's no point in that.
-
I like to wrap integration with third party APIs in an interface that is defined in a secondary package. For example if you have something like sending emails with AWS SES, I'd create a pakcage called
github.com/my_org/mailer
, with an abstract interface, and under it angithub.com/my_org/mailer/ses
package that implements the SES integration. The application code imports themailer
package and its interface, and only inmain
do I somehow inject the usage of SES and integrate things together. -
re middleware - I usually keep it in the same package as the API itself.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论