title: 20240324-learn-go date: 2024-03-24 tags:

  • backend
  • go
  • grpc updated: 2024-03-24 up:
  • "[[backend]]"

quickstart

golang in 100 secoonds

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, world!")
}

編譯可執行檔案:go build hello.go 執行:go run hello.go 初始化 dependancy: go mod init alanhc/school 下載:go install OOO,會出現在go.mod

Tutorial: Get started with Go

  • dependency
  • how? go mod init example/hello
  • Hello world
package main
import "fmt"
func main() {
	fmt.Println("Hello, World!")
}
  • go run .
  • external package
    • e.g. https://pkg.go.dev/search?q=quote
    • go mod tidy:新增或移除未使用的pkg ,會產生go.sum(用於驗證)
import "rsc.io/quote"
func main() {
    fmt.Println(quote.Go())

Tutorial: Create a Go module

Call your code from another module

  • production
    • 會公開 example.com/greetings
  • 替換規則到指定路徑
    • go mod edit -replace example.com/greetings=../greetings
  • 在hello/hello.go go run .

Return and handle an error

import (
	"errors"
func Hello(name string) (string, error) {
	// If no name was given, return an error with a message.
	if name == "" {
		return "", errors.New("empty name")
	}
	
	return message, nil  // nil 代表沒有錯誤
package main

  

import (

"fmt"

"log"

  

"example.com/greetings"

)

  

func main() {

// Set properties of the predefined Logger, including

// the log entry prefix and a flag to disable printing

// the time, source file, and line number.

log.SetPrefix("greetings: ") // log 開頭會是greetings

log.SetFlags(0) //禁用列印時間

  

// Request a greeting message.

message, err := greetings.Hello("")

// If an error was returned, print it to the console and

// exit the program.

if err != nil {

log.Fatal(err)

}

  

// If no error was returned, print the returned message

// to the console.

fmt.Println(message)

}

Return a random greeting

  • greetings.go
import (
    "errors"
    "fmt"
    "math/rand"
...
func Hello(name string) (string, error) {
...
	message := fmt.Sprintf(randomFormat(), name)
// randomFormat returns one of a set of greeting messages. The returned
// message is selected at random.
func randomFormat() string {
    // A slice of message formats.
    formats := []string{
        "Hi, %v. Welcome!",
        "Great to see you, %v!",
        "Hail, %v! Well met!",
    }

    // Return a randomly selected message format by specifying
    // a random index for the slice of formats.
    return formats[rand.Intn(len(formats))]
}
  • hello.go
message, err := greetings.Hello("Gladys")

Return greetings for multiple people

  • greeting.go
// Hellos returns a map that associates each of the named people
// with a greeting message.
func Hellos(names []string) (map[string]string, error) {
    // A map to associate names with messages.
    messages := make(map[string]string)
    // Loop through the received slice of names, calling
    // the Hello function to get a message for each name.
    for _, name := range names {
        message, err := Hello(name)
        if err != nil {
            return nil, err
        }
        // In the map, associate the retrieved message with
        // the name.
        messages[name] = message
    }
    return messages, nil
}
  • hello.go
// A slice of names.
    names := []string{"Gladys", "Samantha", "Darrin"}

    // Request greeting messages for the names.
    messages, err := greetings.Hellos(names)
    if err != nil {
        log.Fatal(err)
    }
    // If no error was returned, print the returned map of
    // messages to the console.
    fmt.Println(messages)

Add a test

  • test: 直接在原本資料夾新增 _test.go e.g. greetings_test.go
  • testing.T: 指到testing pkg的指標

Compile and install the application

  • go build 會產生 .hello / hello.exe
  • 查詢安裝路徑:go list -f '{{.Target}}'
  • 確認GOPATH要加到 ~/.zshrc

Tutorial: Getting started with multi-module workspaces

  • 新增workspace: go work init ./hello
  • 執行: go run ./hello
  • 使用外部
    • git clone https://go.googlesource.com/example
    • 加到workspace go work use ./example/hello
    • 新增到workspace:  workspace/example/hello/reverse 新增int.go
    • 執行 go run ./hello
package main

import (
    "fmt"

    "golang.org/x/example/hello/reverse"
)

func main() {
    fmt.Println(reverse.String("Hello"), reverse.Int(24601))
}

Tutorial: Accessing a relational database

mkdir data-access go mod init example/data-access

mysql -u root -p
mysql> create database recordings;
mysql> use recordings;
Database changed

 create-tables.sql

DROP TABLE IF EXISTS album;
CREATE TABLE album (
  id         INT AUTO_INCREMENT NOT NULL,
  title      VARCHAR(128) NOT NULL,
  artist     VARCHAR(255) NOT NULL,
  price      DECIMAL(5,2) NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO album
  (title, artist, price)
VALUES
  ('Blue Train', 'John Coltrane', 56.99),
  ('Giant Steps', 'John Coltrane', 63.99),
  ('Jeru', 'Gerry Mulligan', 17.99),
  ('Sarah Vaughan', 'Sarah Vaughan', 34.98);
mysql> source /path/to/create-tables.sql
  • main.go
package main

import (
    "database/sql"
    "fmt"
    "log"
    "os"

    "github.com/go-sql-driver/mysql"
)

var db *sql.DB

type Album struct {
    ID     int64
    Title  string
    Artist string
    Price  float32
}

func main() {
    // Capture connection properties.
    cfg := mysql.Config{
        User:   os.Getenv("DBUSER"),
        Passwd: os.Getenv("DBPASS"),
        Net:    "tcp",
        Addr:   "127.0.0.1:3306",
        DBName: "recordings",
    }
    // Get a database handle.
    var err error
    db, err = sql.Open("mysql", cfg.FormatDSN())
    if err != nil {
        log.Fatal(err)
    }

    pingErr := db.Ping()
    if pingErr != nil {
        log.Fatal(pingErr)
    }
    fmt.Println("Connected!")

    albums, err := albumsByArtist("John Coltrane")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Albums found: %v\n", albums)

    // Hard-code ID 2 here to test the query.
    alb, err := albumByID(2)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Album found: %v\n", alb)

    albID, err := addAlbum(Album{
        Title:  "The Modern Sound of Betty Carter",
        Artist: "Betty Carter",
        Price:  49.99,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ID of added album: %v\n", albID)
}

// albumsByArtist queries for albums that have the specified artist name.
func albumsByArtist(name string) ([]Album, error) {
    // An albums slice to hold data from returned rows.
    var albums []Album

    rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
    if err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    defer rows.Close()
    // Loop through rows, using Scan to assign column data to struct fields.
    for rows.Next() {
        var alb Album
        if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
            return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
        }
        albums = append(albums, alb)
    }
    if err := rows.Err(); err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    return albums, nil
}

// albumByID queries for the album with the specified ID.
func albumByID(id int64) (Album, error) {
    // An album to hold data from the returned row.
    var alb Album

    row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
    if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
        if err == sql.ErrNoRows {
            return alb, fmt.Errorf("albumsById %d: no such album", id)
        }
        return alb, fmt.Errorf("albumsById %d: %v", id, err)
    }
    return alb, nil
}

// addAlbum adds the specified album to the database,
// returning the album ID of the new entry
func addAlbum(alb Album) (int64, error) {
    result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    id, err := result.LastInsertId()
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    return id, nil
}

Tutorial: Developing a RESTful API with Go and Gin

  • Design API endpoints
    • /albums
    • /albums/:id
    • c *gin.Context c 是一個指向 gin.Context 結構體的指標,優點:
      • 傳址效率更高,可降低內存使用,因為傳遞一個大型結構體時,傳值會複製該結構體的每一個欄位,這樣會消耗更多的記憶體和 CPU 資源

Tutorial: Getting started with generics

  • 寫法:放在 []
  • 限制型別(Constraints)
func SumNumbers[T int | float64](a, b T) T {
    return a + b
}
package main

import "fmt"

type Number interface {
    int64 | float64
}

func main() {
    // Initialize a map for the integer values
    ints := map[string]int64{
        "first": 34,
        "second": 12,
    }

    // Initialize a map for the float values
    floats := map[string]float64{
        "first": 35.98,
        "second": 26.99,
    }

    fmt.Printf("Non-Generic Sums: %v and %v\n",
        SumInts(ints),
        SumFloats(floats))

    fmt.Printf("Generic Sums: %v and %v\n",
        SumIntsOrFloats[string, int64](ints),
        SumIntsOrFloats[string, float64](floats))

    fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
        SumIntsOrFloats(ints),
        SumIntsOrFloats(floats))

    fmt.Printf("Generic Sums with Constraint: %v and %v\n",
        SumNumbers(ints),
        SumNumbers(floats))
}

// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}

// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}

// SumIntsOrFloats sums the values of map m. It supports both floats and integers
// as map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

// SumNumbers sums the values of map m. Its supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

Q&A

  • Gin 框架中,使用 Context 這個名稱是為了表示這個物件與當前的 HTTP 請求的「上下文」有關。Context 一詞表示這個物件包含了有關請求處理的所有相關信息和操作,讓開發者可以在處理請求的過程中方便地存取和操作請求及回應。
    • why? 1. 什麼是 context? Go 的 context 是一個用來在同一請求的不同協程間傳遞訊息的機制,它能夠:
  • 取消操作:當一個操作不再需要進行時,透過 context 可以通知相關的協程停止運行。
  • 設置超時:允許設置一個超時時間,超過時間後自動取消操作。
  • 傳遞元數據:可以在請求處理過程中,跨不同的協程傳遞一些與請求相關的數據(如用戶 ID、認證資訊等)。 2. context 的用途 在需要並發處理的場景,特別是當有長時間運行的任務時,context 非常有用。例如: • 處理 HTTP 請求時,每個請求可能會啟動多個協程來執行子任務,而如果請求被取消了,這些子任務也需要立刻停止執行。 • 操作數據庫查詢或外部 API 時,為了避免請求卡死,通常會設置一個超時,context 可以幫助實現這種超時控制。 3. 常見的 context 用法 Go 中的 context 有幾個常見的使用模式: • 背景上下文 (context.Background()): • 這是用來初始化 context 的根節點,它表示一個空的上下文,通常作為 context 的起點。
ctx := context.Background()
  • 帶取消功能的上下文 (context.WithCancel())
    • 這會創建一個新的 context,並且可以使用 cancel 函數來手動取消這個上下文。
ctx, cancel := context.WithCancel(context.Background())
// 需要時調用 cancel() 來取消此上下文
cancel()
  • 帶超時功能的上下文 (context.WithTimeout())
    • 用來設置一個操作的超時時間,超過時間自動取消操作。
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() // 超過 5 秒自動取消
  • 帶截止時間的上下文 (context.WithDeadline())
    • 類似於 WithTimeout(),但直接指定一個具體的時間點。
deadline := time.Now().Add(1 * time.Hour)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
  • 傳遞資料 (context.WithValue())
    • 可以將一些關聯數據傳遞到 context 中,例如用戶 ID 等,在請求處理過程中不同協程可以共享這些數據。
ctx := context.WithValue(context.Background(), "userID", 123)
userID := ctx.Value("userID")
  • 4. context 的應用場景
  • HTTP 請求處理: 當處理一個 HTTP 請求時,如果該請求被取消或超時,我們可以通過 context 來通知所有處理該請求的協程停止操作。
  • 長時間運行的任務: 在進行一些需要長時間運行的任務時,context 可以幫助控制任務的生命週期,確保在適當的時候可以中止任務,節省資源。 • 並發操作: 當我們在多個協程中進行併發操作時,context 可以用來協調這些協程的執行狀態,比如某個協程完成或取消時,通知其他協程一起停止。
package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

// album represents data about a record album.
type album struct {
    ID     string  `json:"id"`
    Title  string  `json:"title"`
    Artist string  `json:"artist"`
    Price  float64 `json:"price"`
}

// albums slice to seed record album data.
var albums = []album{
    {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
    {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
    {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)
    router.GET("/albums/:id", getAlbumByID)
    router.POST("/albums", postAlbums)

    router.Run("localhost:8080")
}

// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, albums)
}

// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
    var newAlbum album

    // Call BindJSON to bind the received JSON to
    // newAlbum.
    if err := c.BindJSON(&newAlbum); err != nil {
        return
    }

    // Add the new album to the slice.
    albums = append(albums, newAlbum)
    c.IndentedJSON(http.StatusCreated, newAlbum)
}

// getAlbumByID locates the album whose ID value matches the id
// parameter sent by the client, then returns that album as a response.
func getAlbumByID(c *gin.Context) {
    id := c.Param("id")

    // Loop through the list of albums, looking for
    // an album whose ID value matches the parameter.
    for _, a := range albums {
        if a.ID == id {
            c.IndentedJSON(http.StatusOK, a)
            return
        }
    }
    c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}

web

  • https://gowebexamples.com

Routing (mux)

  • https://gowebexamples.com/routes-using-gorilla-mux/

Middleware

  • https://gowebexamples.com/basic-middleware/

JSON

  • https://gowebexamples.com/json/

Websocket

  • https://gowebexamples.com/websockets/

crypto / password hashing

  • https://gowebexamples.com/password-hashing/

gRPC

install

  • https://grpc.io/docs/protoc-installation/

Quickstart

  • https://grpc.io/docs/languages/go/quickstart/

Basic

  • https://grpc.io/docs/languages/go/basics/
  • Define a service in a .proto file.
  • Generate server and client code using the protocol buffer compiler.
  • Use the Go gRPC API to write a simple client and server for your service.
  • why gRPC?
  1. 寫proto file
  2. 產生 protoc --go_out=. --go_opt=paths=source_relative
    --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/rating.proto
proto file import 問題

solution: protoc -I=. --go_out=$GOROOT/src --go-grpc_out=require_unimplemented_servers=false:$GOROOT/src proto/**.proto

protoc -I=. --go_out=$GOROOT/src --go-grpc_out=$GOROOT/src proto/rating.proto

Mongodb

go get go.mongodb.org/mongo-driver/mongo

Mongodb grpc go

syntax = "proto3";

option go_package = "alanhc/ratingpb";
message Rating {
  // 教師姓名
  string teacher = 1;

  // 科目名稱
  string subject = 2;

  // 顯示名稱
  string name = 3;

  // 評分內容
  string content = 4;

  // 建立時間
  string created_at = 5;

  // 修改時間
  string modified_at = 6;


  // 作業評分
  float rate_homework = 9;
  // 學習評分
  float rate_learning = 10;

  // 推薦評分
  float rate_recommendation = 11;

}

message ListRatingReq {
  string teacher = 1;
  string subject = 2;
}

message ListRatingRes {
  Rating rating = 1;
}

service ratingService {
  rpc ListRating(ListRatingReq) returns (stream ListRatingRes);
}
package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"os"
	"os/signal"

	pb "alanhc/ratingpb"

	"github.com/joho/godotenv"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"google.golang.org/grpc"
)

var db *mongo.Database

type RatingItem struct {
	Teacher            string  `bson:"teacher"`
	Subject            string  `bson:"subject"`
	Name               string  `bson:"name"`
	Content            string  `bson:"content"`
	CreatedAt          string  `bson:"createdAt"`
	ModifiedAt         string  `bson:"modifiedAt"`
	RateHomework       float32 `bson:"rateHomework"`
	RateLearning       float32 `bson:"rateLearning"`
	RateRecommendation float32 `bson:"rateRecommendation"`
}

func (s *RatingServiceServer) ListRating(req *pb.ListRatingReq, stream pb.RatingService_ListRatingServer) error {
	fmt.Println("ReadRating function was invoked with ", req.Teacher, req.Subject)

	cursor, err := db.Collection("rating").Find(context.Background(), bson.M{"teacher": req.Teacher, "subject": req.Subject})
	if err != nil {
		log.Fatalf("Error finding rating: %v", err)
	}
	defer cursor.Close(context.Background())
	data := &RatingItem{}
	for cursor.Next(context.Background()) {
		// var data pb.Rating
		err := cursor.Decode(data)
		if err != nil {
			log.Fatalf("Error decoding data: %v", err)
		}
		// fmt.Println(data)
		// ratingPb := data.toRatingPB()
		if err := stream.Send(&pb.ListRatingRes{
			Rating: &pb.Rating{
				Teacher:            data.Teacher,
				Subject:            data.Subject,
				Name:               data.Name,
				Content:            data.Content,
				CreatedAt:          data.CreatedAt,
				ModifiedAt:         data.ModifiedAt,
				RateHomework:       data.RateHomework,
				RateLearning:       data.RateLearning,
				RateRecommendation: data.RateRecommendation,
			},
		}); err != nil {
			log.Fatalf("Error sending data to client: %v", err)
		}

	}
	err = cursor.Close(context.Background())
	if err := cursor.Err(); err != nil {
		log.Fatalf("Error cursor.Err(): %v", err)
	}
	return nil
}

type RatingServiceServer struct {
}

// ListRating implements ratingpb.RatingServiceServer.
// func (s *RatingServiceServer) ListRating(req *pb.ListRatingReq, stream pb.RatingService_ListRatingServer) error {
// 	fmt.Println("ReadRating function was invoked with ", req.Teacher, req.Subject)

// 	cursor, err := db.Collection("rating").Find(context.Background(), bson.M{"teacher": req.Teacher, "subject": req.Subject})
// 	if err != nil {
// 		log.Fatalf("Error finding rating: %v", err)
// 	}
// 	defer cursor.Close(context.Background())
// 	data := &RatingItem{}
// 	for cursor.Next(context.Background()) {
// 		// var data pb.Rating
// 		err := cursor.Decode(data)
// 		if err != nil {
// 			log.Fatalf("Error decoding data: %v", err)
// 		}
// 		// fmt.Println(data)
// 		// ratingPb := data.toRatingPB()
// 		if err := (*stream).Send(&pb.ListRatingRes{ // Fix: Dereference the pointer to the interface before calling Send method
// 			Rating: &pb.Rating{
// 				Teacher:            data.Teacher,
// 				Subject:            data.Subject,
// 				Name:               data.Name,
// 				Content:            data.Content,
// 				CreatedAt:          data.CreatedAt,
// 				ModifiedAt:         data.ModifiedAt,
// 				RateHomework:       data.RateHomework,
// 				RateLearning:       data.RateLearning,
// 				RateRecommendation: data.RateRecommendation,
// 			},
// 		}); err != nil {
// 			log.Fatalf("Error sending data to client: %v", err)
// 		}
// 		//stream.Send(data)
// 	}
// 	err = cursor.Close(context.Background())
// 	if err := cursor.Err(); err != nil {
// 		log.Fatalf("Error cursor.Err(): %v", err)
// 	}
// 	return nil

// }

// ListRating implements ratingpb.RatingServiceServer.
// func (s *RatingServiceServer) ListRating(ctx context.Context, req *pb.ListRatingReq, stream *pb.RatingService_ListRatingServer) error {
// 	fmt.Println("ReadRating function was invoked with ", req.Teacher, req.Subject)

// 	cursor, err := db.Collection("rating").Find(context.Background(), bson.M{"teacher": req.Teacher, "subject": req.Subject})
// 	if err != nil {
// 		log.Fatalf("Error finding rating: %v", err)
// 	}
// 	defer cursor.Close(context.Background())
// 	data := &RatingItem{}
// 	for cursor.Next(context.Background()) {
// 		// var data pb.Rating
// 		err := cursor.Decode(data)
// 		if err != nil {
// 			log.Fatalf("Error decoding data: %v", err)
// 		}
// 		// fmt.Println(data)
// 		// ratingPb := data.toRatingPB()
// 		if err := (*stream).Send(&pb.ListRatingRes{ // Fix: Dereference the pointer to the interface before calling Send method
// 			Rating: &pb.Rating{
// 				Teacher:            data.Teacher,
// 				Subject:            data.Subject,
// 				Name:               data.Name,
// 				Content:            data.Content,
// 				CreatedAt:          data.CreatedAt,
// 				ModifiedAt:         data.ModifiedAt,
// 				RateHomework:       data.RateHomework,
// 				RateLearning:       data.RateLearning,
// 				RateRecommendation: data.RateRecommendation,
// 			},
// 		}); err != nil {
// 			log.Fatalf("Error sending data to client: %v", err)
// 		}
// 		//stream.Send(data)
// 	}
// 	err = cursor.Close(context.Background())
// 	if err := cursor.Err(); err != nil {
// 		log.Fatalf("Error cursor.Err(): %v", err)
// 	}
// 	return nil
// }

var mongoCtx context.Context

func main() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal("Error loading .env file:", err)
	}
	CONNECTION_STRING := os.Getenv("CONNECTION_STRING")
	// fmt.Println(CONNECTION_STRING)
	fmt.Println("Starting server on port :50051...")
	lis, err := net.Listen("tcp", "localhost:50051")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	var grpc_opts []grpc.ServerOption
	srv := &RatingServiceServer{}
	// var srv *RatingServiceServer
	grpcServer := grpc.NewServer(grpc_opts...)
	pb.RegisterRatingServiceServer(grpcServer, srv)
	// Initialize MongoDb client
	fmt.Println("Connecting to MongoDB...")
	mongoCtx = context.Background()
	serverAPI := options.ServerAPI(options.ServerAPIVersion1)
	opts := options.Client().ApplyURI(CONNECTION_STRING).SetServerAPIOptions(serverAPI)
	// Create a new client and connect to the server
	client, err := mongo.Connect(context.TODO(), opts)
	// 連線到 MongoDB
	if err != nil {
		panic(err)
	}
	err = client.Ping(mongoCtx, nil)
	if err != nil {
		log.Fatalf("Could not connect to MongoDB: %v\n", err)
	} else {
		fmt.Println("Connected to Mongodb")
	}
	db = client.Database("school")

	defer func() {
		if err = client.Disconnect(context.TODO()); err != nil {
			panic(err)
		}
	}()
	// Send a ping to confirm a successful connection
	var result bson.M
	if err := client.Database("admin").RunCommand(context.TODO(), bson.D{{"ping", 1}}).Decode(&result); err != nil {
		panic(err)
	}
	fmt.Println("Pinged your deployment. You successfully connected to MongoDB!")

	fmt.Println("Server is running on port 50051")
	defer lis.Close()
	// Start the server in a child routine
	go func() {
		if err := grpcServer.Serve(lis); err != nil {
			log.Fatalf("failed to serve: %v", err)
		}
	}()
	fmt.Println("Server succesfully started on port :50051")
	// Create a channel to receive OS signals
	c := make(chan os.Signal)
	// Relay os.Interrupt to our channel (os.Interrupt = CTRL+C)
	// Ignore other incoming signals
	signal.Notify(c, os.Interrupt)
	// Block main routine until a signal is received
	// As long as user doesn't press CTRL+C a message is not passed
	// And our main routine keeps running
	// If the main routine were to shutdown so would the child routine that is Serving the server
	<-c
	fmt.Println("\nStopping the server...")
	grpcServer.Stop()
	lis.Close()
	fmt.Println("Closing MongoDB connection")
	// db.Disconnect(mongoCtx)
	fmt.Println("Done.")

}

CLI

  • Cobra CLI
  • https://github.com/spf13/cobra

(待看) Deploy Go Apps on Google Cloud Serverless Platforms

  • https://www.cloudskillsboost.google/focuses/10532?parent=catalog

教學網站

  • https://go.dev/learn/

Ref

sample code

CRUD example - http + pg

package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"

	_ "github.com/lib/pq"
)

type Student struct {
	ID   int
	Name string
	Age  int
}

func main() {
	db, err := sql.Open("postgres", "user=postgres dbname=school sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close() // 確保程式結束前關閉資料庫連線

	// 建立路由
	http.HandleFunc("/hello", hello)
	http.HandleFunc("/students", listStudents(db))
	http.HandleFunc("/students/create", createStudent(db))
	http.HandleFunc("/students/update", updateStudent(db))
	http.HandleFunc("/students/delete", deleteStudent(db))
	log.Fatal(http.ListenAndServe(":8080", nil)) // 如果錯誤就記錄錯誤

}
func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello, World!")
}
func listStudents(db *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		rows, err := db.Query("SELECT * FROM students")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer rows.Close()
		for rows.Next() {
			var s Student
			if err := rows.Scan(&s.ID, &s.Name, &s.Age); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			fmt.Fprintf(w, "%d %s %d\n", s.ID, s.Name, s.Age)
		}
	}
}
func createStudent(db *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		r.ParseForm()
		name := r.FormValue("name")
		age := r.FormValue("age")
		_, err := db.Exec("INSERT INTO students (name, age) VALUES ($1, $2)", name, age)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		fmt.Fprint(w, "created")
	}
}
func updateStudent(db *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		r.ParseForm()
		id := r.FormValue("id")
		name := r.FormValue("name")
		age := r.FormValue("age")
		_, err := db.Exec("UPDATE students SET name=$1, age=$2 WHERE id=$3", name, age, id)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		fmt.Fprint(w, "updated")
	}
}
func deleteStudent(db *sql.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		r.ParseForm()
		id := r.FormValue("id")
		_, err := db.Exec("DELETE FROM students WHERE id=$1", id)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		fmt.Fprint(w, "deleted")
	}
}