gRPC微服务 library-book-grpc-service ,书籍管理内部服务。提供书籍管理的 rpc 接口,主要实现了根据用户ID获取书籍列表的功能,用户管理服务通过 GRPC 调用此服务接口。
完整代码:
https://github.com/Justin02180218/micro-kit
包结构
各个包的含义与上两篇基本一样,这里就不一一说明了。
代码实现
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/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
生成的文件如图:
配置文件
同 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 函数。
修改 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
接口测试
使用postman进行接口测试
返回书籍列表,gRPC调用返回成功,等后面加入调用的链路追踪就可以清晰的看到调用链路。
下一篇文章,我们给微服务加入限流功能。
完整代码:
https://github.com/Justin02180218/micro-kit
更多【分布式专辑】【架构实战专辑】系列文章,请关注公众号