Создание контейнерных микросервисов на Голанге
Благодаря постоянным обновлениям веб-приложений и архитектуры разработки микросервисы стали последней тенденцией в архитектуре разработки приложений. Достижения в области разработки приложений в сочетании с протоколами для контейнерных перевозок открыли новые возможности для скорости и эффективности разработки.
Контейнеризация всех микросервисных приложений может помочь открыть двери для более гибкой разработки и повышения скорости доставки. В этой статье мы рассмотрим, насколько легко можно создавать контейнерные сервисы в среде микрослужб. Оставайтесь с нами, пока мы проводим вас через весь процесс, а также посмотрите на код, необходимый для того же.
Шаги по разработке контейнерных микросервисов в Голанге
Теперь мы рассмотрим шаги, необходимые для создания контейнерных сервисов в Golang.
Водяной знак
Первым шагом является создание службы водяных знаков в папке pkg. Разработчики могут создать новый файл под именем service.go в папке пакета.
import (
"context"
"github.com/velotiotech/watermark-service/internal"
)
type Service interface {
// Get the list of all documents
Get(ctx context.Context, filters ...internal.Filter) ([]internal.Document, error)
Status(ctx context.Context, ticketID string) (internal.Status, error)
Watermark(ctx context.Context, ticketID, mark string) (int, error)
AddDocument(ctx context.Context, doc *internal.Document) (string, error)
ServiceStatus(ctx context.Context) (int, error)
}
Внедрение сервиса
Следующий шаг — внедрение сервиса. Приведенный ниже код мгновенно обновит и внедрит службу:
import (
"context"
"net/http"
"os"
"github.com/velotiotech/watermark-service/internal"
"github.com/go-kit/kit/log"
"github.com/lithammer/shortuuid/v3"
)
type watermarkService struct{}
func NewService() Service { return &watermarkService{} }
func (w *watermarkService) Get(_ context.Context, filters ...internal.Filter) ([]internal.Document, error) {
// query the database using the filters and return the list of documents
// return error if the filter (key) is invalid and also return error if no item found
doc := internal.Document{
Content: "book",
Title: "Harry Potter and Half Blood Prince",
Author: "J.K. Rowling",
Topic: "Fiction and Magic",
}
return []internal.Document{doc}, nil
}
func (w *watermarkService) Status(_ context.Context, ticketID string) (internal.Status, error) {
// query database using the ticketID and return the document info
// return err if the ticketID is invalid or no Document exists for that ticketID
return internal.InProgress, nil
,,,,}
func (w *watermarkService) Watermark(_ context.Context, ticketID, mark string) (int, error) {
// update the database entry with watermark field as non empty
// first check if the watermark status is not already in InProgress, Started or Finished state
// If yes, then return invalid request
// return error if no item found using the ticketID
return http.StatusOK, nil
}
func (w *watermarkService) AddDocument(_ context.Context, doc *internal.Document) (string, error) {
// add the document entry in the database by calling the database service
// return error if the doc is invalid and/or the database invalid entry error
newTicketID := shortuuid.New()
return newTicketID, nil
}
func (w *watermarkService) ServiceStatus(_ context.Context) (int, error) {
logger.Log("Checking the Service health...")
return http.StatusOK, nil
}
var logger log.Logger
func init() {
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
}
Создание пакета конечных точек
Следующим шагом является создание и установка службы конечных точек. Служба конечной точки будет содержать два файла. Первый файл будет предназначен для распознавания запросов конечных точек. В то время как второй файл будет предназначен для хранения всех ответов.
import "github.com/velotiotech/watermark-service/internal"
type GetRequest struct {
Filters []internal.Filter `json:"filters,omitempty"`
}
type GetResponse struct {
Documents []internal.Document `json:"documents"`
Err string `json:"err,omitempty"`
}
type StatusRequest struct {
TicketID string `json:"ticketID"`
}
type StatusResponse struct {
Status internal.Status `json:"status"`
Err string `json:"err,omitempty"`
}
type WatermarkRequest struct {
TicketID string `json:"ticketID"`
Mark string `json:"mark"`
}
type WatermarkResponse struct {
Code int `json:"code"`
Err string `json:"err"`
}
type AddDocumentRequest struct {
Document *internal.Document `json:"document"`
}
type AddDocumentResponse struct {
TicketID string `json:"ticketID"`
Err string `json:"err,omitempty"`
}
type ServiceStatusRequest struct{}
type ServiceStatusResponse struct {
Code int `json:"status"`
Err string `json:"err,omitempty"`
}
Кроме того, мы также создадим файл с именем endpoints.go. Этот файл обеспечит бесперебойную работу кода:
import (
"context"
"errors"
"os"
"github.com/aayushrangwala/watermark-service/internal"
"github.com/aayushrangwala/watermark-service/pkg/watermark"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log"
)
type Set struct {
GetEndpoint endpoint.Endpoint
AddDocumentEndpoint endpoint.Endpoint
StatusEndpoint endpoint.Endpoint
ServiceStatusEndpoint endpoint.Endpoint
WatermarkEndpoint endpoint.Endpoint
}
func NewEndpointSet(svc watermark.Service) Set {
return Set{
GetEndpoint: MakeGetEndpoint(svc),
AddDocumentEndpoint: MakeAddDocumentEndpoint(svc),
StatusEndpoint: MakeStatusEndpoint(svc),
ServiceStatusEndpoint: MakeServiceStatusEndpoint(svc),
WatermarkEndpoint: MakeWatermarkEndpoint(svc),
}
}
func MakeGetEndpoint(svc watermark.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(GetRequest)
docs, err := svc.Get(ctx, req.Filters...)
if err != nil {
return GetResponse{docs, err.Error()}, nil
}
return GetResponse{docs, ""}, nil
}
}
func MakeStatusEndpoint(svc watermark.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(StatusRequest)
status, err := svc.Status(ctx, req.TicketID)
if err != nil {
return StatusResponse{Status: status, Err: err.Error()}, nil
}
return StatusResponse{Status: status, Err: ""}, nil
}
}
func MakeAddDocumentEndpoint(svc watermark.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(AddDocumentRequest)
ticketID, err := svc.AddDocument(ctx, req.Document)
if err != nil {
return AddDocumentResponse{TicketID: ticketID, Err: err.Error()}, nil
}
return AddDocumentResponse{TicketID: ticketID, Err: ""}, nil
}
}
func MakeWatermarkEndpoint(svc watermark.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(WatermarkRequest)
code, err := svc.Watermark(ctx, req.TicketID, req.Mark)
if err != nil {
return WatermarkResponse{Code: code, Err: err.Error()}, nil
}
return WatermarkResponse{Code: code, Err: ""}, nil
}
}
func MakeServiceStatusEndpoint(svc watermark.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
_ = request.(ServiceStatusRequest)
code, err := svc.ServiceStatus(ctx)
if err != nil {
return ServiceStatusResponse{Code: code, Err: err.Error()}, nil
}
return ServiceStatusResponse{Code: code, Err: ""}, nil
}
}
func (s *Set) Get(ctx context.Context, filters ...internal.Filter) ([]internal.Document, error) {
resp, err := s.GetEndpoint(ctx, GetRequest{Filters: filters})
if err != nil {
return []internal.Document{}, err
}
getResp := resp.(GetResponse)
if getResp.Err != "" {
return []internal.Document{}, errors.New(getResp.Err)
}
return getResp.Documents, nil
}
func (s *Set) ServiceStatus(ctx context.Context) (int, error) {
resp, err := s.ServiceStatusEndpoint(ctx, ServiceStatusRequest{})
svcStatusResp := resp.(ServiceStatusResponse)
if err != nil {
return svcStatusResp.Code, err
}
if svcStatusResp.Err != "" {
return svcStatusResp.Code, errors.New(svcStatusResp.Err)
}
return svcStatusResp.Code, nil
}
func (s *Set) AddDocument(ctx context.Context, doc *internal.Document) (string, error) {
resp, err := s.AddDocumentEndpoint(ctx, AddDocumentRequest{Document: doc})
if err != nil {
return "", err
}
adResp := resp.(AddDocumentResponse)
if adResp.Err != "" {
return "", errors.New(adResp.Err)
}
return adResp.TicketID, nil
}
func (s *Set) Status(ctx context.Context, ticketID string) (internal.Status, error) {
resp, err := s.StatusEndpoint(ctx, StatusRequest{TicketID: ticketID})
if err != nil {
return internal.Failed, err
}
stsResp := resp.(StatusResponse)
if stsResp.Err != "" {
return internal.Failed, errors.New(stsResp.Err)
}
return stsResp.Status, nil
}
func (s *Set) Watermark(ctx context.Context, ticketID, mark string) (int, error) {
resp, err := s.WatermarkEndpoint(ctx, WatermarkRequest{TicketID: ticketID, Mark: mark})
wmResp := resp.(WatermarkResponse)
if err != nil {
return wmResp.Code, err
}
if wmResp.Err != "" {
return wmResp.Code, errors.New(wmResp.Err)
}
return wmResp.Code, nil
}
var logger log.Logger
func init() {
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
}
Создайте файл http.go
Далее мы создадим файл http.go для передачи функций по ходу сервиса:
import (
"context"
"encoding/json"
"net/http"
"os"
"github.com/velotiotech/watermark-service/internal/util"
"github.com/velotiotech/watermark-service/pkg/watermark/endpoints"
"github.com/go-kit/kit/log"
httptransport "github.com/go-kit/kit/transport/http"
)
func NewHTTPHandler(ep endpoints.Set) http.Handler {
m := http.NewServeMux()
m.Handle("/healthz", httptransport.NewServer(
ep.ServiceStatusEndpoint,
decodeHTTPServiceStatusRequest,
encodeResponse,
))
m.Handle("/status", httptransport.NewServer(
ep.StatusEndpoint,
decodeHTTPStatusRequest,
encodeResponse,
))
m.Handle("/addDocument", httptransport.NewServer(
ep.AddDocumentEndpoint,
decodeHTTPAddDocumentRequest,
encodeResponse,
))
m.Handle("/get", httptransport.NewServer(
ep.GetEndpoint,
decodeHTTPGetRequest,
encodeResponse,
))
m.Handle("/watermark", httptransport.NewServer(
ep.WatermarkEndpoint,
decodeHTTPWatermarkRequest,
encodeResponse,
))
return m
}
func decodeHTTPGetRequest(_ context.Context, r *http.Request) (interface{}, error) {
var req endpoints.GetRequest
if r.ContentLength == 0 {
logger.Log("Get request with no body")
return req, nil
}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return nil, err
}
return req, nil
}
func decodeHTTPStatusRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var req endpoints.StatusRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return nil, err
}
return req, nil
}
func decodeHTTPWatermarkRequest(_ context.Context, r *http.Request) (interface{}, error) {
var req endpoints.WatermarkRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return nil, err
}
return req, nil
}
func decodeHTTPAddDocumentRequest(_ context.Context, r *http.Request) (interface{}, error) {
var req endpoints.AddDocumentRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return nil, err
}
return req, nil
}
func decodeHTTPServiceStatusRequest(_ context.Context, _ *http.Request) (interface{}, error) {
var req endpoints.ServiceStatusRequest
return req, nil
}
func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
if e, ok := response.(error); ok && e != nil {
encodeError(ctx, e, w)
return nil
}
return json.NewEncoder(w).Encode(response)
}
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
switch err {
case util.ErrUnknown:
w.WriteHeader(http.StatusNotFound)
case util.ErrInvalidArgument:
w.WriteHeader(http.StatusBadRequest)
default:
w.WriteHeader(http.StatusInternalServerError)
}
json.NewEncoder(w).Encode(map[string]interface{}{
"error": err.Error(),
})
}
var logger log.Logger
func init() {
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
}
We will also create a file by the name of grpc.go to define the map of protobuf payload.
import (
"context"
"github.com/velotiotech/watermark-service/api/v1/pb/watermark"
"github.com/velotiotech/watermark-service/internal"
"github.com/velotiotech/watermark-service/pkg/watermark/endpoints"
grpctransport "github.com/go-kit/kit/transport/grpc"
)
type grpcServer struct {
get grpctransport.Handler
status grpctransport.Handler
addDocument grpctransport.Handler
watermark grpctransport.Handler
serviceStatus grpctransport.Handler
}
func NewGRPCServer(ep endpoints.Set) watermark.WatermarkServer {
return &grpcServer{
get: grpctransport.NewServer(
ep.GetEndpoint,
decodeGRPCGetRequest,
decodeGRPCGetResponse,
),
status: grpctransport.NewServer(
ep.StatusEndpoint,
decodeGRPCStatusRequest,
decodeGRPCStatusResponse,
),
addDocument: grpctransport.NewServer(
ep.AddDocumentEndpoint,
decodeGRPCAddDocumentRequest,
decodeGRPCAddDocumentResponse,
),
watermark: grpctransport.NewServer(
ep.WatermarkEndpoint,
decodeGRPCWatermarkRequest,
decodeGRPCWatermarkResponse,
),
serviceStatus: grpctransport.NewServer(
ep.ServiceStatusEndpoint,
decodeGRPCServiceStatusRequest,
decodeGRPCServiceStatusResponse,
),
}
}
func (g *grpcServer) Get(ctx context.Context, r *watermark.GetRequest) (*watermark.GetReply, error) {
_, rep, err := g.get.ServeGRPC(ctx, r)
if err != nil {
return nil, err
}
return rep.(*watermark.GetReply), nil
}
func (g *grpcServer) ServiceStatus(ctx context.Context, r *watermark.ServiceStatusRequest) (*watermark.ServiceStatusReply, error) {
_, rep, err := g.get.ServeGRPC(ctx, r)
if err != nil {
return nil, err
}
return rep.(*watermark.ServiceStatusReply), nil
}
func (g *grpcServer) AddDocument(ctx context.Context, r *watermark.AddDocumentRequest) (*watermark.AddDocumentReply, error) {
_, rep, err := g.addDocument.ServeGRPC(ctx, r)
if err != nil {
return nil, err
}
return rep.(*watermark.AddDocumentReply), nil
}
func (g *grpcServer) Status(ctx context.Context, r *watermark.StatusRequest) (*watermark.StatusReply, error) {
_, rep, err := g.status.ServeGRPC(ctx, r)
if err != nil {
return nil, err
}
return rep.(*watermark.StatusReply), nil
}
func (g *grpcServer) Watermark(ctx context.Context, r *watermark.WatermarkRequest) (*watermark.WatermarkReply, error) {
_, rep, err := g.watermark.ServeGRPC(ctx, r)
if err != nil {
return nil, err
}
return rep.(*watermark.WatermarkReply), nil
}
func decodeGRPCGetRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*watermark.GetRequest)
var filters []internal.Filter
for _, f := range req.Filters {
filters = append(filters, internal.Filter{Key: f.Key, Value: f.Value})
}
return endpoints.GetRequest{Filters: filters}, nil
}
func decodeGRPCStatusRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*watermark.StatusRequest)
return endpoints.StatusRequest{TicketID: req.TicketID}, nil
}
func decodeGRPCWatermarkRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*watermark.WatermarkRequest)
return endpoints.WatermarkRequest{TicketID: req.TicketID, Mark: req.Mark}, nil
}
func decodeGRPCAddDocumentRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*watermark.AddDocumentRequest)
doc := &internal.Document{
Content: req.Document.Content,
Title: req.Document.Title,
Author: req.Document.Author,
Topic: req.Document.Topic,
Watermark: req.Document.Watermark,
}
return endpoints.AddDocumentRequest{Document: doc}, nil
}
func decodeGRPCServiceStatusRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
return endpoints.ServiceStatusRequest{}, nil
}
func decodeGRPCGetResponse(_ context.Context, grpcReply interface{}) (interface{}, error) {
reply := grpcReply.(*watermark.GetReply)
var docs []internal.Document
for _, d := range reply.Documents {
doc := internal.Document{
Content: d.Content,
Title: d.Title,
Author: d.Author,
Topic: d.Topic,
Watermark: d.Watermark,
}
docs = append(docs, doc)
}
return endpoints.GetResponse{Documents: docs, Err: reply.Err}, nil
}
func decodeGRPCStatusResponse(_ context.Context, grpcReply interface{}) (interface{}, error) {
reply := grpcReply.(*watermark.StatusReply)
return endpoints.StatusResponse{Status: internal.Status(reply.Status), Err: reply.Err}, nil
}
func decodeGRPCWatermarkResponse(ctx context.Context, grpcReply interface{}) (interface{}, error) {
reply := grpcReply.(*watermark.WatermarkReply)
return endpoints.WatermarkResponse{Code: int(reply.Code), Err: reply.Err}, nil
}
func decodeGRPCAddDocumentResponse(ctx context.Context, grpcReply interface{}) (interface{}, error) {
reply := grpcReply.(*watermark.AddDocumentReply)
return endpoints.AddDocumentResponse{TicketID: reply.TicketID, Err: reply.Err}, nil
}
func decodeGRPCServiceStatusResponse(ctx context.Context, grpcReply interface{}) (interface{}, error) {
reply := grpcReply.(*watermark.ServiceStatusReply)
return endpoints.ServiceStatusResponse{Code: int(reply.Code), Err: reply.Err}, nil
}
Теперь служба будет готова к внедрению, и контейнерные микросервисы в Golang можно будет совместно использовать с клиентом. Вы также можете преобразовать весь интерфейс в служебный файл. В этом блоге уже предполагается, что вы знаете, как создать pb-файл. Вы можете создать pb-файл, пройдя по одному из приведенных выше путей и используя proto-файл.
Комментарии
Отправить комментарий