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

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

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

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]删除
最新教程 更多>
  • 如何使用替换指令在GO MOD中解析模块路径差异?
    如何使用替换指令在GO MOD中解析模块路径差异?
    在使用GO MOD时,在GO MOD 中克服模块路径差异时,可能会遇到冲突,其中3个Party Package将另一个PAXPANCE带有导入式套件之间的另一个软件包,并在导入式套件之间导入另一个软件包。如回声消息所证明的那样: go.etcd.io/bbolt [&&&&&&&&&&&&&&&&...
    编程 发布于2025-07-03
  • Java是否允许多种返回类型:仔细研究通用方法?
    Java是否允许多种返回类型:仔细研究通用方法?
    在Java中的多个返回类型:一种误解类型:在Java编程中揭示,在Java编程中,Peculiar方法签名可能会出现,可能会出现,使开发人员陷入困境,使开发人员陷入困境。 getResult(string s); ,其中foo是自定义类。该方法声明似乎拥有两种返回类型:列表和E。但这确实是如此吗...
    编程 发布于2025-07-03
  • Go web应用何时关闭数据库连接?
    Go web应用何时关闭数据库连接?
    在GO Web Applications中管理数据库连接很少,考虑以下简化的web应用程序代码:出现的问题:何时应在DB连接上调用Close()方法?,该特定方案将自动关闭程序时,该程序将在EXITS EXITS EXITS出现时自动关闭。但是,其他考虑因素可能保证手动处理。选项1:隐式关闭终止数...
    编程 发布于2025-07-03
  • Java数组中元素位置查找技巧
    Java数组中元素位置查找技巧
    在Java数组中检索元素的位置 利用Java的反射API将数组转换为列表中,允许您使用indexof方法。 (primitives)(链接到Mishax的解决方案) 用于排序阵列的数组此方法此方法返回元素的索引,如果发现了元素的索引,或一个负值,指示应放置元素的插入点。
    编程 发布于2025-07-03
  • 在Java中使用for-to-loop和迭代器进行收集遍历之间是否存在性能差异?
    在Java中使用for-to-loop和迭代器进行收集遍历之间是否存在性能差异?
    For Each Loop vs. Iterator: Efficiency in Collection TraversalIntroductionWhen traversing a collection in Java, the choice arises between using a for-...
    编程 发布于2025-07-03
  • Java中Lambda表达式为何需要“final”或“有效final”变量?
    Java中Lambda表达式为何需要“final”或“有效final”变量?
    Lambda Expressions Require "Final" or "Effectively Final" VariablesThe error message "Variable used in lambda expression shou...
    编程 发布于2025-07-03
  • 如何限制动态大小的父元素中元素的滚动范围?
    如何限制动态大小的父元素中元素的滚动范围?
    在交互式接口中实现垂直滚动元素的CSS高度限制问题:考虑一个布局,其中我们具有与用户垂直滚动一起移动的可滚动地图div,同时与固定的固定sidebar保持一致。但是,地图的滚动无限期扩展,超过了视口的高度,阻止用户访问页面页脚。$("#map").css({ marginT...
    编程 发布于2025-07-03
  • 如何在其容器中为DIV创建平滑的左右CSS动画?
    如何在其容器中为DIV创建平滑的左右CSS动画?
    通用CSS动画,用于左右运动 ,我们将探索创建一个通用的CSS动画,以向左和右移动DIV,从而到达其容器的边缘。该动画可以应用于具有绝对定位的任何div,无论其未知长度如何。问题:使用左直接导致瞬时消失 更加流畅的解决方案:混合转换和左 [并实现平稳的,线性的运动,我们介绍了线性的转换。这...
    编程 发布于2025-07-03
  • 您如何在Laravel Blade模板中定义变量?
    您如何在Laravel Blade模板中定义变量?
    在Laravel Blade模板中使用Elegance 在blade模板中如何分配变量对于存储以后使用的数据至关重要。在使用“ {{}}”分配变量的同时,它可能并不总是最优雅的解决方案。幸运的是,Blade通过@php Directive提供了更优雅的方法: $ old_section =“...
    编程 发布于2025-07-03
  • Python环境变量的访问与管理方法
    Python环境变量的访问与管理方法
    Accessing Environment Variables in PythonTo access environment variables in Python, utilize the os.environ object, which represents a mapping of envir...
    编程 发布于2025-07-03
  • 如何使用Python的请求和假用户代理绕过网站块?
    如何使用Python的请求和假用户代理绕过网站块?
    如何使用Python的请求模拟浏览器行为,以及伪造的用户代理提供了一个用户 - 代理标头一个有效方法是提供有效的用户式header,以提供有效的用户 - 设置,该标题可以通过browser和Acterner Systems the equestersystermery和操作系统。通过模仿像Chro...
    编程 发布于2025-07-03
  • 如何在无序集合中为元组实现通用哈希功能?
    如何在无序集合中为元组实现通用哈希功能?
    在未订购的集合中的元素要纠正此问题,一种方法是手动为特定元组类型定义哈希函数,例如: template template template 。 struct std :: hash { size_t operator()(std :: tuple const&tuple)const {...
    编程 发布于2025-07-03
  • CSS可以根据任何属性值来定位HTML元素吗?
    CSS可以根据任何属性值来定位HTML元素吗?
    靶向html元素,在CSS 中使用任何属性值,在CSS中,可以基于特定属性(如下所示)基于特定属性的基于特定属性的emants目标元素: 字体家庭:康斯拉斯(Consolas); } 但是,出现一个常见的问题:元素可以根据任何属性值而定位吗?本文探讨了此主题。的目标元素有任何任何属性值,属...
    编程 发布于2025-07-03
  • Python中何时用"try"而非"if"检测变量值?
    Python中何时用"try"而非"if"检测变量值?
    使用“ try“ vs.” if”来测试python 在python中的变量值,在某些情况下,您可能需要在处理之前检查变量是否具有值。在使用“如果”或“ try”构建体之间决定。“ if” constructs result = function() 如果结果: 对于结果: ...
    编程 发布于2025-07-03
  • 大批
    大批
    [2 数组是对象,因此它们在JS中也具有方法。 切片(开始):在新数组中提取部分数组,而无需突变原始数组。 令ARR = ['a','b','c','d','e']; // USECASE:提取直到索引作...
    编程 发布于2025-07-03

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

Copyright© 2022 湘ICP备2022001581号-3