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

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

完整代码:
https://github.com/Justin02180218/micro-kit

包结构 docker|五,gRPC微服务(library-book-grpc-service)
文章图片

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

代码实现 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) {} }

【docker|五,gRPC微服务(library-book-grpc-service)】下载 protoc 可执行程序:
在 https://github.com/protocolbuffers/protobuf/releases 下,找到操作系统对应的版本下载安装。

安装代码生成工具:
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1


生成 go 程序文件:
进入工程下的 protos 目录下执行:
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative book/book.proto


生成的文件如图:
docker|五,gRPC微服务(library-book-grpc-service)
文章图片


配置文件
同 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 函数。
docker|五,gRPC微服务(library-book-grpc-service)
文章图片


修改 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 { userDaodao.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
docker|五,gRPC微服务(library-book-grpc-service)
文章图片


进入 library-user-service 目录,执行 go run main.go
docker|五,gRPC微服务(library-book-grpc-service)
文章图片


接口测试
使用postman进行接口测试
docker|五,gRPC微服务(library-book-grpc-service)
文章图片

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

下一篇文章,我们给微服务加入限流功能。
完整代码:
https://github.com/Justin02180218/micro-kit
更多【分布式专辑】【架构实战专辑】系列文章,请关注公众号
docker|五,gRPC微服务(library-book-grpc-service)
文章图片

    推荐阅读