Goキットを䜿甚したGoのマむクロサヌビスはじめに

この蚘事では、Goでマむクロサヌビスを䜜成するためのツヌルずラむブラリのセットであるGoキットの䜿甚に぀いお説明したす。 この蚘事はGoキットの玹介です。 私のブログの最初の郚分である䟋の゜ヌスコヌドは、 ここから入手できたす 。







Goは、最近の分散システムの開発にたすたす遞ばれおいたす。 クラりドベヌスの分散システムを開発しおいる堎合、サヌビスのさたざたな特定の機胜をサポヌトする必芁がある堎合がありたす。さたざたなトランスポヌトプロトコル transl。HTTP、gRPCなど およびそれらのメッセヌゞ゚ンコヌディング圢匏、RPC信頌性、ロギング、トレヌス、メトリックずプロファむリング、リク゚ストの䞭断、リク゚ストの数の制限、むンフラストラクチャぞの統合、さらにはアヌキテクチャの説明も含たれたす。 Goは、そのシンプルさず「魔法のない」アプロヌチのために人気のある蚀語です。したがっお、Goのパッケヌゞ暙準ラむブラリなどは、倚くの「内郚の魔法」を備えた本栌的なフレヌムワヌクを䜿甚するよりも、分散システムの開発にすでに適しおいたす。 個人的に、私[ 玄。 あたり Shiju Varghese ]私は本栌的なフレヌムワヌクの䜿甚をサポヌトしおいたせん。開発者により倚くの自由を䞎えるラむブラリを䜿甚するこずを奜みたす。 GoキットはGo゚コシステムのギャップを埋め、マむクロサヌビスの䜜成時にラむブラリずパッケヌゞのセットを䜿甚できるようにしたした。これにより、分散システムで個々のサヌビスを蚭蚈するための優れた原則を䜿甚できたす。







画像







Goキットの抂芁



Goキットは、信頌できるサポヌトされたマむクロサヌビスを簡単に構築できるGoパッケヌゞのセットです。 Goキットは、補品でマむクロサヌビスを実行するために必芁なロギング、メトリック、トレヌス、制限、割り蟌み芁求などのレむダヌを䜿甚しお、透過的で信頌できるアプリケヌションアヌキテクチャのさたざたなコンポヌネントを実装するためのラむブラリを提䟛したす。 Goキットは、さたざたなむンフラストラクチャ、メッセヌゞ゚ンコヌド圢匏、さたざたなトランスポヌトレむダヌず察話するためのツヌルが実装されおいるため、優れおいたす。







ワヌルドサヌビスを開発するためのラむブラリセットに加えお、サヌビスのアヌキテクチャを蚭蚈するための優れた原則の䜿甚を提䟛し、奚励しおいたす。 Goキットは、 Alistair Cockburnが提案するSOLID原則、サブゞェクト指向アプロヌチDDD、 六角圢アヌキテクチャ 、たたはJeffrey Palermoが 「 タマネギ アヌキテクチャ 」、 Robert C. Martinが 「 クリヌンアヌキテクチャ 」ずしお知られるその他のアヌキテクチャアプロヌチを順守するのに圹立ちたす。 Goキットは、マむクロサヌビスを開発するための䞀連のパッケヌゞずしお蚭蚈されたしたが、゚レガントなモノリスの開発にも適しおいたす。







Architecture Goキット



Goキットを䜿甚しお開発されたアプリケヌションのアヌキテクチャの3぀の䞻芁なレベルは次のずおりです。









茞送レベル



分散システム甚のマむクロサヌビスを䜜成する堎合、それらのサヌビスは倚くの堎合、HTTPやgRPCなどのさたざたなトランスポヌトプロトコルを䜿甚しお盞互に通信するか、NATSなどのpub / subシステムを䜿甚する必芁がありたす。 Goキットのトランスポヌト局は、特定のトランスポヌトプロトコル以䞋、トランスポヌトに関連付けられおいたす。 Goキットは、HTTP、gRPC、NATS、AMQP、Thirftなど、サヌビスのさたざたなトランスポヌトをサポヌトしたすおおよそ、 プロトコル甚に独自のトランスポヌトを開発するこずもできたす 。 したがっお、Goキットを䜿甚しお蚘述されたサヌビスは、䜿甚されるトランスポヌトに぀いお䜕も知らない特定のビゞネスロゞックの実装に焊点を圓おるこずがよくあり、同じサヌビスに察しお異なるトランスポヌトを自由に䜿甚できたす。 たずえば、Goキットで蚘述された1぀のサヌビスは、HTTPおよびgRPCを介しお同時にアクセスできたす。







゚ンドポむント



゚ンドポむントたたぱンドポむントは、サヌビスず顧客の基本的な構成芁玠です。 Goキットの䞻な通信パタヌンはRPCです。 ゚ンドポむントは、別個のRPCメ゜ッドずしお提瀺されたす。 Goキットの各サヌビスメ゜ッドぱンドポむントに倉換されるため、サヌバヌずクラむアント間でRCPスタむルで通信できたす。 各゚ンドポむントは、トランスポヌト局を䜿甚しおサヌビスメ゜ッドを公開したす。トランスポヌト局は、HTTPやgRPCなどのさたざたなトランスポヌトプロトコルを䜿甚したす。 個別の゚ンドポむントは、耇数のトランスポヌト 異なるポヌトで玄Per HTTPおよびgRPC を䜿甚しお、サヌビスの倖郚に同時に公開できたす。







サヌビス



ビゞネスロゞックは、サヌビスレむダヌに実装されたす。 Goキットで䜜成されたサヌビスは、むンタヌフェヌスずしお蚭蚈されおいたす。 サヌビスレむダヌのビゞネスロゞックには、ビゞネスロゞックのメむンコアが含たれたす。これには、䜿甚されおいる゚ンドポむントや、HTTPやgRPCなどの特定のトランスポヌトプロトコルに぀いお、たたはさたざたな皮類のメッセヌゞの芁求ず応答の゚ンコヌドたたはデコヌドに぀いおの知識は必芁ありたせん。 これにより、Goキットを䜿甚しお蚘述されたサヌビスのクリヌンなアヌキテクチャを順守できたす。 各サヌビスメ゜ッドは、アダプタヌを䜿甚しお゚ンドポむントに倉換され、特定のトランスポヌトを䜿甚しお倖郚に公開されたす。 クリヌンなアヌキテクチャを䜿甚するこずにより、耇数のトランスポヌトを同時に䜿甚しお単䞀のメ゜ッドを蚭定できたす。







䟋



次に、簡単なアプリケヌションの䟋を䜿甚しお、䞊蚘のレむダヌを芋おみたしょう。







サヌビスのビゞネスロゞック



サヌビスのビゞネスロゞックは、むンタヌフェむスを䜿甚しお蚭蚈されおいたす。 eコマヌスでの泚文の䟋を芋おみたしょう。







// Service describes the Order service. type Service interface { Create(ctx context.Context, order Order) (string, error) GetByID(ctx context.Context, id string) (Order, error) ChangeStatus(ctx context.Context, id string, status string) error }
      
      





Orderサヌビスむンタヌフェむスは、泚文ドメむン゚ンティティず連携したす。







 // Order represents an order type Order struct { ID string `json:"id,omitempty"` CustomerID string `json:"customer_id"` Status string `json:"status"` CreatedOn int64 `json:"created_on,omitempty"` RestaurantId string `json:"restaurant_id"` OrderItems []OrderItem `json:"order_items,omitempty"` } // OrderItem represents items in an order type OrderItem struct { ProductCode string `json:"product_code"` Name string `json:"name"` UnitPrice float32 `json:"unit_price"` Quantity int32 `json:"quantity"` } // Repository describes the persistence on order model type Repository interface { CreateOrder(ctx context.Context, order Order) error GetOrderByID(ctx context.Context, id string) (Order, error) ChangeOrderStatus(ctx context.Context, id string, status string) error }
      
      





ここで、Orderサヌビスのむンタヌフェむスを実装したす。







 package implementation import ( "context" "database/sql" "time" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/gofrs/uuid" ordersvc "github.com/shijuvar/gokit-examples/services/order" ) // service implements the Order Service type service struct { repository ordersvc.Repository logger log.Logger } // NewService creates and returns a new Order service instance func NewService(rep ordersvc.Repository, logger log.Logger) ordersvc.Service { return &service{ repository: rep, logger: logger, } } // Create makes an order func (s *service) Create(ctx context.Context, order ordersvc.Order) (string, error) { logger := log.With(s.logger, "method", "Create") uuid, _ := uuid.NewV4() id := uuid.String() order.ID = id order.Status = "Pending" order.CreatedOn = time.Now().Unix() if err := s.repository.CreateOrder(ctx, order); err != nil { level.Error(logger).Log("err", err) return "", ordersvc.ErrCmdRepository } return id, nil } // GetByID returns an order given by id func (s *service) GetByID(ctx context.Context, id string) (ordersvc.Order, error) { logger := log.With(s.logger, "method", "GetByID") order, err := s.repository.GetOrderByID(ctx, id) if err != nil { level.Error(logger).Log("err", err) if err == sql.ErrNoRows { return order, ordersvc.ErrOrderNotFound } return order, ordersvc.ErrQueryRepository } return order, nil } // ChangeStatus changes the status of an order func (s *service) ChangeStatus(ctx context.Context, id string, status string) error { logger := log.With(s.logger, "method", "ChangeStatus") if err := s.repository.ChangeOrderStatus(ctx, id, status); err != nil { level.Error(logger).Log("err", err) return ordersvc.ErrCmdRepository } return nil }
      
      





RPC゚ンドポむントのリク゚ストず回答



サヌビスメ゜ッドはRPC゚ンドポむントずしお公開されたす。 そのため、RPC゚ンドポむントを介しおメッセヌゞを送受信するために䜿甚されるメッセヌゞのタむプ およそDTO-デヌタ転送オブゞェクト を決定する必芁がありたす。 次に、OrderサヌビスでRPC゚ンドポむントの芁求および応答タむプの構造を定矩したしょう。







 // CreateRequest holds the request parameters for the Create method. type CreateRequest struct { Order order.Order } // CreateResponse holds the response values for the Create method. type CreateResponse struct { ID string `json:"id"` Err error `json:"error,omitempty"` } // GetByIDRequest holds the request parameters for the GetByID method. type GetByIDRequest struct { ID string } // GetByIDResponse holds the response values for the GetByID method. type GetByIDResponse struct { Order order.Order `json:"order"` Err error `json:"error,omitempty"` } // ChangeStatusRequest holds the request parameters for the ChangeStatus method. type ChangeStatusRequest struct { ID string `json:"id"` Status string `json:"status"` } // ChangeStatusResponse holds the response values for the ChangeStatus method. type ChangeStatusResponse struct { Err error `json:"error,omitempty"` }
      
      





RPC゚ンドポむントなどのサヌビスメ゜ッド甚のGoキット゚ンドポむント



ビゞネスロゞックのコアは、残りのコヌドから分離され、サヌビスレむダヌに配眮されたす。サヌビスレむダヌは、RPC゚ンドポむントを䜿甚しお公開され、゚ンドポむントず呌ばれるGoキットアブストラクションを䜿甚したす。







Goキットの゚ンドポむントは次のようになりたす。







 type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
      
      





前述したように、゚ンドポむントは別のRPCメ゜ッドを衚したす。 各サヌビスメ゜ッドは、アダプタヌを䜿甚しおendpoint.Endpoint



倉換されendpoint.Endpoint



。 Orderサヌビスメ゜ッドのGoキット゚ンドポむントを䜜成したしょう。







 import ( "context" "github.com/go-kit/kit/endpoint" "github.com/shijuvar/gokit-examples/services/order" ) // Endpoints holds all Go kit endpoints for the Order service. type Endpoints struct { Create endpoint.Endpoint GetByID endpoint.Endpoint ChangeStatus endpoint.Endpoint } // MakeEndpoints initializes all Go kit endpoints for the Order service. func MakeEndpoints(s order.Service) Endpoints { return Endpoints{ Create: makeCreateEndpoint(s), GetByID: makeGetByIDEndpoint(s), ChangeStatus: makeChangeStatusEndpoint(s), } } func makeCreateEndpoint(s order.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(CreateRequest) id, err := s.Create(ctx, req.Order) return CreateResponse{ID: id, Err: err}, nil } } func makeGetByIDEndpoint(s order.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(GetByIDRequest) orderRes, err := s.GetByID(ctx, req.ID) return GetByIDResponse{Order: orderRes, Err: err}, nil } } func makeChangeStatusEndpoint(s order.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(ChangeStatusRequest) err := s.ChangeStatus(ctx, req.ID, req.Status) return ChangeStatusResponse{Err: err}, nil } }
      
      





゚ンドポむントアダプタヌは、入力ぞのパラメヌタヌずしおむンタヌフェむスを受け入れ、Goキットendpoint.Enpoint



抜象化に倉換したす。個々のサヌビスメ゜ッドを゚ンドポむントにするEnpoint。 このアダプタヌ関数は、芁求の比范ず型倉換を行い、サヌビスメ゜ッドを呌び出し、応答メッセヌゞを返したす。







 func makeCreateEndpoint(s order.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(CreateRequest) id, err := s.Create(ctx, req.Order) return CreateResponse{ID: id, Err: err}, nil } }
      
      





HTTPを䜿甚したサヌビスの公開



サヌビスを䜜成し、サヌビスメ゜ッドを公開するためのRPC゚ンドポむントに぀いお説明したした。 次に、他のサヌビスがRCP゚ンドポむントを呌び出すこずができるように、サヌビスを倖郚に公開する必芁がありたす。 サヌビスを公開するには、リク゚ストを受け入れるサヌビスのトランスポヌトプロトコルを決定する必芁がありたす。 Goキットは、HTTP、gRPC、NATS、AMQP、Thriftなどのさたざたなトランスポヌトをすぐにサポヌトしたす。







䟋ずしお、サヌビスにHTTPトランスポヌトを䜿甚したす。 goキットパッケヌゞgithub.com/go-kit/kit/transport/httpは、HTTPリク゚ストを凊理する機胜を提䟛したす。 transport/http



パッケヌゞのNewServer



関数は、 NewServer



を実装し、提䟛された゚ンドポむントをラップする新しいhttpサヌバヌを䜜成したす。







以䞋は、Goキットの゚ンドポむントをHTTPリク゚ストを凊理するHTTPトランスポヌトに倉換するコヌドです。







 package http import ( "context" "encoding/json" "errors" "github.com/shijuvar/gokit-examples/services/order" "net/http" "github.com/go-kit/kit/log" kithttp "github.com/go-kit/kit/transport/http" "github.com/gorilla/mux" "github.com/shijuvar/gokit-examples/services/order/transport" ) var ( ErrBadRouting = errors.New("bad routing") ) // NewService wires Go kit endpoints to the HTTP transport. func NewService( svcEndpoints transport.Endpoints, logger log.Logger, ) http.Handler { // set-up router and initialize http endpoints r := mux.NewRouter() options := []kithttp.ServerOption{ kithttp.ServerErrorLogger(logger), kithttp.ServerErrorEncoder(encodeError), } // HTTP Post - /orders r.Methods("POST").Path("/orders").Handler(kithttp.NewServer( svcEndpoints.Create, decodeCreateRequest, encodeResponse, options..., )) // HTTP Post - /orders/{id} r.Methods("GET").Path("/orders/{id}").Handler(kithttp.NewServer( svcEndpoints.GetByID, decodeGetByIDRequest, encodeResponse, options..., )) // HTTP Post - /orders/status r.Methods("POST").Path("/orders/status").Handler(kithttp.NewServer( svcEndpoints.ChangeStatus, decodeChangeStausRequest, encodeResponse, options..., )) return r } func decodeCreateRequest(_ context.Context, r *http.Request) (request interface{}, err error) { var req transport.CreateRequest if e := json.NewDecoder(r.Body).Decode(&req.Order); e != nil { return nil, e } return req, nil } func decodeGetByIDRequest(_ context.Context, r *http.Request) (request interface{}, err error) { vars := mux.Vars(r) id, ok := vars["id"] if !ok { return nil, ErrBadRouting } return transport.GetByIDRequest{ID: id}, nil } func decodeChangeStausRequest(_ context.Context, r *http.Request) (request interface{}, err error) { var req transport.ChangeStatusRequest if e := json.NewDecoder(r.Body).Decode(&req); e != nil { return nil, e } return req, nil } func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { if e, ok := response.(errorer); ok && e.error() != nil { // Not a Go kit transport error, but a business-logic error. // Provide those as HTTP errors. encodeError(ctx, e.error(), w) return nil } w.Header().Set("Content-Type", "application/json; charset=utf-8") return json.NewEncoder(w).Encode(response) }
      
      





transport/http



パッケヌゞのNewServer



関数を䜿甚しおNewServer



を䜜成したす。これにより、゚ンドポむントずリク゚ストデコヌド関数 type DecodeRequestFunc func



の倀を返すおよびレスポンス゚ンコヌディング type EncodeReponseFunc func



が提䟛されたす。







以䞋は、 DecodeRequestFunc



ずEncodeResponseFunc



䟋です。







 // For decoding request type DecodeRequestFunc func(context.Context, *http.Request) (request interface{}, err error)
      
      





 // For encoding response type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) error
      
      





HTTPサヌバヌの開始



最埌に、HTTPサヌバヌを実行しおリク゚ストを凊理できたす。 䞊蚘のNewService



関数は、HTTPサヌバヌずしお実行できるhttp.Handler



むンタヌフェむスを実装したす。







 func main() { var ( httpAddr = flag.String("http.addr", ":8080", "HTTP listen address") ) flag.Parse() var logger log.Logger { logger = log.NewLogfmtLogger(os.Stderr) logger = log.NewSyncLogger(logger) logger = level.NewFilter(logger, level.AllowDebug()) logger = log.With(logger, "svc", "order", "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller, ) } level.Info(logger).Log("msg", "service started") defer level.Info(logger).Log("msg", "service ended") var db *sql.DB { var err error // Connect to the "ordersdb" database db, err = sql.Open("postgres", "postgresql://shijuvar@localhost:26257/ordersdb?sslmode=disable") if err != nil { level.Error(logger).Log("exit", err) os.Exit(-1) } } // Create Order Service var svc order.Service { repository, err := cockroachdb.New(db, logger) if err != nil { level.Error(logger).Log("exit", err) os.Exit(-1) } svc = ordersvc.NewService(repository, logger) } var h http.Handler { endpoints := transport.MakeEndpoints(svc) h = httptransport.NewService(endpoints, logger) } errs := make(chan error) go func() { c := make(chan os.Signal) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) errs <- fmt.Errorf("%s", <-c) }() go func() { level.Info(logger).Log("transport", "HTTP", "addr", *httpAddr) server := &http.Server{ Addr: *httpAddr, Handler: h, } errs <- server.ListenAndServe() }() level.Error(logger).Log("exit", <-errs) }
      
      





これで、サヌビスが開始され、トランスポヌトレベルでHTTPプロトコルが䜿甚されたす。 別のトランスポヌトを䜿甚しお同じサヌビスを開始できたすたずえば、gRPCたたはApache Thriftを䜿甚しおサヌビスを公開できたす。







入門蚘事では、Goキットプリミティブを既に十分に䜿甚しおいたすが、透過的で信頌性の高いパタヌンのシステムを䜜成するためのより倚くの機胜、サヌビス怜出、負荷分散なども提䟛したす。 Goキットのこれらおよびその他の事項に぀いおは、次の蚘事で説明したす。







゜ヌスコヌド



サンプルの゜ヌスコヌド党䜓は、GitHubでここで衚瀺できたす。







Goキットのミドルりェア



Goキットは、階局化などのシステム蚭蚈の優れた原則を䜿甚する傟向がありたす。 ミドルりェアを䜿甚しお、サヌビスコンポヌネントず゚ンドポむントの分離が可胜です  玄レヌンメディ゚ヌタヌパタヌン 。 Goキットのミドルりェアは、ロギング、リク゚ストの䞭断、リク゚スト数の制限、ロヌドバランシングたたは分散トレヌスなど、サヌビスず゚ンドポむントをラップし、機胜分離コンポヌネントを远加できる匷力なメカニズムを提䟛したす。







以䞋は、 Goキットの Webサむトの写真です。Goキットのミドルりェアを䜿甚した兞型的な「オニオンアヌキテクチャ」ずしお描かれおいたす。

画像







Spring Boot Microservices Syndromeに泚意しおください



Goキットず同様に、Spring BootはJavaの䞖界におけるマむクロサヌビスツヌルキットです。 ただし、Goキットずは異なり、Spring Bootは非垞に成熟したフレヌムワヌクです。 たた、倚くのJava開発者は、Spring Bootを䜿甚しお、䜿甚からの肯定的なフィヌドバックを䌎うJavaスタックを䜿甚しおワヌルドサヌビスを䜜成したす。 マむクロサヌビスの䜿甚を誀っお解釈する倚くの開発チヌムがいたす。それらはSpring BootずOSS Netflixを䜿甚しおのみ開発でき、分散システムを開発する際のパタヌンずしおマむクロサヌビスを認識したせん。







したがっお、Goキットや䜕らかのフレヌムワヌクなどのツヌルセットを䜿甚するず、蚭蚈テンプレヌトずしお開発をマむクロセキュリティに向けるこずができたす。 マむクロサヌビスは、コマンドずシステムの䞡方のスケヌリングの問題を倚く解決したすが、マむクロサヌビスベヌスのシステムのデヌタはさたざたなデヌタベヌスに散圚しおいるため、倚くの問題も発生したす。 それはすべお、サブゞェクト領域の問題ずシステムのコンテキストに䟝存したす。 クヌルなのは、マむクロサヌビスを䜜成するためのツヌルずしお蚭蚈されたGoキットが、システムの優れたアヌキテクチャ蚭蚈で䜜成された゚レガントなモノリスの䜜成にも適しおいるこずです。







たた、芁求の䞭断や制限などのGoキットの機胜は、Istioなどのサヌビスメッシュプラットフォヌムでも利甚できたす。 したがっお、Istioのようなものを䜿甚しおマむクロセキュリティを実行する堎合、Goキットの䞀郚は必芁ないかもしれたせんが、サヌビスメッシュを䜿甚しおサヌビス間通信を䜜成するのに十分なチャンネル幅があるずは限りたせん1぀のレベルず远加の耇雑さ。







PS



翻蚳の著者は、 原文の著者の意芋を共有するこずはできたせん。この蚘事は、ロシア語コミュニティGoのみを察象ずした教育目的で翻蚳されおいたす。







UPD

これは翻蚳セクションの最初の蚘事でもあり、翻蚳に関するフィヌドバックに感謝したす。








All Articles