
gRPC Server 實作 - Clean Architecture in Go
在上一階段我們完成了可以運行的 gRPC Server 接下來就可以將原本 RESTful API 上的實作移植到 gRPC 上,讓我們可以使用 gRPC 來操作這些功能。
前置準備
因為我們將 RESTful API 常用的 Controller 定位在「處理輸入」的角色,所以 Controller 只有對 JSON 解析相關的處理,實際的商業邏輯都是放在 UseCase 裡面。
基於這樣的理由,我們只需要讓 *grpc.OrderServer
跟 *rest.Api
一樣參考這些 UseCase 就可以重現相同的功能。
1// internal/api/grpc/grpc.go
2
3var DefaultSet = wire.NewSet(
4 wire.Struct(new(OrderServer), "*"),
5 NewServer,
6)
7
8var _ orderspb.OrderServer = &OrderServer{}
9
10type OrderServer struct {
11 orderspb.OrderServer `wire:"-"`
12 PlaceOrderUsecase *usecase.PlaceOrder
13 LookupOrderUsecase *usecase.LookupOrder
14}
15
16// ...
因為 wire
的依賴注入會掃描所有的 Field,我們需要用 wire:"-"
來表示不用對這個 Field 注入,原本的 NewOrderServer
也可以改為 wire.Struct
的方式替換掉。
使用 wire
命令重新生成依賴注入的檔案後,我們就可以進行後續的實作。
Lookup Order
我們先從比較單純的 Lookup Order 來處理,實作的方式跟 RESTful API 基本上相同。將原本 internal/api/grpc/grpc.go
的方法移動到 internal/api/grpc/lookup_order.go
加入以下實作。
1// ...
2
3func (s *OrderServer) LookupOrder(ctx context.Context, req *orderspb.LookupOrderRequest) (*orderspb.LookupOrderResponse, error) {
4 out, err := s.LookupOrderUsecase.Execute(ctx, &usecase.LookupOrderInput{
5 Id: req.Id,
6 })
7
8 if err != nil {
9 return nil, err
10 }
11
12 res := buildLookupOrderResponse(out)
13
14 return res, nil
15}
16
17func buildLookupOrderResponse(out *usecase.LookupOrderOutput) *orderspb.LookupOrderResponse {
18 outItems := make([]*orderspb.OrderItem, 0, len(out.Items))
19 for _, item := range out.Items {
20 outItems = append(outItems, &orderspb.OrderItem{
21 Name: item.Name,
22 Quantity: int32(item.Quantity),
23 UnitPrice: int32(item.UnitPrice),
24 })
25 }
26
27 return &orderspb.LookupOrderResponse{
28 Id: out.Id,
29 Name: out.Name,
30 Items: outItems,
31 }
32}
除了將 gRPC 生成的資料結構轉換為我們 UseCase 的結構外,和使用 RESTfult API 幾乎沒有差異,透過這樣的手法我們讓這些方法可以很輕易地轉換到不同的協定上,或者在各類情境中使用。
Place Order
基本上和 Lookup Order 相同,我們將方法移動到 internal/api/grpc/place_order.go
中,並且加入以下實作。
1func (s *OrderServer) PlaceOrder(ctx context.Context, req *orderspb.PlaceOrderRequest) (*orderspb.PlaceOrderResponse, error) {
2 input := buildPlaceOrderInput(req)
3
4 out, err := s.PlaceOrderUsecase.Execute(ctx, input)
5 if err != nil {
6 return nil, err
7 }
8
9 res := buildPlaceOrderResponse(out)
10
11 return res, nil
12}
13
14func buildPlaceOrderInput(req *orderspb.PlaceOrderRequest) *usecase.PlaceOrderInput {
15 inItems := make([]usecase.PlaceOrderItem, 0, len(req.Items))
16 for _, item := range req.Items {
17 inItems = append(inItems, usecase.PlaceOrderItem{
18 Name: item.Name,
19 Quantity: int(item.Quantity),
20 UnitPrice: int(item.UnitPrice),
21 })
22 }
23
24 return &usecase.PlaceOrderInput{
25 Name: req.Name,
26 Items: inItems,
27 }
28}
29
30func buildPlaceOrderResponse(out *usecase.PlaceOrderOutput) *orderspb.PlaceOrderResponse {
31 outItems := make([]*orderspb.OrderItem, 0, len(out.Items))
32 for _, item := range out.Items {
33 outItems = append(outItems, &orderspb.OrderItem{
34 Name: item.Name,
35 Quantity: int32(item.Quantity),
36 UnitPrice: int32(item.UnitPrice),
37 })
38 }
39
40 return &orderspb.PlaceOrderResponse{
41 Id: out.Id,
42 Name: out.Name,
43 Items: outItems,
44 }
45}
因為輸入跟輸出的內容比較多,因此需要花比較多力氣在處理上除此之外沒什麼不同。
最後,我們可以啟動 gRPC Server 使用 grpcurl
命令來測試看看。
1$ grpcurl -plaintext localhost:8080 orders.Order.PlaceOrder
2{
3 "id": "e1454ea1-f31c-415a-9df0-2f89dd8ef85a"
4}
好像哪裡有點奇怪,跟 RESTful API 的行為似乎有點不同,我們接下來會來探討一下這個問題。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- 連載介紹 - Clean Architecture in Go
- 目標設定 - Clean Architecture in Go
- wire 的依賴注入 - Clean Architecture in Go
- 案例說明 - Clean Architecture in Go
- 操作介面設計 - Clean Architecture in Go
- Place Order 實作 Controller 部分 - Clean Architecture in Go
- Place Order 實作 Entity 部分 - Clean Architecture in Go
- Place Order 實作 Repository 部分 - Clean Architecture in Go
- Lookup Order 功能 - Clean Architecture in Go
- Tokenization 機制設計 - Clean Architecture in Go
- 在 Place Order 實作 Token 機制 - Clean Architecture in Go
- 在 Lookup Order 實作 Token 機制 - Clean Architecture in Go
- Token 內容加密 - Clean Architecture in Go
- gRPC Server 準備 - Clean Architecture in Go
- gRPC Server 實作 - Clean Architecture in Go
- 輸入檢查 - Clean Architecture in Go