五,gRPC微服务(library-book-grpc-service)

gRPC微服务 library-book-grpc-service ,书籍管理内部服务。提供书籍管理的 rpc 接口,主要实现了根据用户ID获取书籍列表的功能,用户管理服务通过 GRPC 调用此服务接口。

完整代码:

https://github.com/Justin02180218/micro-kit

包结构

五,gRPC微服务(library-book-grpc-service)_第1张图片

各个包的含义与上两篇基本一样,这里就不一一说明了。

代码实现

gRPC Server

编写 book.proto 文件

syntax = "proto3";
package book;
option go_package = "/book";
message BookInfo {
    uint64 id = 1;
    string bookname = 2;
}
message BooksByUserIDRequest {
    uint64 userID = 1;
}
message BooksResponse {
    repeated BookInfo books = 1;
}
service Book {
    // 根据用户ID查找书籍列表
    rpc FindBooksByUserID (BooksByUserIDRequest) returns (BooksResponse) {}
}

下载 protoc 可执行程序:

在 https://github.com/protocolbuffers/protobuf/releases 下,找到操作系统对应的版本下载安装。

安装代码生成工具:

go install google.golang.org/protobuf/cmd/[email protected]
go install google.golang.org/grpc/cmd/[email protected]

生成 go 程序文件:

进入工程下的 protos 目录下执行:

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative book/book.proto

生成的文件如图:

五,gRPC微服务(library-book-grpc-service)_第2张图片

配置文件

同 book.yaml ,端口与微服务的名称不同。

server:
  port: 10088
  mode: debug
  name: "book-rpc-service"

mysql:
  host: "localhost"
  port: 3306
  db: "library"
  username: "root"
  password: "123456"
  debug: true

数据库表

在 library 数据库建立 user_book 表,存储用户与书籍的对应关系。

CREATE TABLE `user_book` (
  `user_id` bigint(20) NOT NULL,
  `book_id` bigint(20) NOT NULL,
  PRIMARY KEY (`user_id`,`book_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

dao层

在 dao 层创建与数据库交互的 book_dao.go

定义 BookDao 接口及实现:

type BookDao interface {
    FindBooksByUserID(userID uint64) ([]models.Book, error)
}

type BookDaoImpl struct{}

func NewBookDaoImpl() BookDao {
    return &BookDaoImpl{}
}
  • FindBooksByUserID:根据用户ID查询所借书籍列表

BookDao 接口的函数实现

func (b *BookDaoImpl) FindBooksByUserID(userID uint64) ([]models.Book, error) {
    books := new([]models.Book)
    sql := "select b.* from book b, user_book ub where b.id = ub.book_id and ub.user_id = ?"
    err := databases.DB.Raw(sql, userID).Scan(books).Error
    if err != nil {
        return nil, err
    }
    return *books, nil
}

service层

在 service 层创建 book_service.go

定义 BookService 接口及实现:

type BookService interface {
    FindBooksByUserID(ctx context.Context, req *pbbook.BooksByUserIDRequest) (*pbbook.BooksResponse, error)
}

type BookServiceImpl struct {
    bookDao dao.BookDao
}

func NewBookServiceImpl(bookDao dao.BookDao) BookService {
    return &BookServiceImpl{
        bookDao: bookDao,
    }
}

BookService 接口的函数实现

func (b *BookServiceImpl) FindBooksByUserID(ctx context.Context, req *pbbook.BooksByUserIDRequest) (*pbbook.BooksResponse, error) {
    books, err := b.bookDao.FindBooksByUserID(req.UserID)
    if err != nil {
        return &pbbook.BooksResponse{}, err
    }

    pbbooks := new([]*pbbook.BookInfo)
    for _, book := range books {
        *pbbooks = append(*pbbooks, &pbbook.BookInfo{
            Id:       book.ID,
            Bookname: book.Bookname,
        })
    }
    return &pbbook.BooksResponse{
        Books: *pbbooks,
    }, nil
}

endpoint层

在 endpoint 层创建 book_endpoint.go,

定义 BookEndpoints struct,只有一个请求,所以对应的只有一个endpoint

type BookEndpoints struct {
    FindBooksByUserIDEndpoint endpoint.Endpoint
}

func NewFindBooksByUserIDEndpoint(bookService service.BookService) endpoint.Endpoint {
    return func(ctx context.Context, request interface{}) (response interface{}, err error) {
        req := request.(*pbbook.BooksByUserIDRequest)
        res, err := bookService.FindBooksByUserID(ctx, req)
        if err != nil {
            return nil, err
        }
        return res, nil
    }
}

transport层

在 transport 层定义 grpcServer struct,实现在 book.proto 中定义的接口 book 和函数 FindBooksByUserID

type grpcServer struct {
    pbbook.UnimplementedBookServer
    findBooksByUserID kitrpc.Handler
}

func NewBookServer(ctx context.Context, endpoints endpoint.BookEndpoints) pbbook.BookServer {
    return &grpcServer{
        findBooksByUserID: kitrpc.NewServer(
            endpoints.FindBooksByUserIDEndpoint,
            decodeFindBooksByUserIDRequest,
            encodeFindBooksByUserIDResponse,
        ),
    }
}

func (g grpcServer) FindBooksByUserID(ctx context.Context, r *pbbook.BooksByUserIDRequest) (*pbbook.BooksResponse, error) {
    _, res, err := g.findBooksByUserID.ServeGRPC(ctx, r)
    if err != nil {
        return nil, err
    }
    return res.(*pbbook.BooksResponse), nil
}

启动服务

main.go

var configFile = flag.String("f", "book_rpc.yaml", "book rpc config file")
var quiteChan = make(chan error, 1)

func main() {
    flag.Parse()

    err := configs.Init(*configFile)
    if err != nil {
        panic(err)
    }
    err = databases.InitMySql(configs.Conf.MySQLConfig)
    if err != nil {
        fmt.Println("load mysql failed")
    }

    ctx := context.Background()

    bookDao := dao.NewBookDaoImpl()
    bookService := service.NewBookServiceImpl(bookDao)
    endpoints := endpoint.BookEndpoints{
        FindBooksByUserIDEndpoint: endpoint.NewFindBooksByUserIDEndpoint(bookService),
    }

    go func() {
        handler := transport.NewBookServer(ctx, endpoints)
        listener, err := net.Listen("tcp", fmt.Sprintf(":%s", strconv.Itoa(configs.Conf.ServerConfig.Port)))
        if err != nil {
            fmt.Println("listen tcp err", err)
            quiteChan <- err
            return
        }
        gRPCServer := grpc.NewServer()
        pbbook.RegisterBookServer(gRPCServer, handler)
        err = gRPCServer.Serve(listener)
        if err != nil {
            fmt.Println("gRPCServer Serve err", err)
            quiteChan <- err
            return
        }
    }()

    go func() {
        c := make(chan os.Signal, 1)
        signal.Notify(c, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP, syscall.SIGQUIT)
        quiteChan <- fmt.Errorf("%s", <-c)
    }()

    fmt.Println(<-quiteChan)
}

gRPC Client

在 library-user-service 中增加 FindBooksByUserID 函数,通过 gRPC Client 访问 library-book-grpc-service 中 gRPC Server 提供的 FindBooksByUserID 函数。

五,gRPC微服务(library-book-grpc-service)_第3张图片

修改 user_service.go

type UserService interface {
    Register(ctx context.Context, vo *dto.RegisterUser) (*dto.UserInfo, error)
    FindByID(ctx context.Context, id uint64) (*dto.UserInfo, error)
    FindByEmail(ctx context.Context, email string) (*dto.UserInfo, error)
    FindBooksByUserID(ctx context.Context, id uint64) (interface{}, error)
}

type UserServiceImpl struct {
    userDao    dao.UserDao
    bookClient pbbook.BookClient
}

func (u *UserServiceImpl) FindBooksByUserID(ctx context.Context, id uint64) (interface{}, error) {
    req := &pbbook.BooksByUserIDRequest{UserID: id}
    res, err := u.bookClient.FindBooksByUserID(ctx, req)
    if err != nil {
        return nil, err
    }
    return res, nil
}

修改 library-user-service的 main.go ,建立 gRPC Client 的链接

conn, err := grpc.Dial("127.0.0.1:10088", grpc.WithInsecure())
if err != nil {
    log.Println("连接user rpc 错误", err)
    panic(err)
}
defer conn.Close()
bookClient := pbbook.NewBookClient(conn)

userDao := dao.NewUserDaoImpl()
userService := service.NewUserServiceImpl(userDao, bookClient)

启动

进入 library-book-grpc-service 目录,执行 go run main.go

图片

进入 library-user-service 目录,执行 go run main.go

五,gRPC微服务(library-book-grpc-service)_第4张图片

接口测试

使用postman进行接口测试

五,gRPC微服务(library-book-grpc-service)_第5张图片

返回书籍列表,gRPC调用返回成功,等后面加入调用的链路追踪就可以清晰的看到调用链路。

下一篇文章,我们给微服务加入限流功能。

完整代码:

https://github.com/Justin02180218/micro-kit


更多【分布式专辑】【架构实战专辑】系列文章,请关注公众号

你可能感兴趣的:(docker,go,微服务,golang,微服务架构,docker,k8s,rpc)