vlambda博客
学习文章列表

读书笔记《building-distributed-applications-in-gin》评估

Assessments

本节包含所有章节问题的答案。

Chapter 1 – Getting started with Gin

  1. Golang is currently one of the fastest growing programming languages in the software development industry. It is a lightweight, open-source language suited for today's microservices architectures.
  2. Multiple web frameworks exist, the most popular are Gin, Martini, and Gorilla.
  3. A Go module is a way to group together a set of packages and give it a version number to mark its existence at a specific point in time.
  4. The default port of an HTTP server backed by Gin framework is 8080.
  5. You can use the c.JSON() or c.XML() methods to return literal JSON or XML structs.

Chapter 2 – Setting up API Endpoints

  1. GitFlow is a branching strategy that developers can follow when using version control. To apply the GitFlow model, you need a central Git repository with two main branches:

    Master:存储官方发布历史。

    开发:它作为功能的集成分支。

  2. A model is a normal structs with basic Go types. To declare a struct in Go, use the following format:
    Type ModelName struct{ 
        Field1 TYPE 
        Fiel2 TYPE 
    }
  3. To bind a request body into a type, we use Gin model binding. Gin supports binding of JSON, XML and YAML. Gin provides two sets of methods for binding:

    应该绑定:ShouldBindJSON()ShouldBindXML()ShouldBindYAML()

    必须绑定:使用指定的绑定引擎绑定结构指针。如果发生任何错误,它将使用 HTTP 400 中止请求。

  4. First, define a route with an ID as a path parameter:
    router.GET("/recipes/:id", GetRecipeHandler)

    GetRecipeHandler 函数解析 ID 参数并 go 循环遍历食谱列表。如果 ID 与列表的某个配方匹配,则返回,否则将抛出 404 错误,如下所示:

    func GetRecipeHandler(c *gin.Context) {
         id := c.Query("id")
         for i := 0; i < len(recipes); i++ {
              if recipes[i].ID == id {
                   c.JSON(http.StatusOK, recipes[i])
              }
         }
         c.JSON(http.StatusNotFound, gin.H{"error": "Recipe  	                                       not found"})
    }
  5. To define a parameter, we use the swagger:parameters annotation:
    // swagger:parameters recipes newRecipe
    type Recipe struct {
         //swagger:ignore
         ID string `json:"id"`
         Name string `json:"name"`
         Tags []string `json:"tags"`
         Ingredients []string `json:"ingredients"`
         Instructions []string `json:"instructions"`
         PublishedAt time.Time `json:"publishedAt"`
    }

    使用 swagger generate 命令生成规范并在 Swagger UI 上加载结果。

    您现在可以通过直接从 Swagger UI 填写配方字段来发出 POST 请求。

Chapter 3 – Managing Data Persistence with MongoDB

  1. You can delete recipes using collection.DeleteOne() or collection.DeleteMany(). Here you pass bson.D({}) as the filter argument, which will match all documents in the collection.

    更新 DeleteRecipeHandler 如下:

    func (handler *RecipesHandler) DeleteRecipeHandler(c *gin.Context) {
       id := c.Param("id")
       objectId, _ := primitive.ObjectIDFromHex(id)
       _, err := handler.collection.DeleteOne(handler.ctx, bson.M{
           "_id": objectId,
       })
       if err != nil {
           c.JSON(http.StatusInternalServerError,  	 	 	              gin.H{"error": err.Error()})
           return
       }
       c.JSON(http.StatusOK, gin.H{"message": "Recipe has  	                               been deleted"})
    }

    确保在 DELETE /recipes/{id} 资源上注册处理程序,如下所示:

    router.DELETE("/recipes/:id", recipesHandler.DeleteRecipeHandler)
  2. To find a recipe, you will need a filter document, as well as a pointer to value into which the result can be decoded. To find a single recipe, use collection.FindOne(). This method returns a single result which can be decoded into a Recipe struct. You'll use the same filter variable you used in the update query to match a recipe where ID is 600dcc85a65917cbd1f201b0.

    main.go 文件上注册一个处理程序:

    router.GET("/recipes/:id", recipesHandler.GetOneRecipeHandler)

    然后,在 handler.go 中声明 GetOneRecipeHandler,内容如下:

    func (handler *RecipesHandler) GetOneRecipeHandler(c *gin.Context) {
       id := c.Param("id")
       objectId, _ := primitive.ObjectIDFromHex(id)
       cur := handler.collection.FindOne(handler.ctx, bson.M{
           "_id": objectId,
       })
       var recipe models.Recipe
       err := cur.Decode(&recipe)
       if err != nil {
           c.JSON(http.StatusInternalServerError, 	 	 	              gin.H{"error": err.Error()})
           return
       }
       c.JSON(http.StatusOK, recipe)
    }
  3. JSON documents in MongoDB are stored in a binary representation called BSON (Binary-encoded JSON). This format includes additional types such as:

    一个。双倍的

    湾。细绳

    C。目的

    d。大批

    e.二进制数据

    F。不明确的

    G。对象 ID

    H。布尔值

    一世。日期

    j.无效的

    这使得应用程序更容易可靠地处理、排序和比较数据。

  4. Least Recently Used (LRU) algorithm uses the recent past to approximate the near future. It simply deletes the keys that has not been used for the longest period of time.

Chapter 4 – Building API Authentication

  1. In order to create a user or sign them up, we need to define a HTTP handler with SignUpHandler as follows:
    func (handler *AuthHandler) SignUpHandler(c *gin.Context) {
       var user models.User
       if err := c.ShouldBindJSON(&user); err != nil {
           c.JSON(http.StatusBadRequest, gin.H{"error":                                            err.Error()})
           return
       }
       cur := handler.collection.FindOne(handler.ctx, bson.M{
           "username": user.Username,
       })
       if curTalent.Err() == mongo.ErrNoDocuments {
           err := handler.collection.InsertOne(handler.ctx,  	                                           user)
           if err != nil {
               c.JSON(http.StatusInternalServerError, 	 	                  gin.H{"error": err.Error()})
               return
           }
           c.JSON(http.StatusAccepted, gin.H{"message": 	 	              "Account has been created"})
       }
       c.JSON(http.StatusInternalServerError, gin.H{"error":  	          "Username already taken"})
    }
    Then, register the handler on POST /signup route:
    router.POST("/signup", authHandler.SignUpHandler)

    为确保用户名字段在所有用户条目中唯一,您可以为用户名字段创建唯一索引。

  2. Define a ProfileHandler with the following body:
    func (handler *AuthHandler) ProfileHandler(c *gin.Context) {
       var user models.User
       username, _ := c.Get("username")
       cur := handler.collection.FindOne(handler.ctx, bson.M{
           "username": user.Username,
       })
       cur.Decode(&user)
       c.JSON(http.StatusAccepted, user)
    }
    Register the HTTP handler on the router group as below:
    authorized := router.Group("/")
    authorized.Use(authHandler.AuthMiddleware()){
           authorized.POST("/recipes",                        recipesHandler.NewRecipeHandler)
           authorized.PUT("/recipes/:id",                       recipesHandler.UpdateRecipeHandler)
           authorized.DELETE("/recipes/:id",                       recipesHandler.DeleteRecipeHandler)
           authorized.GET("/recipes/:id",                       recipesHandler.GetOneRecipeHandler)
           authorized.GET("/profile",                       authHandler.ProfileHandler)
    }
  3. Add the following Swagger annotation in top of SignOutHandler signature:
    // swagger:operation POST /signout auth signOut
    // Signing out
    // ---
    // responses:
    //     '200':
    //         description: Successful operation
    func (handler *AuthHandler) SignOutHandler(c *gin.Context) {}

Chapter 5 – Serving Static HTML in Gin

  1. Create a header.tmpl file with the following content:
    <head>
        <title>Recipes</title>
        <link rel="stylesheet" href="/assets/css/app.css">
        <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
    </head>

    然后,使用以下代码块引用 recipe.tmpl 中的文件:

    {{template "/templates/header.tmpl.tmpl"}}

    按照相同的方法为页脚部分创建可重复使用的模板。

  2. The full-source code of the NewRecipe.js component is available on the GitHub repository under the folder for Chapter 5, Serving static HTML in Gin.
  3. Cross-compiling works by setting required environment variables that specify the target operating system and architecture. We use the variable GOOS for the target operating system, and GOARCH for the target architecture. To build an executable, the command would take this form:
    GOOS=target-OS GOARCH=target-architecture go build –o main *.go

    例如,要为 Windows 构建二进制文件,您可以使用以下命令:

    GOOS=windows GOARCH=amd64 go build –o main main.go

Chapter 7 – Testing Gin HTTP Routes

  1. Define a TestUpdateRecipeHandler in main_test.go as follows:
    func TestUpdateRecipeHandler(t *testing.T) { 
       ts := httptest.NewServer(SetupServer()) 
       defer ts.Close() 
     
       recipe := Recipe{ 
           ID:   "c0283p3d0cvuglq85log", 
           Name: "Oregano Marinated Chicken", 
       } 
     
       raw, _ := json.Marshal(recipe) 
       resp, err := http.PUT(fmt.Sprintf("%s/recipes/%s", ts.URL, recipe.ID), bytes.NewBuffer(raw)) 
       defer resp.Body.Close() 
       assert.Nil(t, err) 
       assert.Equal(t, http.StatusOK, resp.StatusCode) 
       data, _ := ioutil.ReadAll(resp.Body) 
     
       var payload map[string]string 
       json.Unmarshal(data, &payload) 
     
       assert.Equal(t, payload["message"], "Recipe has been updated") 
    } 
    Define TestDeleteRecipeHandler in main_test.go as follows: 
    func TestDeleteRecipeHandler(t *testing.T) { 
       ts := httptest.NewServer(SetupServer()) 
       defer ts.Close() 
     
       resp, err := http.DELETE(fmt.Sprintf("%s/recipes/c0283p3d0cvuglq85log", ts.URL)) 
       defer resp.Body.Close() 
       assert.Nil(t, err) 
       assert.Equal(t, http.StatusOK, resp.StatusCode) 
       data, _ := ioutil.ReadAll(resp.Body) 
     
       var payload map[string]string 
       json.Unmarshal(data, &payload) 
     
       assert.Equal(t, payload["message"],                 "Recipe has been deleted") 
    } 
  2. Define TestFindRecipeHandler in main_test.go as follows:
    func TestDeleteRecipeHandler(t *testing.T) { 
       ts := httptest.NewServer(SetupServer()) 
       defer ts.Close() 
     
       resp, err := http.DELETE(fmt.Sprintf("%s/recipes          /c0283p3d0cvuglq85log", ts.URL)) 
       defer resp.Body.Close() 
       assert.Nil(t, err) 
       assert.Equal(t, http.StatusOK, resp.StatusCode) 
       data, _ := ioutil.ReadAll(resp.Body) 
     
       var payload map[string]string 
       json.Unmarshal(data, &payload) 
     
       assert.Equal(t, payload["message"],                 "Recipe has been deleted") 
    }
  3. Define TestFindRecipeHandler in main_test.go as follows:
    func TestFindRecipeHandler(t *testing.T) { 
       ts := httptest.NewServer(SetupServer()) 
       defer ts.Close() 
     
       expectedRecipe := Recipe{ 
           ID:   "c0283p3d0cvuglq85log", 
           Name: "Oregano Marinated Chicken", 
           Tags: []string{"main", "chicken"}, 
       } 
     
       resp, err := http.GET(fmt.Sprintf("%s/recipes/c0283p3d0cvuglq85log", ts.URL)) 
       defer resp.Body.Close() 
       assert.Nil(t, err) 
       assert.Equal(t, http.StatusOK, resp.StatusCode) 
       data, _ := ioutil.ReadAll(resp.Body) 
     
       var actualRecipe Recipe 
       json.Unmarshal(data, &actualRecipe) 
     
       assert.Equal(t, expectedRecipe.Name,                 actualRecipe.Name) 
       assert.Equal(t, len(expectedRecipe.Tags), len(actualRecipe.Tags)) 
    }

Chapter 8 – Deploying the Application on AWS

  1. Create a Docker volume with the following command:
    docker volume create mongodata

    然后在运行 Docker 容器时挂载卷:

    docker run -d -p 27017:27017 -v mongodata:/data/db --name mongodb mongodb:4.4.3
  2. To deploy RabbitMQ, you can use the docker-compose.yml to deploy an additional service based on the RabbitMQ official image as follows:
    rabbitmq:
         image: rabbitmq:3-management
         ports:
           - 8080:15672
         environment:
           - RABBITMQ_DEFAULT_USER=admin
           - RABBITMQ_DEFAULT_PASS=password
  3. Create the user's credentials in the form of a Kubernetes secret:
    kubectl create secret generic mongodb-password --from-literal="password=YOUR_PASSWORD"

    创建密钥后,我们需要更新 mongodb-deployment.yaml 以使用 Kubernetes 密钥:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
     annotations:
       kompose.cmd: kompose convert
       kompose.version: 1.22.0 (955b78124)
     creationTimestamp: null
     labels:
       io.kompose.service: mongodb
     name: mongodb
    spec:
     replicas: 1
     selector:
       matchLabels:
         io.kompose.service: mongodb
     strategy: {}
     template:
       metadata:
         annotations:
           kompose.cmd: kompose convert
           kompose.version: 1.22.0 (955b78124)
         creationTimestamp: null
         labels:
           io.kompose.service: mongodb
       spec:
         containers:
           - env:
               - name: MONGO_INITDB_ROOT_PASSWORD
                 valueFrom:
                   secretKeyRef:
                     name: mongodb-password
                     key: password
               - name: MONGO_INITDB_ROOT_USERNAME
                 value: admin
             image: mongo:4.4.3
             name: mongodb
             ports:
               - containerPort: 27017
             resources: {}
         restartPolicy: Always
    status: {}
  4. To scale the API pods with kubectl, issue the following command:
    kubectl scale deploy

Chapter 9 – Implementing a CI/CD Pipeline

  1. The pipeline will have the following stages:

    一个。从 GitHub 存储库签出源代码。

    湾。使用 npm install 命令安装 NPM 包。

    C。使用 npm run build 命令生成资产。

    d。安装 AWS CLI 并将新资产推送到 S3 存储桶。

    e. config.yml 在这里给出:

    version: 2.1
    executors:
     environment:
       docker:
         - image: node:lts
       working_directory: /dashboard
    jobs:
     build:
       executor: environment
       steps:
         - checkout
         - restore_cache:
             key: node-modules-{{checksum "package.json"}}
         - run:
             name: Install dependencies
             command: npm install
         - save_cache:
             key: node-modules-{{checksum "package.json"}}
             paths:
               - node_modules
         - run:
             name: Build artifact
             command: CI=false npm run build
         - persist_to_workspace:
             root: .
             paths:
               - build
     deploy:
       executor: environment
       steps:
         - attach_workspace:
             at: dist
         - run:
             name: Install AWS CLI
             command: |
               apt-get update
               apt-get install -y python3-pip
               pip3 install awscli
         - run:
             name: Push to S3 bucket
             command: |
               cd dist/build/dashboard/
               aws configure set preview.cloudfront true
               aws s3 cp --recursive . s3://YOUR_S3_BUCKET/ --region YOUR_AWS_REGION
    workflows:
     ci_cd:
       jobs:
         - build
         - deploy:
             requires:
               - build
             filters:
               branches:
                 only:
                   - master

    在运行管道之前,您需要向 CircleCI IAM 用户授予访问 S3​​:PutObject 权限。

  2. You can configure the Slack ORB to send a notification on a successful pipeline as follows:
    - slack/notify:
         event: pass
         custom: |
          {
            "blocks": [
              {
                "type": "section",
                "text": {
                  "type": "mrkdwn",
                  "text": "Current Job: $CIRCLE_JOB"
                }
              },
              {
                "type": "section",
                "text": {
                  "type": "mrkdwn",
                  "text": "New release has been successfully  	                       deployed!"
                }
              }
             ]
          }

Why subscribe?

  • Spend less time learning and more time coding with practical eBooks and Videos from over 4,000 industry professionals
  • Improve your learning with Skill Plans built especially for you
  • Get a free eBook or video every month
  • Fully searchable for easy access to vital information
  • Copy and paste, print, and bookmark content

您是否知道 Packt 提供每本已出版书籍的电子书版本,并提供 PDF 和 ePub 文件?您可以在 packt.com 升级到电子书版本,作为印刷书客户,您有权享受电子书折扣复制。请通过 [email protected] 与我们联系以了解更多详情。

www.packt.com,您还可以阅读一系列免费技术文章,注册一系列免费时事通讯,并获得 Packt 书籍和电子书的独家折扣和优惠。