Golang中使用gRPC时如何组织代码结构

Golang中使用gRPC时如何组织代码结构 大家好。我刚开始接触GO并用它编写微服务。我练习了几个专门编写后端的项目。我想为自己写一个新闻门户网站。我已经为数据库解析了几个新闻网站。并且我为我的门户网站编写了一个REST管理面板。现在我想在gRPC上编写门户网站本身的后端。问题是如何正确组织代码结构。用gRPC编写的项目示例也会非常有帮助。也欢迎任何处理过gRPC的人提供建议。

2 回复

这种结构示例很常见,https://github.com/GoogleCloudPlatform/microservices-demo

我使用 pb 包来存储 proto 文件,每个微服务都有一个 shell 脚本来获取这个 proto 文件,并将其包含在我的应用程序中,然后生成缓冲区消息,请看链接中的示例

src/
   cartservice/
      pb/
          cart.pb.go (由 genproto.sh 生成)
      genproto.sh
   sessionservice/
      pb/
          session.proto.go (由 genproto.sh 生成)
      genproto.sh
   ....
pb/
   cart.proto
   session.proto

更多关于Golang中使用gRPC时如何组织代码结构的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中使用gRPC时,合理的代码结构对于维护和扩展至关重要。以下是一个典型的项目结构示例:

project/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── handler/
│   │   └── news_handler.go
│   ├── service/
│   │   └── news_service.go
│   └── repository/
│       └── news_repository.go
├── pkg/
│   ├── pb/
│   │   ├── news.pb.go
│   │   └── news_grpc.pb.go
│   └── config/
│       └── config.go
├── proto/
│   └── news.proto
└── go.mod

首先定义proto文件:

// proto/news.proto
syntax = "proto3";

package news;
option go_package = "github.com/yourname/project/pkg/pb";

service NewsService {
    rpc GetArticle(GetArticleRequest) returns (Article);
    rpc ListArticles(ListArticlesRequest) returns (ListArticlesResponse);
}

message GetArticleRequest {
    string id = 1;
}

message Article {
    string id = 1;
    string title = 2;
    string content = 3;
    string created_at = 4;
}

message ListArticlesRequest {
    int32 page = 1;
    int32 page_size = 2;
}

message ListArticlesResponse {
    repeated Article articles = 1;
    int32 total = 2;
}

生成gRPC代码:

protoc --go_out=. --go-grpc_out=. proto/news.proto

实现服务层:

// internal/service/news_service.go
package service

import (
    "context"
    "github.com/yourname/project/internal/repository"
    "github.com/yourname/project/pkg/pb"
)

type NewsService struct {
    repo repository.NewsRepository
    pb.UnimplementedNewsServiceServer
}

func NewNewsService(repo repository.NewsRepository) *NewsService {
    return &NewsService{repo: repo}
}

func (s *NewsService) GetArticle(ctx context.Context, req *pb.GetArticleRequest) (*pb.Article, error) {
    article, err := s.repo.GetByID(ctx, req.Id)
    if err != nil {
        return nil, err
    }
    
    return &pb.Article{
        Id:        article.ID,
        Title:     article.Title,
        Content:   article.Content,
        CreatedAt: article.CreatedAt.Format(time.RFC3339),
    }, nil
}

func (s *NewsService) ListArticles(ctx context.Context, req *pb.ListArticlesRequest) (*pb.ListArticlesResponse, error) {
    articles, total, err := s.repo.List(ctx, int(req.Page), int(req.PageSize))
    if err != nil {
        return nil, err
    }
    
    pbArticles := make([]*pb.Article, len(articles))
    for i, article := range articles {
        pbArticles[i] = &pb.Article{
            Id:        article.ID,
            Title:     article.Title,
            Content:   article.Content,
            CreatedAt: article.CreatedAt.Format(time.RFC3339),
        }
    }
    
    return &pb.ListArticlesResponse{
        Articles: pbArticles,
        Total:    int32(total),
    }, nil
}

实现处理器:

// internal/handler/news_handler.go
package handler

import (
    "net"

    "github.com/yourname/project/internal/service"
    "github.com/yourname/project/pkg/pb"
    "google.golang.org/grpc"
)

type GRPCServer struct {
    server *grpc.Server
}

func NewGRPCServer(newsService *service.NewsService) *GRPCServer {
    grpcServer := grpc.NewServer()
    pb.RegisterNewsServiceServer(grpcServer, newsService)
    
    return &GRPCServer{
        server: grpcServer,
    }
}

func (s *GRPCServer) Serve(addr string) error {
    lis, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    
    return s.server.Serve(lis)
}

func (s *GRPCServer) GracefulStop() {
    s.server.GracefulStop()
}

主程序入口:

// cmd/server/main.go
package main

import (
    "log"
    
    "github.com/yourname/project/internal/handler"
    "github.com/yourname/project/internal/repository"
    "github.com/yourname/project/internal/service"
)

func main() {
    // 初始化依赖
    repo := repository.NewNewsRepository()
    newsService := service.NewNewsService(repo)
    grpcServer := handler.NewGRPCServer(newsService)
    
    // 启动gRPC服务器
    log.Println("Starting gRPC server on :50051")
    if err := grpcServer.Serve(":50051"); err != nil {
        log.Fatalf("Failed to serve: %v", err)
    }
}

这种分层结构将业务逻辑(service)、数据访问(repository)和传输层(handler)分离,使代码更易于测试和维护。gRPC服务实现直接嵌入生成的UnimplementedNewsServiceServer,确保向前兼容。

回到顶部