O que é e como aplicar Dependency Inversion Principle

Nesse segundo post da série de posts sobre SOLID em Go, foquemos na letra ‘D’, ou seja, no Dependency Inversion Principle.

Se você não viu o primeiro post da série, recomendo a leitura para ter uma visão geral do que é SOLID.

Antes de ver como aplicar Dependency Inversion Principle em Go, vamos relembrar seu conceito.

Dependências devem ser abstraídas, para que os módulos de alto nível não dependam dos módulos de baixo nível.

Para facilitar o entendimento, vejamos um pouco de código. Para começar, um exemplo de como violar o Dependency Inversion Principle.

Leia mais »
crop nurse with syringe ready to vaccinate patients

O que é e como utilizar Dependency Injection

Se você já ouviu falar mas não sabe ao certo o que é Dependency Injection ou como a utilizar em Golang, nesse post espero te ajudar a sanar as duas dúvidas.

Para que todos estejam na mesma página, antes de ver como utilizar, vamos falar um pouco sobre o que é essa tal Dependency Injection ou DI para os íntimos.

Podemos definir DI (Dependency Injection) como uma técnica onde os módulos recebem todas ou parte de suas dependências de forma indireta, ou seja, por parâmetro em uma função/método ou sendo passada diretamente para o campo de uma struct, onde tais parâmetros ou campos não tenham um tipo definido, mas sim uma interface.

Vantagens da Dependency Injection

Utilizar essa técnica ajuda com que nosso código tenha baixo acoplamento, o que torna a tarefa de refatorar partes do sistema muito mais fácil.

Leia mais »
photo of gray faucet

Como resolver memory leaks em maps

Uma das formas mais comuns de se fazer cache em aplicações Go é utilizando um map. Se você já fez isso, deve ter notado um aumento gradual no consumo de memória, e que normalmente após um restart da máquina ou pod volta ao “normal”.

Isso acontece devido a forma como o map funciona. Por isso, antes de ver o que podemos fazer para resolver esse tipo de problema, vamos entender melhor o map.

Para exemplificar o problema, vamos considerar uma variável do tipo map[int][128]byte, que será “carregada” com 1 milhão de elementos e que na sequência serão removidas.

package main

import (
	"fmt"
	"runtime"
)

func main() {
	n := 1_000_000
	m := make(map[int][128]byte)
	printAlloc()

	for i := 0; i < n; i++ {
		m[i] = [128]byte{}
	}
	printAlloc()

	for i := 0; i < n; i++ {
		delete(m, i)
	}

	runtime.GC()
	printAlloc()
	runtime.KeepAlive(m)
}

func printAlloc() {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	fmt.Printf("%d KB\\n", m.Alloc/1024)
}
Leia mais »
gray concrete road near forest

Mutex ou channels? Como resolver race condition

Existem basicamente duas formas para resolver problemas de race condition. Mutex e Channels.

Se você não está familiarizado com o termo race condition, não se preocupe. Esse termo é utilizado para descrever um pedaço do código que será executado por múltiplas goroutines e que, a cada execução, seu resultado pode variar devido a forma como o Go alterna a execução entre goroutines.

Para ficar um pouco mais claro, vamos dar uma olhada no código abaixo.

package main

import (
	"fmt"
	"sync"
)

var total = 0

func count(wg *sync.WaitGroup) {
	total++
	wg.Done()
}

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go count(&wg)
	}
	wg.Wait()
	fmt.Println("total: ", total)
}
Leia mais »
snow top mountain under clear sky

Como diminuir o tamanho da sua aplicação com ldflags

Como você deve ter visto no vídeo que postamos no canal, uma das formas de reduzir o tamanho de uma imagem docker para aplicações Go é utilizando a imagem scratch como base.

Mas e o binário? Como podemos reduzi-lo sem remover código?

É isso que vamos ver nesse post.

Para que tenhamos uma base de programa para testar os comandos que vamos ver nesse post, vamos escrever uma API bem simples.

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(rw, "Olá Mundo\\n")
	})

	log.Fatal(http.ListenAndServe(":8080", nil))
}
Leia mais »

Como colocar rótulo em looping

Não sei vocês, mas a primeira vez que “ouvi” falar dessa feature foi há algumas semanas atrás. Mais uma vez, não foi em nenhum blog post ou vídeo do youtube que ví a utilização dessa feature, mas sim no código fonte da linguagem.

Com a minha experiência em outras linguagens, sempre que tinha que lidar com um looping dentro de outro, a maneira mais comum de fazer um “break” dos 2 loopings era com uma variável de controle.

Nessa estratégia, o looping interno altera essa variável e faz um break. Com seu valor alterado, o looping externo também faz um “break”.

Leia mais »

Implementando um worker pool

Agora que já falamos praticamente tudo que havia para ser falado sobre goroutines e channels, vamos utilizar esse conhecimento para implementar um worker pool.

Se você não conhece o termo, um worker pool é basicamente uma coleção de threads que ficam esperando tarefas serem atribuídas a elas. Quando a thread finaliza a tarefa que foi atribuída, se torna disponível novamente para execução de uma nova tarefa.

Antes de começar a meter a mão na massa, vou deixar aqui o link para os outros posts da série sobre goroutines e channels.

O worker pool que vamos implementar irá somar os dígitos passados e armazenar o resultado.

Leia mais »

Múltiplos channels e a cláusula select

Dando continuidade ao nosso estudo de goroutines e channels, nesse post vamos falar sobre uma cláusula pouco utilizada.

Antes de começar, vou deixar os links para os outros posts caso você tenha perdido algum da série.

A cláusula select é utilizada para que uma função consiga trabalhar com múltiplos channels. Ela bloqueia a execução da função até que um dos channels esteja pronto para ser executado. Caso mais de um channel esteja pronto para ser executado, ela selecionará de forma aleatória qual executar.

Para tentar ficar um pouco mais claro, vamos escrever um pequeno programa para ilustrar o comportamento.

Leia mais »

Trabalhando com generics

Se você está no mundo Go a algum tempo, com certeza já ouviu falar desse carinha ai. Para aqueles que estão chegando agora, generics é uma das features mais pedidas/aguardadas desde que a linguagem foi liberada para o mundo.

Como já falamos em outros posts, Go é uma linguagem de tipagem forte, ou seja, sempre precisamos declarar o tipo da variável, parâmetros de uma função e seus retornos. Isso acaba fazendo com que, em algumas ocasiões, seja necessário duplicar nosso código só para que ele atenda dois tipos diferentes de dados.

Um exemplo básico desse problema seria uma função que soma uma lista de valores, onde sem generics, caso fossemos somar int64 e float64, seria necessário criar duas funções basicamente iguais, só mudando o tipo de dado do parâmetro e do retorno.

Leia mais »

Aguardando execução de múltiplas goroutines

Pense em um cenário onde você abra centenas ou talvez milhares de goroutines, porém que essa quantidade não seja fixa.

Utilizar um channel para controlar a quantidade de goroutines que já finalizaram a execução pode ser muito trabalhoso e em alguns casos até impossível já que, para um channel simples, teriamos que esperar a execução de cada goroutine antes de iniciar a próxima.

Se resolvemos usar um channel com buffer, teremos que especificar sua capacidade durante sua criação, o que também pode nos levar a criar um buffer muito pequeno, onde não conseguimos iniciar todas as goroutines que queremos de uma só vez.

Se você quiser saber mais sobre goroutines e channels antes de continuar, vou deixar aqui a lista com os 3 últimos posts onde abordamos esses assuntos:

Bom, mas se gerir a execução das goroutines com channels pode ser trabalhoso ou em alguns casos até mesmo inviável, como podemos fazer?

Leia mais »