All Projects → jegutierrez → functional_patterns_go

jegutierrez / functional_patterns_go

Licence: MIT license
Patrones funcionales en Go

Programming Languages

go
31211 projects - #10 most used programming language

Projects that are alternatives of or similar to functional patterns go

meetup
📢 Repositorio dedicado a todo lo relacionado a la meetup
Stars: ✭ 23 (-28.12%)
Mutual labels:  meetup
AngularMeetupCFP
Do you want to give a talk at an Angular Meetup meeting or have an angular to ...
Stars: ✭ 12 (-62.5%)
Mutual labels:  meetup
seek-meetup
收集台灣各地的前端活動!好聚好善!
Stars: ✭ 30 (-6.25%)
Mutual labels:  meetup
talks
Repository dédié aux soumissions de talks pour Paris TypeScript
Stars: ✭ 17 (-46.87%)
Mutual labels:  meetup
ios night
Let's talk about iOS development -- iOS Night 📱🌙 You might apply to this meetup from
Stars: ✭ 17 (-46.87%)
Mutual labels:  meetup
bogota
Bogotá, Colombia 🍺
Stars: ✭ 22 (-31.25%)
Mutual labels:  meetup
nyc-infosec
Mapping the NYC Infosec Community
Stars: ✭ 41 (+28.13%)
Mutual labels:  meetup
vue-bangalore
VueBLR Meetup Resources, Requests & Proposals
Stars: ✭ 37 (+15.63%)
Mutual labels:  meetup
meetup-slides
Speaker slides from monthly meetups and conference
Stars: ✭ 18 (-43.75%)
Mutual labels:  meetup
meetup
Official repository of React Rotterdam meetup
Stars: ✭ 17 (-46.87%)
Mutual labels:  meetup
video-recording-setup
How we do recordings at ReactVienna
Stars: ✭ 17 (-46.87%)
Mutual labels:  meetup
dev-meetup.github.io
developer meetup, seminar
Stars: ✭ 79 (+146.88%)
Mutual labels:  meetup
meetups
No description or website provided.
Stars: ✭ 13 (-59.37%)
Mutual labels:  meetup
MeetupPS
PowerShell module to interact with Meetup.com API
Stars: ✭ 15 (-53.12%)
Mutual labels:  meetup
meetup
Open source home for the VanJS Meetup 👩🏽‍💻👨🏼‍💻
Stars: ✭ 36 (+12.5%)
Mutual labels:  meetup
hackergarten.github.io
Hackgarten Homepage
Stars: ✭ 17 (-46.87%)
Mutual labels:  meetup
e-books
IT technical related e-books and PPT information, continuous updating. For those in need, Keep real, peace and love.
Stars: ✭ 470 (+1368.75%)
Mutual labels:  meetup
meetup
bftf meetup slides
Stars: ✭ 26 (-18.75%)
Mutual labels:  meetup
freecodecampba.github.io
🌐 La página de freeCodeCamp BA, con toda la info y links que necesitás.
Stars: ✭ 14 (-56.25%)
Mutual labels:  meetup
awesome-croatia
A curated list of things that show the awesome side of Croatia 😎 🇭🇷
Stars: ✭ 34 (+6.25%)
Mutual labels:  meetup

Functional patterns en Go

Recopilación de algunos patrones útiles utilizando funciones en Go.

Nota: Este repo NO es una introducción a Go, se asume que el lector tiene idea de la sintaxis básica del lenguaje, manejo de funciones y conocimiento básico de algunos paquetes de la standard library como net/http y testing. En caso de que no sea así, se recomienda primero dar una mirada al tour de Go que es una introdución oficial y muy completa al lenguaje.

Entonces, ¿hacemos programación funcional en Go?

Go tiene la capacidad de usar funciones como ciudadanos de primera clase, es decir, se pueden utilizar las funciones para pasarlas como parámetro o retornarlas como valor de otra función (esto es conocido como funciones de orden superior). Sin embargo, como todo en software es acerca de tradeoffs, lo recomendado es siempre preferir la claridad del código antes que cualquier patrón o paradigma.

En el siguiente post vamos a describir una serie de técnicas de programación funcional, aprovechando esta capacidad de trabajar con funciones como valor.

Que implica hacer programación funcional

La programación funcional nos trae dos grandes restricciones:

  • Usar funciones para todo: hacer todas las operaciones con funciones.
  • No mutar estado: no mutar valores una vez declarados, no tener estructuras de datos mutables, no tener side effects dentro de nuestras funciones.

Repasemos algunos de los conceptos clave en programación funcional:

Recursividad

Despues de ver que los dos requisitos importantes son trabajar con funciones y no mutar estado, una de las primeras técnicas que nos viene a la mente es la recursividad, que es la capacidad de una función de llamarse a sí misma. Esto es necesario porque es la manera de hacer loops en programación funcional.

Por lo tanto lo primero que no se puede usar para mantener inmutabilidad en nuestra aplicación es for loops, ya que vamos mutando una variable que toma diferente valor en cada iteración.

Otra de las cosas que acostumbramos a usar en Go son estructuras de datos a las que vamos agregando elementos, esto tampoco podríamos si seguimos las reglas de programación funcional, aunque el array en Go es inmutable y en el caso del slice usando la función append() no estamos violando la regla, ya que nos retorna un nuevo slice cada vez que se agrega un elemento.

¿Es buena idea no usar for loops en Go?

Veamos con un ejemplo y pruebas de performance:

Escribir una función para calcular numeros de la serie de Fibonacci

Versión recursiva:

func FibonacciRecursive(n int) int {
	if n <= 1 {
		return n
	}
	return FibonacciRecursive(n-1) + FibonacciRecursive(n-2)
}

Versión usando for loop y una tabla para guardar los resultados de cada iteración:

func FibonacciFor(n int) int {
	if n <= 1 {
		return n
	}

	var n2, n1 int = 0, 1

	for i := 2; i < n; i++ {
		n2, n1 = n1, n1+n2
	}

	return n2 + n1
}

Benchmark para n=45

func BenchmarkFibonacciRecursive(b *testing.B) {
	result := FibonacciRecursive(45)

	if result != 1134903170 {
		b.Errorf("unspected result, want 1134903170, got: %d", result)
	}
}

func BenchmarkFibonacciFor(b *testing.B) {
	result := FibonacciFor(45)

	if result != 1134903170 {
		b.Errorf("unspected result, want 1134903170, got: %d", result)
	}
}

Run the benchmark using go test

go test -bench=Fibonacci

Results:

BenchmarkFibonacciRecursive-4                    1        6755780536 ns/op
BenchmarkFibonacciFor-4                 2000000000              0.00 ns/op

Vemos que hay una diferencia considerable entre las dos versiones: la recursiva tarda un poco más de 6 segundos y la versión que usa for loop tiene un tiempo cercano a cero. Esto es principalmente debido a la cantidad de llamadas anidadas en el stack de ejecuciones (podríamos optimizar nuestra versión recursiva utilizando alguna técnica de memoization, pero solo buscamos mostrar que la recursividad puede tener un costo grande en algunos casos).

Dejo todo el código y otro ejemplo utilizando tail recursion, junto con los test y benchmarks aplicados aquí.

Después de haber visto los ejemplos y benchmarks nos podemos dar cuenta que no es tan buena idea usar recursividad en todos los casos en Go, ya que no tenemos problema de que crezca call el stack, además de ser la manera más idiomática de resolver casi todos los problemas en el ecosistema de Go, sin embargo, como dije antes, lo que tenemos que priorizar siempre es la claridad del código y si para el equipo una solución funcional resulta más clara, pues entonces es la manera correcta.

Como dato para pensar, Go ya es sumamente eficiente y en la mayoría de los casos el problema de performance que vimos en los benchmarks va a ser insignificante. Normalmente vamos a tener cuellos de botella en otro lugar fuera del código, sobre todo si tenemos llamadas a través de la red, manejo de archivos, bases de datos, etc.

Funciones como valor

En Go se pueden utilizar funciones como valor y es muy común en la standard library del lenguaje.

Un ejemplo de esto es el paquete net/http, ampliamente utilizado en el ecosistema de Go para hacer servidores http (APIs REST por ejemplo).

Para ver el uso de funciones primero veamos como acostumbramos escribir un servidor HTTP básico usando el paquete net/http.

func main() {
    http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello gophers!")
    })

    http.ListenAndServe(":80", nil)
}

Un concepto fundamental en los servidores net/http son los handlers. Un handler es un objeto que implementa la interfaz http.Handler. Una forma común de escribir un handler es mediante el uso del adaptador http.HandlerFunc que es tipo que describe la firma adecuada.

Las funciones que sirven como handlers toman un http.ResponseWriter y un http.Request como argumentos. El writer de respuestas se utiliza para completar la respuesta HTTP.

Cuando creamos una aplicación web, probablemente haya alguna funcionalidad compartida que queremos ejecutar para muchos (o incluso todos) los request HTTP. Es posible que deseemos loggear cada solicitud, comprimir cada respuesta, hacer validaciones o actualizar un caché antes de realizar un procesamiento pesado.

Una forma de organizar esta funcionalidad compartida es configurarla como middleware, que es un código autónomo que actúa de forma independiente con cada request, antes o después de los handlers de aplicaciones normales. En Go, un lugar común para usar middleware es entre un servidor y sus handlers.

Los middlewares http son simples funciones que reciben y retornan un http.HandlerFunc y dentro se pueden hacer operaciones necesarias sobre el request y/o response.

Veamos un ejemplo: este middleware valida que un usuario esté autenticado, sino retorna un "404 Not found" y evita que se obtenga la información.

func onlyAuthenticated(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !isAuth(r) {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		h(w, r)
	}
}

Luego, al llamar a nuestro handler http, lo wrappeamos con el middleware de la siguiente manera.

srv := http.NewServeMux()
srv.HandleFunc("/balance/", onlyAuthenticated(balanceHandler))

Otro ejemplo es un validador. Según el tipo de datos, guardamos en un Map la función de validación correspondiente.

Supongamos que tenemos los tipos Movement y validator

type Movement struct {
	ID           int
	Amount       float64
	Fee          float64
	MovementType string
}

type validator func(Movement) bool

Luego tenemos un mapa de funciones de validación. Es importante destacar que Go trata las funciones como valor y por eso las podemos guardar en un Map.

En este caso definimos que tenemos dos tipos de datos: income y expense

  • El income debe ser positivo y el movimiento debe tener un fee asociado
  • El expense debe ser negativo
var MovementValidator = map[string]validator{
	"income": func(m Movement) bool {
		return m.Amount >= 0 || m.Fee >= 0
	},
	"expense": func(m Movement) bool {
		return m.Amount < 0
	},
}

Luego se usa el validador de la siguiente manera:

  • Declaramos 2 movimientos válidos y 1 inválido
  • Imprimimos el ID del movimiento si es inválido
func main() {
	validIncome := Movement{
		ID:           1,
		Amount:       10,
		Fee:          1,
		MovementType: "income",
	}
	validExpense := Movement{
		ID:           2,
		Amount:       -10,
		MovementType: "expense",
	}
	invalidIncomeMov := Movement{
		ID:           3,
		Amount:       10,
		MovementType: "income",
	}

	if !MovementValidator[validIncome.MovementType](validIncome) {
		log.Printf("Invalid movement %d", validIncome.ID)
	}
	if !MovementValidator[validExpense.MovementType](validExpense) {
		log.Printf("Invalid movement %d", validExpense.ID)
	}
	if !MovementValidator[invalidIncomeMov.MovementType](invalidIncomeMov) {
		log.Printf("Invalid movement %d", invalidIncomeMov.ID)
	}
}

El código completo del validador se puede encontrar aquí

Closures y partial application

Un closure es la combinación de una función y el ámbito en el que se declaró dicha función. Y la particularidad es que la función definida en el closure "recuerda" el entorno en el que se ha creado y puede acceder a valores o punteros de ese entorno en cualquier momento.

Esto es una de las cosas más poderosas para tener en cuenta al trabajar con funciones en Go.

Veamos 3 casos sacados de aplicaciones productivas, donde los closures resuelven problemas de manera muy elegante:

1. Filtrado de datos Genérico

Supongamos que tenemos tipos que comparten un tipo de dato en común y tenemos repetida la lógica de filtrados en varias partes de nuestro programa.

Aprovechando el pase de funciones y los closures para hacer una función genérica.

Tenemos los tipos AccountMovement y Debt, que no tienen mucha relación entre ellos.

type AccountMovement struct {
	ID     int
	From   string
	To     string
	Amount float64
}

type Debt struct {
	ID     int
	UserID int
	Reason string
	Amount float64
}

Y luego tenemos un slice de AccountMovements y Debts.

movements := []AccountMovement{
	{ID: 1, From: "a", To: "b", Amount: 7},
	{ID: 2, From: "c", To: "b", Amount: 14},
	...
}
debts := []Debt{
	{ID: 1, Reason: "x", UserID: 4, Amount: 16},
	{ID: 2, Reason: "x", UserID: 2, Amount: 4},
	...
}

En algunos lenguajes como Java o JavaScript tenemos la función helper filter() que puede ser usada sobre una lista, donde pasando una función predicado nos retorna una nueva lista de los elementos donde ese predicado es true. En Go, en cambio, no tenemos estas funciones en la standard library, pero podemos construirlas con un poco de ayuda de closures:

func Filter(l int, predicate func(int) bool, appender func(int)) {
	for i := 0; i < l; i++ {
		if predicate(i) {
			appender(i)
		}
	}
}

Y para llamarla en las listas de tipo AccountMovement y Debt hacemos lo siguiente:

  • Declaramos un slice de elementos AccountMovement fuera de la llamada a filter
var bigMovements []AccountMovement
Filter(len(movements), func(i int) bool {
	return movements[i].Amount > 20
}, func(i int) {
	bigMovements = append(bigMovements, movements[i])
})

var bigDebts []Debt
Filter(len(debts), func(i int) bool {
	return debts[i].Amount > 20
}, func(i int) {
	bigDebts = append(bigDebts, debts[i])
})

2. Testing

Otro lugar donde es muy común utilizar técnicas funcionales es en los tests.

Veamos cómo escribir test en Go utilizando el paquete testing:

import "testing"

func TestAbc(t *testing.T) {
    t.Error() // para indicar que el test falló
}

Veamos un caso real:

Tenemos una interfaz DB de ejemplo, que emula un repositorio con un método para guardar un usuario en la base de datos y el tipo MySQL que lo implementa.

type DB interface {
	SaveUser(u User)
}

type MySQL struct{}

func (m MySQL) SaveUser(u User) {
	// DB save
}

Usando closures en los tests se pueden mockear funciones que nos interesa testear, además de poder hacer asserts dentro del closure conservando el scope de cada ejecución de un tests.

Definimos un tipo MySQL mock:

type MockDB struct {
	MockSaveUserFn func(User)
}

func (m MockDB) SaveUser(u User) {
	m.MockSaveUserFn(u)
}

Y un helper que recibe el context del test *testing.T y en retorna la función para guardar un usuario que utiliza el t declarado en el closure.

func helperMockDB(t *testing.T) func(User) {
	t.Helper()

	return func(u User) {
		if u.ID != 0 {
			t.Errorf("user ID must not be preset")
		}
	}
}

Dentro de un test creamos el handler, inyectando el contexto del test y luego se ejecuta el assert dentro de la función SaveUser.

func TestHttpHandler(t *testing.T) {
	body := strings.NewReader(`{"name": "john"}`)
	req, err := http.NewRequest("POST", "/users", body)
	if err != nil {
		t.Fatal(err.Error())
	}

	res := httptest.NewRecorder()

	saveFn := helperMockDB(t)
	mockDB := MockDB{
		MockSaveUserFn: saveFn,
	}

	saveUserHandler(mockDB)(res, req)

	if status := res.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}
}

3. Handler http

Para hacer servidores http uno de los componentes clave es el http handler.

Veamos un caso real, suponiendo que tenemos una dependencia como NewRelic para hacer tracing de requests en nuestra API:

type FakeNewrelic struct {
	Name string
}

func NewRelicTracer(name string) FakeNewrelic {
	return FakeNewrelic{
		Name: fmt.Sprintf("trace %s", name),
	}
}

func (n *FakeNewrelic) Trace() {
	log.Printf(n.Name)
}

Algo que resulta muy útil es, en vez de que nuestro handler sea un http.HandlerFunc, que sea una función que recibe los parámetros necesarios y retorna un http.HandlerFunc. Esto nos permite recibir parámetros y crear un entorno closure donde se puede inicializar funcionalidad antes de crear nuestro handler en sí. Para que quede más claro, veamos un ejemplo.

Después de tener definida nuestra dependencia (NewRelic), vamos a ver como utilizarla en nuestro handler:

  • La función balanceHandler recibe un delay para utilizar dentro del handler.
  • balanceHandler es un closure que nos permite declarar e inicializar cualquier dependencia antes de retornar el handler. En nuestro caso inicializamos un tracer y declaramos un tipo response.
  • Despues dentro del handler func(w http.ResponseWriter, r *http.Request) podemos utilizar el tracer nr.Trace() y el delay que recibe como parámetro el balanceHandler de la siguiente manera time.Sleep(delayMs * time.Millisecond).
func balanceHandler(delayMs time.Duration) http.HandlerFunc {

	nr := NewRelicTracer("balances")

	type response struct {
		UserID int     `json:"user_id"`
		Amount float64 `json:"amount"`
	}

	return func(w http.ResponseWriter, r *http.Request) {

		nr.Trace()

		balanceUserID := strings.TrimPrefix(r.URL.Path, "/balance/")
		userID, err := strconv.Atoi(balanceUserID)
		if err != nil {
			log.Println("balanceUserID is not a number")
			w.WriteHeader(400)
		}
		balance := response{UserID: userID, Amount: 100}

		time.Sleep(delayMs * time.Millisecond)

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(balance)
	}
}

El código completo y los tests aplicados con closures se puede encontrar aquí

Funciones lambda & closures en concurrencia

Frecuentemente utilizamos técnicas funcionales para trabajar con concurrencia en Go como funciones lambda o closures.

Vamos a mostrar el ejemplo real de hacer llamadas a múltiples APIs y luego unir los resultados. Lo que buscamos es hacer múltiples requests http en paralelo y luego esperar a que vuelvan las respuestas para procesarlas.

En Go no tenemos en la standard library algo parecido a las promesas en JavaScript o Futures en Java, sin embargo utilizado goroutines, waitgroups y channel podemos construir nuestra propia manera de resolver promesas al estilo simple de Go.

Primero mostramos un ejemplo de 3 endpoint, con delays:

  • /users -> 150 ms
  • /balance -> 350 ms
  • /user-debts -> 250 ms

1. Cliente http bloqueante

Primero hacemos un cliente con los 3 request bloqueantes, sin paralelismo:

En este caso:

  • En este caso hacemos 3 requests http y cada uno bloquea hasta completarse.
  • Ignoramos los errores para hacer más concreto el ejemplo.
  • Se unen las respuesta una vez que se completaron las 3.
func GetUserStatusSync(serverURL, userID string) (UserStatus, error) {
	userResponse, _ := http.Get(fmt.Sprintf("%s/users/%s", serverURL, userID))
	balanceResponse, _ := http.Get(fmt.Sprintf("%s/balance/%s", serverURL, userID))
	debtsResponse, _ := http.Get(fmt.Sprintf("%s/user-debts/%s", serverURL, userID))

	var userInfo map[string]string
	unmarshalResponse(userResponse, &userInfo)
	var userBalance map[string]string
	unmarshalResponse(balanceResponse, &userBalance)
	var userDebts []map[string]string
	unmarshalResponse(debtsResponse, &userDebts)

	return UserStatus{
		ID:            userInfo["id"],
		Name:          userInfo["name"],
		BalanceAmount: userBalance["amount"],
		Debts:         userDebts,
	}, nil
}

2. Cliente asíncrono con waitgroups

Una llamada utilizando funciones anónimas, closures, goroutines y un waitgroup para esperar a las respuesta de los 3 endpoints.

En este caso:

  • Declaramos 1 waitgroup para esperar a los 3 requests.
  • Declaramos 3 variables userResponse, balanceResponse, debtsResponse de tipo *http.Response antes de las llamadas http.
  • Cada request http lo hacemos dentro de una lambda y cada lambda se ejecuta en una goroutine diferente y marca cómo Done() en el waitgroup una vez que tiene la respuesta.
  • Bloqueamos con el waitgroup hasta que se completen las 3 llamadas.
  • Se unen las respuesta como en el caso anterior.
func GetUserStatusAsyncWaitGroup(serverURL, userID string) (UserStatus, error) {
	var waitgroup sync.WaitGroup
	waitgroup.Add(3)

	var userResponse, balanceResponse, debtsResponse *http.Response
	go func() {
		userResponse, _ = http.Get(fmt.Sprintf("%s/users/%s", serverURL, userID))
		waitgroup.Done()
	}()
	go func() {
		balanceResponse, _ = http.Get(fmt.Sprintf("%s/balance/%s", serverURL, userID))
		waitgroup.Done()
	}()
	go func() {
		debtsResponse, _ = http.Get(fmt.Sprintf("%s/user-debts/%s", serverURL, userID))
		waitgroup.Done()
	}()
	waitgroup.Wait()

	var userInfo, userBalance map[string]string
	unmarshalResponse(userResponse, &userInfo)
	unmarshalResponse(balanceResponse, &userBalance)
	var userDebts []map[string]string
	unmarshalResponse(debtsResponse, &userDebts)

	return UserStatus{
		ID:            userInfo["id"],
		Name:          userInfo["name"],
		BalanceAmount: userBalance["amount"],
		Debts:         userDebts,
	}, nil
}

3. Cliente asíncrono con channels

Una llamada utilizando funciones anónimas, closures, goroutines y un waitgroup para esperar a las respuesta de los 3 endpoints.

En este caso:

  • Declaramos 3 channels userResponse, balanceResponse, debtsResponse.
  • Cada request http lo hacemos dentro de una lambda y cada lambda se ejecuta en una goroutine diferente y una vez que tiene la respuesta se envía por el channel a la goroutine principal.
  • Para obtener cada response, lo escuchamos del channel correspondiente, ejemplo: <-userResponse
  • Se unen las respuesta como en el caso anterior.
func GetUserStatusAsyncChannels(serverURL, userID string) (UserStatus, error) {

	userResponse := make(chan *http.Response)
	balanceResponse := make(chan *http.Response)
	debtsResponse := make(chan *http.Response)
	defer close(userResponse)
	defer close(balanceResponse)
	defer close(debtsResponse)

	go func() {
		result, _ := http.Get(fmt.Sprintf("%s/users/%s", serverURL, userID))
		userResponse <- result
	}()
	go func() {
		result, _ := http.Get(fmt.Sprintf("%s/balance/%s", serverURL, userID))
		balanceResponse <- result
	}()
	go func() {
		result, _ := http.Get(fmt.Sprintf("%s/user-debts/%s", serverURL, userID))
		debtsResponse <- result
	}()

	var userInfo, userBalance map[string]string
	unmarshalResponse(<-userResponse, &userInfo)
	unmarshalResponse(<-balanceResponse, &userBalance)
	var userDebts []map[string]string
	unmarshalResponse(<-debtsResponse, &userDebts)
	return UserStatus{
		ID:            userInfo["id"],
		Name:          userInfo["name"],
		BalanceAmount: userBalance["amount"],
		Debts:         userDebts,
	}, nil
}

Para comprobar que se ejecutan en paralelo, escribimos un test que levanta un servidor http, ejecuta los 3 clientes y observa los resultados.

func TestGetUserStatus(t *testing.T) {
	srv := httptest.NewServer(handler())
	defer srv.Close()
	userID := "2"

	start := time.Now()
	result, _ := GetUserStatusSync(srv.URL, userID)
	elapsed := time.Since(start)
	log.Printf("GetUserStatusSync took %s\n", elapsed)

	start = time.Now()
	result, _ = GetUserStatusAsyncWaitGroup(srv.URL, userID)
	elapsed = time.Since(start)
	log.Printf("GetUserStatusAsyncWaitGroup took %s\n", elapsed)

	start = time.Now()
	result, _ = GetUserStatusAsyncChannels(srv.URL, userID)
	elapsed = time.Since(start)
	log.Printf("GetUserStatusAsyncChannels took %s\n", elapsed)
	...
}

Resultados:

  • Caso bloqueante tarda 750 ms, que es el total sumado de los delays.
  • Casos 2 y 3 tardan 350 ms aprox, que es el mayor de los delays.
2019/10/12 17:21:35 server listening connections
2019/10/12 17:21:36 GetUserStatusSync took 763.135932ms
2019/10/12 17:21:36 GetUserStatusAsyncWaitGroup took 352.60757ms
2019/10/12 17:21:36 GetUserStatusAsyncChannels took 355.580379ms
PASS

El código completo y los tests aplicados se puede encontrar aquí

Aprovechando la capacidad de pasar funciones se pueden hacer patrones de concurrencia muy elegantes, manteniendo la simpleza en nuestro código.

Conclusiones

  • Utilizando funciones como ciudadanos de primera clase en Go se pueden construir aplicaciones flexibles y sin dejar de ser idiomáticas para el ecosistema.
  • Closures son una de las herramientas más poderosas que tenemos en Go, con los cuales se puede construir funciones genéricas, inicializar dependencias en los handlers, mockear dependencias en los test y construir patrones de concurrencia.
  • No conviene hacer programación funcional pura en Go, ya que no es idiomático en Go, la sintaxis no es amigable para trabajar con funciones. En todo caso, lo ideal es priorizar la claridad del código para el equipo que lo mantiene.
  • En muchos de los casos es menos eficiente la versión funcional.
  • Muchas veces usamos patrones sin darnos cuenta, es bueno identificarlos y utilizarlos en nuestras aplicaciones.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].