”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 使用 Golang 使用 Api 网关模式构建基本的微服务在线商店后端 - 第 1 部分

使用 Golang 使用 Api 网关模式构建基本的微服务在线商店后端 - 第 1 部分

发布于2024-11-08
浏览:310

Introduction

Hey, fellow developers! ? Ever thought about building a microservices architecture but felt overwhelmed by where to start? Worry no more! In this article, we'll build a basic microservices setup using the API Gateway pattern for an online store. And guess what? We'll do it all in Go (Golang)! ?

What You'll Learn

By the end of this guide, you’ll know how to:

  • Implement the API Gateway pattern in a microservices architecture.
  • Use gRPC for seamless communication between services.
  • Set up multiple microservices in Go with Docker.
  • Connect the dots and make everything work smoothly together!

Why Microservices? Why Go? Why API Gateway? ?

Before we dive into the code, let’s talk about why:

  • Microservices: Scalability, independent deployment, and flexibility.
  • Go: Fast, efficient, and ideal for building microservices.
  • **API **Gateway: Centralized entry point to handle all client requests and route them to the appropriate microservice.

Who said that? Me ?. Just kidding, but you'll soon be quoting this too when your app handles a gazillion users without breaking a sweat! Imagine your API dancing through the traffic while sipping coffee ☕. Yes, that’s the power of Microservices with Go and an API Gateway.

Oke, without further ado let's start it.

1. Setting Up Your Environment ?️

  • Install and download Go https://go.dev/doc/install
  • Use docker for your microservices https://docs.docker.com/engine/install/
  • Just use IDE that you want

2. Setup you project

/online-store
├── api-gateway/
├──── cmd/
├────── main.go
├──── internal/
├────── handler/
├──────── handler.go
├── services/
├──── user-service/
├────── cmd/
├──────── main.go
├────── internal/
├──────── handler/
├────────── handler.go
├────── proto/
├──────── user-proto
├────── Dockerfile
├──── /
├── docker-compose.yml
└── README.md

That's will be the dir structure the project, you can tweak it as you want, later we will also create pb directory do store generated pb file from our proto file.

Clone googleapis https://github.com/googleapis/googleapis, we will need that for our proto later. Just clone in root dir under online-store dir.

git clone https://github.com/googleapis/googleapis.git

3. Building the User Service ?

  • Initiate Go Mod
    Let's use our terminal and initiate our user-service go mod init
    go mod init user-service
    you can change "user-service" with your github url, but we will use it for now.

  • Create our first proto file for user
    create a new file under user-service/proto dir with name user.proto, let's use this proto code:

syntax = "proto3";

package order;

option go_package = ".";

import "google/api/annotations.proto";

service OrderService {
    rpc GetMyOrder(GetMyOrderRequest) returns (GetMyOrderResponse) {
        option (google.api.http) = {
            get: "/v1/order/my"
        };
    }
}

message GetMyOrderRequest {
    string user_id = 1;
}

message GetMyOrderResponse {
    string  user_id = 1;
    string order_id = 2;
    string product_id = 3;
    int32 quantity = 4;
    float price = 5;
    string status = 6;
}
  • Use protoc to generate gRPC First, we need to create pb dir under service/user-service to store our generated grpc files and run this command to generate our gRPC:
protoc --proto_path="services/user-service/proto" \
        --go_out="services/user-service/pb" \
        --go-grpc_out="services/user-service/pb" \
        --grpc-gateway_out="services/user-service/pb" \
        "services/user-service/proto/user.proto"

With that command we will generate 3 files (user_grpc.pb.go, user_pb.go, and user.pb.gw.go) and will place them into services/user-service/pb directory.

But, because we want use the same grpc to our Api Gateway, we need to copy them too into api-gateway/pb directory. You can copy it manually each time you generate grpc, but let's just use script for it.

I create a new dir online-store/scripts to store all scripts. Let's create a new file generate-proto.sh, and put this code:

#!/bin/bash

# Error handling function
handle_error() {
  echo "Error occurred in script at line: $1"
  exit 1
}

# Trap any error and call the handle_error function
trap 'handle_error $LINENO' ERR

# Declare an associative array to map proto directories to their corresponding pb directories
declare -A dir_map=(
  ["services/user-service/proto"]="services/user-service/pb"
  # you can add another directory here
  # e.g ["services/order-service/proto"]="services/order-service/pb"
)

# Define Static Dir Path
GOOGLEAPIS_DIR="googleapis"
API_GATEWAY_PB_DIR="api-gateway/pb"

# Ensure the API_GATEWAY_PB_DIR exists
if [ ! -d "$API_GATEWAY_PB_DIR" ]; then
  mkdir -p "$API_GATEWAY_PB_DIR"
  echo "Directory $API_GATEWAY_PB_DIR created."
else 
  echo "Directory $API_GATEWAY_PB_DIR already exists."
fi

# Loop through the associative array and generate Go code for each proto directory
for proto_dir in "${!dir_map[@]}"; do
  pb_dir="${dir_map[$proto_dir]}"

  # Check if the pb directory exists, if not, create it
  if [ ! -d "$pb_dir" ]; then
    mkdir -p "$pb_dir"
    echo "Directory $pb_dir created."
  else
    echo "Directory $pb_dir already exists."
  fi

  # Process each .proto file in the proto directory
  for proto_file in "$proto_dir"/*.proto; do
    # Ensure the proto file exists
    if [ -f "$proto_file" ]; then
      # Generate Go code for the current proto file
      protoc --proto_path="$proto_dir" \
        --proto_path="$GOOGLEAPIS_DIR" \
        --go_out="$pb_dir" \
        --go-grpc_out="$pb_dir" \
        --grpc-gateway_out="$pb_dir" \
        "$proto_file"
      echo "Generated Go code for $proto_file"

      # Copy the generated Go code to the API Gateway directory
      cp -auv "$pb_dir"/* "$API_GATEWAY_PB_DIR/"
      echo "Copied generated Go code to $API_GATEWAY_PB_DIR from $pb_dir"
    else
      echo "No .proto files found in $proto_dir."
    fi
  done
done

That script will create you a new pb directory if it's does not exist.

Now, lets execute our script:

./scripts/generate-proto.sh

You will need to install some packages:

go get github.com/grpc-ecosystem/grpc-gateway/v2
go get google.golang.org/genproto/googleapis/api
go get .golang.org/protobuf

If you get some error regarding import, do this comman go mod tidy

  • Create our user handler code create a user-handler.go file under services/user-services/internal/handler directory, use let's use this simple code:
package handler

import (
    "context"
    "log"

    pb "user-service/pb"

    "google.golang.org/grpc"
)

// server implements the UserServiceServer interface
type server struct {
    pb.UnimplementedUserServiceServer
}

// NewServer creates a new instance of server
func NewServer() pb.UserServiceServer {
    return &server{}
}

// Implement the methods defined in your proto file here
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
    // Log the request details
    log.Printf("Received GetUser request with ID: %s", req.GetId())

    // Implement your logic to get user information
    response := &pb.GetUserResponse{
        Id:    req.GetId(),
        Name:  "John Doe",
        Email: "[email protected]",
    }

    // Log the response details
    log.Printf("Returning GetUser response: % v", response)

    return response, nil
}

// Implement GetUserProfile method
func (s *server) GetUserProfile(ctx context.Context, req *pb.GetUserProfileRequest) (*pb.GetUserProfileResponse, error) {
    // Log the request details
    log.Printf("Received GetUserProfile request with ID: %s", req.GetId())

    response := &pb.GetUserProfileResponse{
        Id:      req.GetId(),
        Name:    "John Doe",
        Email:   "[email protected]",
        Phone:   "1234567890",
        Address: "123 Main St",
    }

    // Log the response details
    log.Printf("Returning GetUserProfile response: % v", response)

    return response, nil
}

// RegisterServices registers the gRPC services with the server
func RegisterServices(s *grpc.Server) {
    pb.RegisterUserServiceServer(s, NewServer())
}

You will need to install some package:

go get google.golang.org/grpc
  • Create our main.go file in user-service/cmd/main.go We use this simple code for our main code:
package main

import (
    "log"
    "net"

    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"

    "user-service/internal/handler"
)

func main() {
    // Create a new gRPC server
    s := grpc.NewServer()

    // Register the server with the gRPC server
    handler.RegisterServices(s)

    // Register reflection service on gRPC server
    reflection.Register(s)

    // Listen on port 50051
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    // Start the gRPC server
    log.Println("Starting gRPC server on :50051")
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

You need to install google.golang.org/grpc/reflection, with this package we can look into our services.

  • Create Dockerfile for user-service create a new file in user-service directory with name Dockerfile, use this for our Dockerfile:
# Stage 1: Build
FROM golang:1.23 AS builder

WORKDIR /app

# Copy go mod and sum files
COPY go.mod go.sum ./
RUN go mod download

# Copy the application code
COPY . .

# Build the Go application
RUN go build -o user-service ./cmd

# Stage 2: Run
FROM ubuntu:22.04

# Install necessary libraries
RUN apt-get update && apt-get install -y \
    ca-certificates \
    libc6 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy the binary from the build stage
COPY --from=builder /app/user-service /app/user-service

ENTRYPOINT ["/app/user-service"]

# Expose port
EXPOSE 50051

4. Build Api Gateway

Because we already have pb files generated under api-gateway/pb directory. Now, we can create handler for our api-gateway, create a new file api-gateway/internal/handler/service-regitry.go, use this code to register our services:

package handler

import (
    "context"
    "log"

    "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    "google.golang.org/grpc"

    pb "api-gateway/pb"
)

// ServiceConfig holds the configuration for each service.
type ServiceConfig struct {
    Name    string
    Address string
}

// RegisterServices registers all services with the mux based on the given service configurations.
func RegisterServices(ctx context.Context, mux *runtime.ServeMux, services []ServiceConfig) error {
    for _, svc := range services {
        opts := []grpc.DialOption{grpc.WithInsecure()}
        var err error
        switch svc.Name {
        case "UserService":
            err = pb.RegisterUserServiceHandlerFromEndpoint(ctx, mux, svc.Address, opts)
        // We can create another cases for another services
        default:
            log.Printf("No handler implemented for service %s", svc.Name)
            continue
        }
        if err != nil {
            return err
        }
        log.Printf("Registered service %s at %s", svc.Name, svc.Address)
    }
    return nil
}

You will also need to install these in api-gateway:

go get github.com/grpc-ecosystem/grpc-gateway
go get google.golang.org/grpc
  • Create our main.go in Api Gateway Now, create a new file api-gateway/cmd/main.go for our main code, use this code:
package main

import (
    "context"
    "log"
    "net/http"

    "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"

    "api-gateway/internal/handler"
)

func main() {
    // Define service configurations
    services := []handler.ServiceConfig{
        {Name: "UserService", Address: "user-service:50051"},
        // You can add another services here
    }

    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    mux := runtime.NewServeMux()

    // Register services
    if err := handler.RegisterServices(ctx, mux, services); err != nil {
        log.Fatalf("Failed to register services: %v", err)
    }

    // Start the HTTP server
    if err := http.ListenAndServe(":8080", mux); err != nil {
        log.Fatalf("Failed to start HTTP server: %v", err)
    }
}

  • Create Dockerfile for Api Gateway We also need Dockerfile for Api Gateway, create a new file api-gateway/Dockerfile and use this config:
# Stage 1: Build
FROM golang:1.23 AS builder

WORKDIR /app

# Copy go mod and sum files
COPY go.mod go.sum ./
RUN go mod download

# Copy the application code
COPY . .

# Build the Go application
RUN go build -o main ./cmd

# Stage 2: Run
FROM ubuntu:22.04

# Install necessary libraries
RUN apt-get update && apt-get install -y \
    ca-certificates \
    libc6 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy the binary from the build stage
COPY --from=builder /app/main /app/main

ENTRYPOINT ["/app/main"]

# Expose port (if necessary)
EXPOSE 8080
  • Create docker-compose.yml Oke, we also need to create a online-store/docker-compose.yml file, use use copy:
version: '4.0'
services:
  api-gateway:
    build: ./api-gateway
    ports:
      - "8080:8080"
    depends_on:
      - user-service

  user-service:
    build: 
      context: ./services/user-service
      dockerfile: Dockerfile
    ports:
      - "50051:50051"

  # Can put another service here

5. Start our Microservice

Because we use docker, make sure your docker already active.
And we can run with this command:

docker-compose up --build -d

You will see your services already up. (I use windows)

Build basic microservice online store backend with Golang use Api Gateway Pattern - Part 1

You can also use this command to see active service:

docker ps

Build basic microservice online store backend with Golang use Api Gateway Pattern - Part 1

You see that i also have another services, you can also add it to your code.

6. Hit our endpoint

I use postman to hit user-service endpoint

Build basic microservice online store backend with Golang use Api Gateway Pattern - Part 1

Because we also put log code in our user-service, we can do this command to look into our service logs:

docker logs --follow user-service-1

Make sure service name by looks into our active service docker ps

Then you will see this log:

Build basic microservice online store backend with Golang use Api Gateway Pattern - Part 1

Conclusion ?

Congratulations! ? You've just built a basic microservices architecture for an online store using Go, gRPC, Docker. Keep experimenting and improving your setup. We will continue to build our online-store until finish, stay tune ?‍??

Repository: https://github.com/agustrinaldokurniawan/online-store/tree/main/backend

版本声明 本文转载于:https://dev.to/agustrinaldokurniawan/build-basic-microservice-online-store-backend-with-golang-use-api-gateway-pattern-1bf?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • Python 中的实例方法与类方法:什么时候应该使用“self”和“cls”?
    Python 中的实例方法与类方法:什么时候应该使用“self”和“cls”?
    深入研究类和实例方法的细微差别:Beyond Self 与 ClsPython 增强提案 (PEP) 8 建议使用“self”作为实例方法中的第一个参数,“cls”作为类方法中的第一个参数。这种区别源于这些方法在处理实例和类时所扮演的不同角色。实例方法:自我优势实例方法在实例的实例上调用班级。它们通...
    编程 发布于2024-11-09
  • 在 Python 中使用 bytes(n) 时,与数字转换的主要区别是什么?
    在 Python 中使用 bytes(n) 时,与数字转换的主要区别是什么?
    Python 中的字节对象:超越数字转换在 Python 中使用字节对象时,必须了解 bytes(n) 是如何转换的函数与数值转换不同。将整数 n 传递给 bytes(n) 不会返回 n 的二进制表示形式,而是创建一个长度为 n 且填充有空字节 (\x00) 的字节字符串。行为背后的基本原理此行为是...
    编程 发布于2024-11-09
  • 如何在 MySQL 中将纪元时间戳转换为人类可读的日期?
    如何在 MySQL 中将纪元时间戳转换为人类可读的日期?
    在 MySQL 中将纪元时间戳转换为人类可读的日期在 MySQL 中,纪元时间戳是日期和时间的数字表示形式。它是自 Unix 纪元(即 1970 年 1 月 1 日 00:00:00 UTC)以来的毫秒数。要将纪元时间戳转换为人类可读的日期,您可以使用 from_unixtime( ) 功能。该函数...
    编程 发布于2024-11-09
  • 如何使用 Pip 获取可用软件包版本列表:综合指南
    如何使用 Pip 获取可用软件包版本列表:综合指南
    如何使用 Pip 获取可用包版本列表:综合指南Pip 是一个广泛使用的 Python 包安装程序,提供了一个安装和管理 Python 包的有效方法。虽然它允许方便地安装特定的软件包版本,但在选择最佳版本之前可能有必要探索所有可能版本的综合列表。本文深入探讨了如何在各种 pip 版本中实现此目的。Pi...
    编程 发布于2024-11-09
  • ## **`std::vector::erase`返回的迭代器在删除后是否指向有效元素?**
    ## **`std::vector::erase`返回的迭代器在删除后是否指向有效元素?**
    std::vector 迭代器失效:详细解释std::vector 中迭代器失效的概念经常被讨论。需要澄清的是,通过 std::vector::erase 擦除向量元素会使严格位于已擦除元素之后的迭代器无效。但是,位于已擦除元素的确切位置的迭代器的有效性仍然不确定。从逻辑上讲,人们可能会假设该迭代器...
    编程 发布于2024-11-09
  • Python 开发人员如何增强调试技术以获得更高效的代码?
    Python 开发人员如何增强调试技术以获得更高效的代码?
    Python 中增强的调试技术增强 Python 中的调试过程对于寻求优化代码的开发人员至关重要。以下是一些帮助您完成此任务的宝贵提示:利用 PDB 模块PDB(Python 调试器)模块提供了全面的调试环境。通过将 pdb.set_trace() 集成到代码中,您可以在特定位置建立断点。这个灵活的...
    编程 发布于2024-11-09
  • AdaBoost - 集成方法,分类:监督机器学习
    AdaBoost - 集成方法,分类:监督机器学习
    Boosting Definition and Purpose Boosting is an ensemble learning technique used in machine learning to improve the accuracy of models...
    编程 发布于2024-11-09
  • 如何修复 macOS 上 Django 中的“配置不正确:加载 MySQLdb 模块时出错”?
    如何修复 macOS 上 Django 中的“配置不正确:加载 MySQLdb 模块时出错”?
    MySQL配置不正确:相对路径的问题在Django中运行python manage.py runserver时,可能会遇到以下错误:ImproperlyConfigured: Error loading MySQLdb module: dlopen(/Library/Python/2.7/site-...
    编程 发布于2024-11-09
  • 重新学习CS基础知识——实现队列
    重新学习CS基础知识——实现队列
    你曾经站在队列中吗,队列数据结构也做同样的事情。当你想在你最喜欢的自助餐厅点餐时,你站在队伍的最后,然后你就可以继续排队并离开。 CS 中的队列数据结构执行相同的功能。队列数据结构是先进先出的数据结构。队列数据结构可以使用两个基本函数 Enqueue 和 Dequeue 来构建,这两个函数基本上是...
    编程 发布于2024-11-09
  • 为 Angular 18 设置 linter 和 IDE
    为 Angular 18 设置 linter 和 IDE
    将 eslint、prettier、env 添加到应用程序中。 遗憾的是,Angular 默认情况下不会自行生成这一切。更改原理图可以提高数千个 Angular 项目的质量。 设置 eslint 9 连接 eslint: yarn ng add @angular-eslint/sch...
    编程 发布于2024-11-09
  • 使用 JavaScript 进行网页抓取和代理设置的初学者指南
    使用 JavaScript 进行网页抓取和代理设置的初学者指南
    使用JavaScript代码模拟用户操作,获取所需信息。包括模拟用户打开网页、点击链接、输入关键字等操作,并从网页中提取所需信息。 Javascript网页抓取的核心原理 使用JavaScript代码模拟用户操作来获取所需信息。包括模拟用户打开网页、点击链接、输入关键字等操作,并从网...
    编程 发布于2024-11-09
  • 在 Android 上运行 Llama:使用 Ollama 的分步指南
    在 Android 上运行 Llama:使用 Ollama 的分步指南
    Llama 3.2 最近在 Meta 开发者大会上推出,展示了令人印象深刻的多模式功能以及针对使用高通和联发科技硬件的移动设备进行优化的版本。这一突破使开发人员能够在移动设备上运行 Llama 3.2 等强大的 AI 模型,为更高效、私密和响应迅速的 AI 应用程序铺平道路。 Meta 发布了 Ll...
    编程 发布于2024-11-09
  • 如何在 Python 中格式化字符串以将它们对齐直列?
    如何在 Python 中格式化字符串以将它们对齐直列?
    以固定宽度打印字符串打印字符串时,将它们对齐成直列可以增强可读性。在 Python 中使用 format 或 f-string 提供了实现此目的的便捷方法。使用 str.format()str.format() 提供了一种简单的填充方法字符串。其语法包括占位符 {},后跟格式化表达式。对于左对齐,请...
    编程 发布于2024-11-09
  • 为什么微服务比单体架构重要
    为什么微服务比单体架构重要
    在当今快节奏的技术环境中,企业需要可扩展且灵活的解决方案来快速适应不断变化的需求。与传统的整体方法相比,这就是微服务架构的亮点。 1.什么是单体架构? 单体架构是一个单一的、统一的系统,其中所有组件都是互连和相互依赖的。这意味着对系统的任何更改或更新都需要重新构建和重新部署整个应用程...
    编程 发布于2024-11-09
  • 如何在 PHP 中访问对象属性:了解语法和错误解决方案
    如何在 PHP 中访问对象属性:了解语法和错误解决方案
    理解 PHP 对象属性访问在 PHP 中,访问对象属性对于处理复杂的数据结构至关重要。属性保存与对象关联的信息,使我们能够管理和操作该数据。访问对象属性有两种常用语法:1。 $property1此语法直接通过名称访问特定属性。它用于分配或检索各个属性的值。但是,这种方法要求您提前知道确切的属性名称。...
    编程 发布于2024-11-09

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3