Como criar um serviço em Golang

Gabriel Beletti
3 min readApr 23, 2022

--

Quando iniciamos os estudos em Go encontramos muitos exemplos de como criar um servidor web ou fazer uma determinada tarefa, no entanto pode ser desafiador encontrar uma estrutura de serviço que inicie os processos, aguarde e então finalize esses processos de maneira robusta e elegante. Este artigo tem como propósito criar uma estrutura básica de um serviço. O código completo está disponível no github.

O que é um serviço?

Em sistemas computacionais um serviço é um programa que executa tarefas específicas em resposta a eventos ou requisições, como por exemplo:

  • requisições HTTP
  • mensagens em sistema de mensageria
  • evento temporal (de hora em hora, a cada 5 minutos, etc)

Um serviço é projetado para rodar para sempre e, como tudo que é bom um dia acaba, deve também se preparar para finalizar de maneira robusta e inteligente.

Antes de um serviço terminar é importante que todas as tarefas estejam finalizadas, todas as conexões fechadas e que todas as goroutines tenham terminado.

Criando o servidor web

Como exemplo vamos criar um serviço que responde a requisições HTTP. Primeiro criamos o pacote com as funções de inicialização e finalização do servidor web.

Servidor HTTP

Repare que a função Start() cria uma goroutine para receber e processar as requisições externas, desta forma a criação e finalização desta goroutine é de responsabilidade deste pacote.

Já o Shutdown() termina o servidor graciosamente de maneira assíncrona, permitindo que o serviço finalize outros processos enquanto aguarda todas as requisições serem processadas.

Iniciando o serviço

Agora podemos ir para o arquivo inicial, por exemplo main.go, e começar criando a função start() . Aqui devemos iniciar tudo que o serviço precisa para funcionar, pode ser abrir conexão com o banco de dados, com sistema de mensageria, iniciar cache, etc.

Repare que no exemplo é criado um contexto com cancelamento. O propósito é que os processos que são iniciados nesta função compartilhem o mesmo contexto, desta forma quando a função cancel() for chamada todos os processos que receberem este contexto poderão parar seus processamentos. Além disso nenhuma das funções devem ser bloqueantes, cada processo interno deve ser responsável por criar sua própria goroutine, como no exemplo web acima.

Aguardando o fim

Agora que nosso serviço iniciou nós não podemos deixar a thread principal terminar. Para isso poderíamos criar um canal e aguardar uma publicação que nunca ocorrerá.

O código acima rodará indefinidamente até o sistema operacional matar o processo. Esta morte abrupta pode causar diversos problemas em um sistema, como gerar dados inconsistentes no banco de dados ou deixar uma conexão aberta com o sistema de mensageria. Isto gera um sistema instável e com grande dificuldade de encontrar bugs.

Devemos então nos preparar para finalizar o serviço e parar todo o processamento de maneira elegante e robusta. Para isso vamos criar um novo pacote servicemanager e criar a função WaitShutdown .

A função signal.Notify fará com que os sinais de término enviado pelo sistema operacional sejam publicados no canal sigce com isso podemos bloquear a thread principal com o comando s := <-sigc.

Nosso main.go agora inicia todos os processos e aguarda para sempre até o sistema operacional dizer que é hora de parar tudo.

Finalizando o serviço

Agora que sabemos quando o processo morrerá nós podemos criar a função shutdown() para parar todos os processos internos do serviço.

Com o comando <-doneHTTP o processo aguardará até que todas as requisições sejam finalizadas antes do serviço deixar de existir. O problema aqui é que muitas vezes o sistema operacional pode não esperar que todas as requisições sejam concluídas e terminar o processo abruptamente. Então é uma boa prática finalizar tudo dentro de um determinado tempo, mesmo que forçadamente.

Esse processo de aguardar a finalização ou terminar é comum entre os serviços, então podemos criar uma nova função no nosso pacote servicemanager .

Com isso o nosso shutdown() fica completo desta forma:

Após cancelar o contexto principal permitindo que os processos internos do serviço concluam suas tarefas, um novo contexto com timeout é criado para que aguarde a conclusão por um determinado período e finalmente termine por conta própria.

Nossa função main() fica simples e curta:

Conclusão

Criar um novo serviço pode parecer simples, mas a compreensão pode ser bastante complexa e requer um bom conhecimento de concorrência e paralelismo. O código completo deste exemplo encontrasse no github.

Espero que este tutorial tinha sido útil e deixe seu comentário na seção abaixo.

--

--

Gabriel Beletti
Gabriel Beletti

Written by Gabriel Beletti

Backend developer | Tech Lead | Engineering Manager | Golang | Kubernetes | Microservices

Responses (1)