Layanan microser on Go dengan kit Go: Pendahuluan

Pada artikel ini saya akan menjelaskan penggunaan kit Go, seperangkat alat dan perpustakaan untuk membuat layanan mikro di Go. Artikel ini adalah pengantar untuk perangkat Go. Bagian pertama di blog saya, kode sumber untuk contoh tersedia di sini .


Go semakin dipilih untuk pengembangan sistem terdistribusi modern. Ketika Anda mengembangkan sistem terdistribusi berbasis cloud, Anda mungkin perlu mendukung berbagai fungsionalitas spesifik dalam layanan Anda, seperti: berbagai protokol transportasi ( dll., HTTP, gRPC, dll. ) Dan format penyandian pesan untuk mereka, keandalan RPC, pencatatan , melacak, metrik dan profil, menginterupsi permintaan, membatasi jumlah permintaan, mengintegrasikan ke dalam infrastruktur, dan bahkan menggambarkan arsitektur. Go adalah bahasa yang populer karena kesederhanaannya dan pendekatan "tanpa sihir", oleh karena itu paket Go, misalnya, perpustakaan standar, sudah lebih cocok untuk mengembangkan sistem terdistribusi daripada menggunakan kerangka kerja lengkap dengan banyak "sihir di bawah tenda". Secara pribadi, saya [ kira-kira. trans. Shiju Varghese ] Saya tidak mendukung penggunaan kerangka kerja lengkap, saya lebih suka menggunakan perpustakaan yang memberikan lebih banyak kebebasan kepada pengembang. Kit Go mengisi celah di ekosistem Go, sehingga memungkinkan untuk menggunakan satu set perpustakaan dan paket saat membuat layanan mikro, yang pada gilirannya memungkinkan penggunaan prinsip-prinsip yang baik untuk merancang layanan individual dalam sistem terdistribusi.


gambar


Pengantar Go kit


Go kit adalah sekumpulan paket Go yang membuatnya mudah untuk membangun layanan microser yang andal dan didukung. Go kit menyediakan pustaka untuk mengimplementasikan berbagai komponen arsitektur aplikasi yang transparan dan andal, menggunakan lapisan seperti pencatatan, metrik, penelusuran, pembatasan, dan interupsi permintaan yang diperlukan untuk menjalankan layanan Microsoft pada prod. Go kit bagus karena memiliki alat yang diimplementasikan dengan baik untuk berinteraksi dengan berbagai infrastruktur, format penyandian pesan, dan berbagai lapisan transportasi.


Selain serangkaian perpustakaan untuk mengembangkan layanan dunia, ia menyediakan dan mendorong penggunaan prinsip-prinsip yang baik untuk merancang arsitektur layanan Anda. Kit Go membantu Anda mematuhi prinsip-prinsip SOLID, pendekatan berorientasi subjek (DDD) dan arsitektur heksagonal yang diusulkan oleh Alistair Cockburn atau pendekatan lain dari prinsip-prinsip arsitektur yang dikenal sebagai " arsitektur bawang " oleh Jeffrey Palermo dan " arsitektur bersih " oleh Robert C. Martin . Meskipun Go kit dirancang sebagai satu set paket untuk mengembangkan layanan microser, itu juga cocok untuk mengembangkan monolit yang elegan.


Perangkat Arsitektur Go


Tiga tingkat utama dalam arsitektur aplikasi yang dikembangkan menggunakan Go kit adalah:


  • tingkat transportasi
  • tingkat titik akhir
  • tingkat layanan

Tingkat transportasi


Ketika Anda menulis layanan microser untuk sistem terdistribusi, layanan di dalamnya sering harus berkomunikasi satu sama lain menggunakan berbagai protokol transportasi, seperti: HTTP atau gRPC, atau menggunakan sistem pub / sub, seperti NATS. Lapisan transport dalam kit Go terikat dengan protokol transport spesifik (selanjutnya transport). Go kit mendukung berbagai transportasi untuk layanan Anda, seperti: HTTP, gRPC, NATS, AMQP dan Thirft ( sekitar. Anda juga dapat mengembangkan transportasi Anda sendiri untuk protokol Anda ). Oleh karena itu, layanan yang ditulis menggunakan kit Go seringkali berfokus pada implementasi logika bisnis tertentu yang tidak tahu apa-apa tentang transportasi yang digunakan, Anda bebas menggunakan transportasi berbeda untuk layanan yang sama. Sebagai contoh, satu layanan yang ditulis dalam Go kit dapat secara bersamaan memberikan akses ke sana melalui HTTP dan gRPC.


Titik akhir


Titik akhir atau titik akhir adalah blok bangunan mendasar untuk layanan dan pelanggan. Di Go kit, pola komunikasi utama adalah RPC. Endpoint disajikan sebagai metode RPC terpisah. Setiap metode layanan dalam kit Go dikonversi ke titik akhir, memungkinkan Anda untuk berkomunikasi antara server dan klien dalam gaya RCP. Setiap titik akhir memperlihatkan metode layanan menggunakan layer Transport, yang pada gilirannya menggunakan berbagai protokol transport, seperti HTTP atau gRPC. Titik akhir yang terpisah dapat ditetapkan dari layanan secara bersamaan menggunakan beberapa transportasi ( kira-kira Lane HTTP dan gRPC pada port yang berbeda ).


Layanan


Logika bisnis diimplementasikan dalam lapisan layanan. Layanan yang ditulis dengan Go kit dirancang sebagai antarmuka. Logika bisnis di lapisan layanan berisi inti utama dari logika bisnis, yang seharusnya tidak mengetahui apa pun tentang titik akhir yang digunakan atau protokol transportasi tertentu, seperti HTTP atau gRPC, atau tentang penyandian atau pengodean permintaan dan respons dari berbagai jenis pesan. Ini akan memungkinkan Anda untuk mematuhi arsitektur bersih dalam layanan yang ditulis menggunakan kit Go. Setiap metode layanan dikonversi ke titik akhir menggunakan adaptor dan diekspos di luar menggunakan transportasi tertentu. Melalui penggunaan arsitektur bersih, satu metode dapat diatur menggunakan beberapa transport pada saat yang bersamaan.


Contohnya


Dan sekarang mari kita lihat lapisan yang dijelaskan di atas menggunakan contoh aplikasi sederhana.


Logika bisnis dalam layanan


Logika bisnis dalam layanan dirancang menggunakan antarmuka. Kami akan melihat contoh pesanan dalam e-commerce:


// 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 } 

Antarmuka layanan pesanan berfungsi dengan entitas domain pesanan:


 // 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 } 

Di sini kami mengimplementasikan antarmuka layanan 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 } 

Permintaan dan Jawaban untuk Titik Akhir RPC


Metode layanan terpapar sebagai titik akhir RPC. Jadi kita perlu menentukan jenis pesan ( kira-kira Per. DTO - objek transfer data ) yang akan digunakan untuk mengirim dan menerima pesan melalui titik akhir RPC. Sekarang mari kita mendefinisikan struktur untuk tipe permintaan dan respons untuk titik akhir RPC di layanan Pesanan:


 // 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"` } 

Pergi endpoint kit untuk metode layanan seperti titik akhir RPC


Inti dari logika bisnis kami dipisahkan dari sisa kode dan dimasukkan ke dalam lapisan layanan, yang diekspos menggunakan titik akhir RPC, yang menggunakan abstraksi Go Kit yang disebut Endpoint .


Beginilah tampilan titik akhir dari perangkat Go:


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

Seperti yang kami katakan di atas, titik akhir mewakili metode RPC yang terpisah. Setiap metode layanan dikonversi ke endpoint.Endpoint . endpoint.Endpoint menggunakan adaptor. Mari kita buat titik akhir Go kit untuk metode layanan Pemesanan:


 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 } } 

Adaptor titik akhir menerima antarmuka sebagai argumen ke input dan mengubahnya menjadi abstraksi endpoint.Enpoint Go kit. endpoint.Enpoint membuat setiap metode layanan individu sebagai titik akhir. Fungsi adaptor ini membuat perbandingan dan mengetik konversi untuk permintaan, memanggil metode layanan dan mengembalikan pesan respons.


 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 } } 

Mengekspos layanan menggunakan HTTP


Kami menciptakan layanan kami dan menjelaskan titik akhir RPC untuk mengekspos metode layanan kami. Sekarang kita perlu menerbitkan layanan kami di luar sehingga layanan lain dapat memanggil titik akhir RCP. Untuk mengekspos layanan kami, kami perlu menentukan protokol transportasi untuk layanan kami, yang menurutnya akan menerima permintaan. Go kit mendukung berbagai transportasi, seperti HTTP, gRPC, NATS, AMQP dan Thrift di luar kotak.


Sebagai contoh, kami menggunakan transportasi HTTP untuk layanan kami. Paket go kit github.com/go-kit/kit/transport/http menyediakan kemampuan untuk melayani permintaan HTTP. Dan fungsi NewServer dari paket transport/http akan membuat server http baru yang akan mengimplementasikan http.Handler dan membungkus titik akhir yang disediakan.


Di bawah ini adalah kode yang mengubah titik akhir kit Go ke transport HTTP yang melayani permintaan 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) } 

Kami membuat http.Handler menggunakan fungsi NewServer dari paket transport/http , yang menyediakan titik akhir dan meminta fungsi decoding (mengembalikan nilai type DecodeRequestFunc func ) dan pengkodean respons (mis. type EncodeReponseFunc func ).


Berikut ini adalah contoh DecodeRequestFunc dan 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 

Mulai server HTTP


Akhirnya, kita dapat menjalankan server HTTP untuk memproses permintaan. Fungsi NewService dijelaskan di atas mengimplementasikan antarmuka http.Handler yang memungkinkan kita untuk menjalankannya sebagai server HTTP:


 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) } 

Sekarang layanan kami diluncurkan dan menggunakan protokol HTTP di tingkat transportasi. Layanan yang sama dapat mulai menggunakan transportasi lain. Misalnya, layanan dapat diekspos menggunakan gRPC atau Apache Thrift.


Untuk artikel pengantar, kami telah menggunakan cukup primitif Go kit, tetapi juga menyediakan lebih banyak fungsi untuk membuat sistem yang transparan, pola yang andal, penemuan layanan, penyeimbangan beban, dll. Kami akan membahas ini dan hal-hal lain dalam kit Go di artikel berikut.


Kode sumber


Seluruh kode sumber untuk contoh dapat dilihat di GitHub di sini.


Middlewares dalam Go kit


Go kit merupakan predisposisi untuk penggunaan prinsip-prinsip desain sistem yang baik, seperti layering. Isolasi komponen layanan dan titik akhir dimungkinkan menggunakan Middlewares ( kira-kira pola mediator Lane ). Middlewares dalam kit Go menyediakan mekanisme yang kuat di mana Anda dapat membungkus layanan dan titik akhir dan menambahkan fungsionalitas (komponen terisolasi), seperti pencatatan, gangguan permintaan, membatasi jumlah permintaan, penyeimbangan muatan, atau pelacakan terdistribusi.


Di bawah ini adalah gambar dari situs web Go kit , yang digambarkan sebagai "arsitektur bawang" khas yang menggunakan Middlewares dalam kit Go:
gambar


Waspadalah terhadap Spring Boot Microservices Syndrome


Seperti Go kit, Spring Boot adalah toolkit microservice di dunia Java. Tapi, tidak seperti Go kit, Spring Boot adalah kerangka kerja yang sangat matang. Juga, banyak pengembang Java menggunakan Spring Boot untuk membuat layanan dunia menggunakan tumpukan Java dengan umpan balik positif dari penggunaan, beberapa dari mereka percaya bahwa layanan microser hanya tentang menggunakan Spring Boot. Saya melihat banyak tim pengembangan yang salah mengartikan penggunaan layanan microser, bahwa mereka hanya dapat dikembangkan menggunakan Spring Boot dan Netflix OSS dan tidak menganggap layanan microser sebagai pola ketika mengembangkan sistem terdistribusi.


Jadi perlu diingat bahwa dengan seperangkat alat, seperti kit Go atau semacam kerangka kerja, Anda mengarahkan pengembangan Anda ke arah mikro, sebagai pola desain. Meskipun layanan microser memecahkan banyak masalah penskalaan baik pada perintah maupun sistem, ini juga menciptakan banyak masalah karena data dalam sistem berbasis layanan mikro tersebar di berbagai basis data, yang terkadang menciptakan banyak masalah saat membuat kueri transaksional atau data. Itu semua tergantung pada masalah area subjek dan konteks sistem Anda. Yang keren adalah bahwa kit Go, yang dirancang sebagai alat untuk membuat layanan microser, juga cocok untuk membuat monolit elegan yang dibuat dengan desain arsitektur yang bagus untuk sistem Anda.


Dan beberapa fitur Go kit, seperti permintaan interupsi dan pembatasan, juga tersedia pada platform layanan, seperti Istio. Jadi jika Anda menggunakan sesuatu seperti Istio untuk meluncurkan microseurises Anda, Anda mungkin tidak memerlukan beberapa hal dari kit Go, tetapi tidak semua orang akan memiliki cukup lebar saluran untuk menggunakan jala layanan untuk membuat komunikasi antar layanan, karena ini menambahkan lebih banyak satu tingkat dan kompleksitas tambahan.


PS


Penulis terjemahan tidak boleh berbagi pendapat penulis teks asli , artikel ini telah diterjemahkan hanya untuk tujuan pendidikan untuk komunitas bahasa Rusia Go.


UPD
Ini juga artikel pertama di bagian terjemahan dan saya akan berterima kasih atas umpan balik pada terjemahan.

Source: https://habr.com/ru/post/id430300/


All Articles