背景:为什么要开关?
nodejs使程序员更简单地使后端编程使您可以在JavaScript中编写服务。尽管Nodejs今天仍然很重要,但JavaScript作为编程语言并不理想写。微软帮助JavaScript社区引入了打字稿,但这并没有解决基本问题:该代码仍被转移到JavaScript。
我可以在JavaScript和Typescript上写一整篇文章。如果您像我这样做,请发表评论。继续前进
源代码
您可以在此处找到本文中的所有代码:GitHub
我强烈建议您仔细阅读代码以更好地理解事物。仅使用本文来理解存储库中决策背后的推理。
选择正确的框架
重点是编写HTTP服务器。 ExpressJS是使用JavaScript构建HTTP服务器的最受欢迎的框架。这里的基本假设将是您已经熟悉它,现在正在尝试切换。我不会专注于自己。重点将是如何在GO中复制相同的ExpressJS结构。对GO的熟悉会有所帮助。
Gin是Golang构建HTTP服务器最受欢迎的框架之一。我的一般建议是在选择您尚不满意的语言时,选择最受欢迎的框架和图书馆。掌握了默认配置和设置后,您可以专注于优化并为用例选择正确的框架。
基本项目设置和目录结构
良好的项目结构总是会随着项目的扩展而帮助您走上路。代码管理取决于编程语言本身。以下是Golang项目结构标准惯例的一些必读文章:
- 标准软件包布局:https://www.gobeyond.dev/standard-package-layout
- Golang标准 - 项目布局:https://github.com/golang-standards/project-layout
构建GO代码的标准实践
请注意,将所有代码放入GO中不是标准练习。以下目录几乎所有项目都必须:
cmd
这是应用程序的入口点。在JavaScript项目中相当于index.js
。您不想在这里放很多代码;只是项目级结构/接口和基本初始化设置。
cmd
目录不得包括任何实现逻辑。请注意,根目录不允许具有任何外部依赖关系。它必须只致电/启动各自的应用程序包。
pkg
这是您在应用程序中定义公共软件包的地方。这是您编写实际实施逻辑的地方。 pkg
目录中的软件包应在您的应用程序之外共享;请记住。
internal
此目录类似于pkg
目录,除了仅用于您的应用程序。这种布局模式由GO编译器本身强制执行。
internal
目录还可以具有pkg
子目录以更好地分离代码。 pkg
子目录将包含在internal
软件包中共享的软件包。
vendor
此目录包含您的应用程序依赖项。这是一个自动管理的目录,因此您不必在此处手动更改任何内容。您需要做的就是执行go mod vendor
来创建和维护此目录。这里的基本假设是您正在使用GO模块来管理依赖关系。
请注意,您需要将-mod=vendor
添加到go build
命令中以构建项目。
configs
您的应用程序配置将进入此处。您可能会从环境变量中读取大多数配置。您可以使用此目录从环境变量提取配置并将其存储在结构中。
项目结构示例
cmd/
__app_name__/
init.go # entrypoint
pkg/
__app_name__/
users/
auth/
internal/
__app_name__/
razorpay/
vendor/
github.com/
golang.org/
google.golang.org/
configs/
configs.go
build/
deploy/
Dockerfile
go.mod
go.sum
路线结构和中间件
cmd/__app_name__
目录内的main.go
文件将很简单。它的唯一目的是启动项目并按顺序进行设置。
package main
import (
"fmt"
"github.com/saranshabd/migrate-from-express/cmd/mfe/http"
"os"
"github.com/gin-gonic/gin"
"github.com/saranshabd/migrate-from-express/cmd/mfe/logger"
"github.com/saranshabd/migrate-from-express/configs"
)
func main() {
// Create a Gin server application with default configurations & middlewares
app := gin.Default()
// Load all the application HTTP endpoints
http.InitRoutes(app)
// Start listening to HTTP requests from the client
logger.App.Info().Msgf("Firing it up on %d port number.", configs.Configs.Port)
if err := app.Run(fmt.Sprintf(":%d", configs.Configs.Port)); err != nil {
logger.App.Error().Err(err).Msg("Could not fire up Gin :/")
os.Exit(1) // Kill the application
}
}
所有路由处理程序都将基于上面讨论的目录结构进入internal/__app_name__/
目录。
internal/
middlewares/
__route_name__/
routes.go
handler1.go
handler2.go
首先,我们必须确保我们发送的所有HTTP响应均为默认情况下。我们可以为此目的创建一个简单的中间件。
package middlewares
import "github.com/gin-gonic/gin"
func JSONResponse() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Content-Type", "application/json")
c.Next()
}
}
其次,我们需要一个地方来导入所有HTTP路由处理程序并将其映射到特定的端点。我们可以将此代码放在cmd
目录中,因为我们要做的就是重要的一堆处理程序,并将其映射到各自的端点。
package http
import (
"github.com/gin-gonic/gin"
"github.com/saranshabd/migrate-from-express/internal/mfe/middlewares"
)
func InitRoutes(app *gin.Engine) {
// Ensure that all the HTTP responses are by-default in the JSON format
app.Use(middlewares.JSONResponse())
// Load all the application HTTP routes
// ...
}
就是这样!杜松子酒中的路由器和中间件设置与ExpressJS中的相似。我们可以从main.go
文件调用此InitRoutes
函数。
从不同来源提取请求参数
我们可以继续使用当前项目设置创建一堆HTTP端点。终点将主要集中于从不同来源提取请求参数,例如请求身体,查询和URI。请求参数将全部返回在响应对象中。
我创建了一堆简单的HTTP实用程序功能,以获得更好的代码可读性。这些功能与教程没有任何关系,因此我不会介绍细节。您可以在GitHub上找到所有源代码。
GO将功能视为一流的公民,因此您的HTTP设置将只是一堆功能。杜松子酒中的HTTP处理程序需要一个参数:杜松子酒上下文。上下文是GO的基本面之一。如果您还不熟悉它,那么我建议您在继续之前花一些时间来理解它们。
从请求主体提取
GO在开箱即用的基本验证中提供了一个漂亮的绑定软件包。可以将绑定模块与Nodej中的JOI -package进行比较。两者之间的主要区别在于,绑定是GO语言本身的一部分,因此我们不必单独定义验证逻辑。
这就是典型的http处理程序的样子:
type CreateExtractParamsArgs struct {
Arg1 string `json:"arg1" binding:"required"`
Arg2 int64 `json:"arg2" binding:"required"`
Arg3 bool `json:"arg3,omitempty"` // optional
}
func CreateExtractParams(c *gin.Context) {
// Extract the parameters from the request.body & validate the object received
var args CreateExtractParamsArgs
if err := c.BindJSON(&args); err != nil {
// Return HTTP 400 if invalid params are passed
http.InvalidRequestParams(c)
return
}
// Return the params object in response if everything is fine
http.RespondOkay(c, "Request parameters extracted successfully", args)
}
请注意,我正在使用自定义http
软件包,这只是我创建的实用程序软件包。您可以参考下面的RespondOkay
功能:
func RespondOkay(c *gin.Context, message string, data any) {
Respond(c, http.StatusOK, true, message, data)
}
func Respond(c *gin.Context, statusCode int, success bool, message string, data any) {
c.JSON(statusCode, &gin.H{
"success": success,
"message": message,
"data": data,
})
}
omitempty
属性将在应对时删除该字段,让我们说json。字段的结构值将是类型的默认值。例如。如果您访问args.Arg3
,那么您将在响应中收到false
。 提防这种行为。这不是javascript!
从查询参数提取
我们可以使用相同的模式使用绑定软件包来验证查询参数。我们将使用BindQuery
功能而不是BindJSON
。其他一切看起来都一样。
type GetExtractParamsArgs struct {
Arg1 string `form:"arg1" json:"arg1" binding:"required"`
Arg2 int64 `form:"arg2" json:"arg2" binding:"required"`
Arg3 bool `form:"arg3" json:"arg3,omitempty"` // optional
}
func GetExtractParams(c *gin.Context) {
// Extract the parameters from the request.query & validate the object received
var args GetExtractParamsArgs
if err := c.BindQuery(&args); err != nil {
// Return HTTP 400 if invalid params are passed
http.InvalidRequestParams(c)
return
}
// Return the params object in response if everything if fine
http.RespondOkay(c, "Request parameres extracted successfully", args)
}
从URI提取
再次,我们可以扩展绑定软件包以验证路径参数,即/path/:arg/
。您可以在杜松子酒中定义路径参数:
r.GET("/:specificPath/", GetSpecificExtractParams)
我们可以将BindQuery
功能与BindUri
交换,并保持所有相同的功能。到现在为止,您必须掌握GO。
type GetSpecificExtractParamsArgs struct {
SpecificPath string `uri:"specificPath" json:"specificPath" binding:"required"`
}
func GetSpecificExtractParams(c *gin.Context) {
// Extract the parameters from the request.params & validate the object received
var args GetSpecificExtractParamsArgs
if err := c.BindUri(&args); err != nil {
// Return HTTP 400 if invalid params are passed
http.InvalidRequestParams(c)
return
}
// Return the params object in response if everything is fine
http.RespondOkay(c, "Request parameters extracted successfully", args)
}
基于标题的授权
身份验证和授权是几乎所有其余API的基础。 auth通常建在HTTP标头的顶部。我们可以使用绑定软件包来验证在请求中收到的标头,并写入中间件以在此基础上添加自定义逻辑。
这是Auth标题的一个简单示例,用于检查传入请求的源:
type VerifiedSourcesArgs struct {
VerifiedSource string `header:"verified-source" binding:"required"`
}
const (
AcceptedSource = "accepted-source"
)
func IsVerifiedSource() gin.HandlerFunc {
return func(c *gin.Context) {
// Extract the header value from the HTTP request
var args VerifiedSourcesArgs
if err := c.ShouldBindHeader(&args); err != nil {
http.InvalidHeaders(c)
return
}
// Check if the source of the incoming request is accepted
if args.VerifiedSource != AcceptedSource {
http.InvalidHeaders(c)
return
}
// Continue with the chain of middlewares/handlers
c.Next()
}
}
杜松子酒中的Next
功能
Gin Middlewares看起来与Expressjs Middlewares非常相似。但是,两者之间有一个讨厌的区别。 Next
功能。 您不必在杜松子酒中间件中调用Next
功能。
这是一个重要的区别,因为您不能期望请求仅仅因为您从中间件返回而无需调用Next
函数而终止。当您的中间件终止时,仍将调用下一个链函数。除非您中止请求。
func AbortAndRespond(c *gin.Context, statusCode int, message string, data any) {
c.AbortWithStatusJSON(statusCode, &gin.H{
"success": false,
"message": message,
"data": data,
})
}
而不是调用c.JSON
,我们需要调用c.AbortWithStatusJSON
函数以在中止请求之前设置状态代码和JSON响应。这是一个令人讨厌的区别,特别是对于来自Nodejs背景的程序员而言。当心!
就是这样!我们设置了HTTP路由,从不同来源验证了传入的请求数据,并为应用程序创建了AUTH MiddleWares。
这仅仅是开始。该应用程序没有与数据库或第三方服务进行交互;两者都是大多数服务的基础知识。这是针对系列的下一篇文章:)
如果我错过了或可以更好地解释的话,请发表评论。
欢呼! ð