-
Son fáciles de mantener y de poner a prueba.
-
Poseen bajo acoplamiento.
-
Se despliegan independientemente.
-
Se organizan con base a las capacidades del negocio.
-
Son propiedad de un grupo pequeño de personas.
Los microservicios son fáciles de mantener/probar, se escalan más eficientemente, utilizan menos recursos y se enfocan más en procesos específicos que brindan valor a la compañía.
Pequeño y simple
Golang es un lenguaje de programación creado hace 10 años y diseñado por un equipo de Google. Sus características principales incluyen una sintaxis similar a C, es un lenguaje fuertemente tipado y cuenta con un recolector de basura. Además, ofrece un excelente manejo de concurrencias y programación paralela, por lo que funciona de maravilla con los microservicios.
“Al día de hoy las aplicaciones utilizan un número de servicios externos: bases de datos, caches, búsquedas y filas de mensajería. No obstante, más y más especialistas utilizan soluciones de microservicios debido a su colección de componentes separados. Al escribir código en Go, los desarrolladores utilizan entradas y salidas asincrónicas (I/O asincrónico) para que la aplicación pueda interactuar con un número de servicios sin bloquear solicitudes web.”
— Vyacheslav Pinchuk, desarrollador Golang de QArea
Otra característica relevante de Golang es que el núcleo de funcionalidades del lenguaje se mantiene pequeño. Este no es un lenguaje lleno de funcionalidades pre-construidas al que quizás estemos acostumbrados, como Python o Ruby. A cambio, se nos brinda una serie pequeña de capacidades básicas que podemos utilizar para crear capacidades más complejas. Pero, ¿esto significa que debemos escribir y preparar manualmente todo el soporte especializado para nuestro microservicio? Evidentemente, la respuesta es no.
Go kit entra en escena
Go kit es un toolkit de programación para construir microservicios en Go. Fue creado para solucionar problemas comunes en sistemas distribuidos y aplicaciones y permitirle a los desarrolladores enfocarse en la parte comercial de la programación. Básicamente, es un grupo de paquetes relacionados que, en conjunto, crean un framework para construir grandes arquitecturas orientadas a servicios (SOA por sus siglas en inglés). Su principal capa de transporte es la llamada de procedimiento remoto (RPC por si siglas en inglés), pero también puede utilizar JSON por medio de HTTP y es fácil de integrar con los componentes infraestructurales más comunes, para reducir la fricción de despliegue y promover la interoperabilidad con sistemas existentes.
Lógica de negocio
Vamos a crear un servicio mínimo Go kit con nuestra lógica de negocio. Siguiendo los ejemplos de Go kit, crearemos nuestro propio ‘string service’ que nos permita manipular los ‘strings’ en ciertas formas. Tomamos ventaja del tipo de interfaz Golang para definir nuestra lógica. Esto nos permite intercambiar implementaciones dependiendo de nuestras necesidades.
//File: service.go
package mystr
import (
"errors"
"strings"
"github.com/go-kit/kit/log"
)
type Service interface {
IsPal(string) error
Reverse(string) string
}
type myStringService struct {
log log.Logger
}
func (svc *myStringService) IsPal(s string) error {
reverse := svc.Reverse(s)
if strings.ToLower(s) != reverse {
return errors.New("Not palindrome")
}
return nil
}
func (svc *myStringService) Reverse(s string) string {
rns := []rune(s) // convert to rune
for i, j := 0, len(rns)-1; i ‹ j; i, j = i+1, j-1 {
// swap the letters of the string,
// like first with last and so on.
rns[i], rns[j] = rns[j], rns[i]
}
// return the reversed string.
return strings.ToLower(string(rns))
}
Solicitudes y respuestas
En Go kit, el principal patrón de mensajería es RPC. Esto significa que cada método en nuestra interfaz debe modelarse como una interacción client-server. El programa de solicitudes es un cliente, y el servicio es el servidor. Esto nos permite especificar los parámetros y los tipos de retorno para cada método.
//requests.go
package mystr
type IsPalRequest struct {
Word string `json:"word"`
}
type ReverseRequest struct {
Word string `json:"word"`
}
//responses.go
package mystr
type IsPalResponse struct {
Message string `json:"message"`
}
type ReverseResponse struct {
Word string `json:"reversed_word"`
}
Endpoints
Aquí es donde entra en juego una de las principales funcionalidades de Go kit: ‘endpoints’. Estas son abstracciones que brinda Go kit y funcionan muy parecido a una acción o un ‘handler’ en un controlador. Es también donde se agrega la lógica de seguridad. Cada ‘endpoint’ representa un método único en la interfaz de nuestro servicio.
package mystr
import (
"context"
"errors"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
)
type Endpoints struct {
GetIsPalindrome endpoint.Endpoint
GetReverse endpoint.Endpoint
}
func MakeEndpoints(svc Service, logger log.Logger, middlewares []endpoint.Middleware) Endpoints {
return Endpoints{
GetIsPalindrome: wrapEndpoint(makeGetIsPalindromeEndpoint(svc, logger), middlewares),
GetReverse: wrapEndpoint(makeGetReverseEndpoint(svc, logger), middlewares),
}
}
func makeGetIsPalindromeEndpoint(svc Service, logger log.Logger) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req, ok := request.(*IsPalRequest)
if !ok {
level.Error(logger).Log("message", "invalid request")
return nil, errors.New("invalid request")
}
msg := svc.IsPal(ctx, req.Word)
return &IsPalResponse{
Message: msg,
}, nil
}
}
func makeGetReverseEndpoint(svc Service, logger log.Logger) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req, ok := request.(*ReverseRequest)
if !ok {
level.Error(logger).Log("message", "invalid request")
return nil, errors.New("invalid request")
}
reverseString := svc.IsPal(ctx, req.Word)
return &ReverseResponse{
Word: reverseString,
}, nil
}
}
func wrapEndpoint(e endpoint.Endpoint, middlewares []endpoint.Middleware) endpoint.Endpoint {
for _, m := range middlewares {
e = m(e)
}
return e
}
Los ‘middlewares’ son una de las estructuras que brinda Go kit para garantizar la seguridad. Éstos funcionan del mismo modo en que lo hacen en otros lenguajes. Son métodos que se ejecutan por medio de la solicitud antes de que llegue a un ‘handler’. Aquí es donde se pueden añadir funcionalidades como ‘logging’, balance de carga, rastreo, etc.
Registro
En el fragmento de código anterior introducimos dos importantes componentes de Go kit. El primero es, naturalmente, el paquete ‘endpoint’. El otro es el ‘logger’. Todos estamos familiarizados con los ‘logs’ (esa herramienta tan práctica que nos permite encontrar dónde suceden esos errores tan molestos). Go kit viene con su propio paquete ‘logger’, el cual nos permite escribir mensajes estructurados que son fáciles de consumir por parte de otros humanos u otras computadoras. Estos implementan una estructura de valores clave que nos permite crear un registro conjunto de varias piezas de información sin tener que escribir varios logs.
Similarmente, también podemos utilizar el paquete ‘log level gokit’. Este paquete nos brinda una capa adicional de información. El tipo de log usual de error, debug, info, etc. al que estamos acostumbrados.
Transporte
El archivo de transporte está destinado a representar la capa correspondiente del modelo OSI. Es el archivo que contiene todas las partes del código responsables por la estrategia de comunicación de principio a fin. Salido de fábrica, Go kit brinda soporte para muchos transportes. Para simplificar, trabajaremos con JSON por medio de HTTP.
El transporte consiste en una serie de objetos de Servidor, uno por cada ‘endpoint’ de nuestro servicio, y recibe cuatro parámetros. Estos son los siguientes:
- Un ‘endpoint’: El ‘handler’ de la solicitud. Dada la separación de las estructuras y responsabilidades, podemos utilizar la misma estructura a lo largo de múltiples implementaciones de transporte.
- Una función de decodificación: Esta función recibe las solicitudes externas y las traduce a código Golang.
- Una función de codificación: Es el opuesto de la función de decodificación. Esta función traduce la respuesta Golang a la salida correspondiente para el transporte seleccionado.
- Una serie de opciones de servidor: Esta serie de opciones podría ser credenciales, codec, mantener parámetros vivos, etc. Estos le brindan capacidades adicionales a nuestra capa de transporte.
package mystr
import (
"context"
"encoding/json"
"errors"
"net/http"
"github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http"
)
func GetIsPalHandler(ep endpoint.Endpoint, options []httptransport.ServerOption) *httptransport.Server {
return httptransport.NewServer(
ep,
decodeGetIsPalRequest,
encodeGetIsPalResponse,
options...,
)
}
func decodeGetIsPalRequest(_ context.Context, r *http.Request) (interface{}, error) {
var req IsPalRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
func encodeGetIsPalResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
resp, ok := response.(*IsPalResponse)
if !ok {
return errors.New("error decoding")
}
return json.NewEncoder(w).Encode(resp)
}
func GetReverseHandler(ep endpoint.Endpoint, options []httptransport.ServerOption) *httptransport.Server {
return httptransport.NewServer(
ep,
decodeGetReverseRequest,
encodeGetReverseResponse,
options...,
)
}
func decodeGetReverseRequest(_ context.Context, r *http.Request) (interface{}, error) {
var req ReverseRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
func encodeGetReverseResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
resp, ok := response.(*ReverseResponse)
if !ok {
return errors.New("error decoding")
}
return json.NewEncoder(w).Encode(resp)
}
Atando cabos
Ahora ponemos a prueba nuestro trabajo. Tenemos todo lo necesario para que nuestro microservicio comience a trabajar. Ahora solamente debemos comenzar la escucha de solicitudes.
package main
import (
"net/http"
"os"
"bitbucket.org/aveaguilar/stringsvc1/pkg/mystr"
"github.com/go-kit/kit/endpoint"
kitlog "github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
httptransport "github.com/go-kit/kit/transport/http"
"github.com/gorilla/mux"
)
func main() {
var logger kitlog.Logger
{
logger = kitlog.NewLogfmtLogger(os.Stderr)
logger = kitlog.With(logger, "ts", kitlog.DefaultTimestampUTC)
logger = kitlog.With(logger, "caller", kitlog.DefaultCaller)
}
var middlewares []endpoint.Middleware
var options []httptransport.ServerOption
svc := mystr.NewService(logger)
eps := mystr.MakeEndpoints(svc, logger, middlewares)
r := mux.NewRouter()
r.Methods(http.MethodGet).Path("/palindrome").Handler(mystr.GetIsPalHandler(eps.GetIsPalindrome, options))
r.Methods(http.MethodGet).Path("/reverse").Handler(mystr.GetReverseHandler(eps.GetReverse, options))
level.Info(logger).Log("status", "listening", "port", "8080")
svr := http.Server{
Addr: "127.0.0.1:8080",
Handler: r,
}
level.Error(logger).Log(svr.ListenAndServe())
}
Aquí, solamente estamos utilizando el paquete Golang HTTP básico para comenzar un servidor HTTP que escucha el puerto 8080. Utilizamos el paquete mux router para obtener un manejo más fácil del método HTTP. Cualquier error será capturado por el logger Go kit que hemos inicializado.
Hay que recordar que debido a que estamos creando un servicio muy simple, no utilizamos ‘middlewares’ u opciones de servidor. Pero estas están disponibles para brindar funcionalidad y seguridad a nuestra aplicación.
Así que ponemos en marcha nuestro servicio:
$go run cmd/stringsvc1/main.go
level=info ts=2020-07-26T00:44:15.447990239Z caller=main.go:30 status=listening port=8080
¡Y comenzamos con las solicitudes!
$curl -XGET '127.0.0.1:8080/palindrome' -d'{"word": "palindrome"}'
{"message":"Is not palindrome"}
$curl -X GET '127.0.0.1:8080/palindrome' -d'{"word": "1234554321"}'
{"message":"Is palindrome"}
$curl -X GET '127.0.0.1:8080/reverse' -d'{"word": "microservice"}'
{"reversed_word":"ecivresorcim"}
¡Y eso es todo! Aunque omitimos algunos de los paquetes básicos, este es un ejemplo de lo mínimo que necesitamos hacer para poder crear nuestro propio servicio. Solo con un poco de archivos y líneas de código, podemos crear una aplicación. Go kit hace que sea muy fácil.
Es clave visitar el sitio web de Go kit para más información. Brinda ejemplos más complejos así como muy buenos artículos que permiten conocer el kit más a fondo. Además, GoDoc es un excelente recurso con el que se puede contar al adentrarse en el mundo de Golang.
Referencias
- https://microservices.io/
- https://golang.org/
- https://qarea.com/blog/why-you-should-write-your-next-microservice-using-golang
- https://peter.bourgon.org/go-kit/
- https://www.leanix.net/en/blog/a-brief-history-of-microservices
Puntos Clave
|