Sitemap

[Resolver] Golang

4 min readOct 18, 2024

Veja neste artigo como aplicar o resolver no Golang

Resolver no Golang

No desenvolvimento de software moderno, o conceito resolver vem ganhando cada vez mais espaço, especialmente quando falamos sobre a flexibilidade e a modularidade necessárias em sistemas distribuídos.

O meu primeiro contato com este padrão foi no desenvolvimento de uma API GraphQL.

Como sabemos, no GraphQL, cada campo de uma query ou mutation é resolvido por uma função chamada de resolver. Essa função é responsável por buscar os dados, transformá-los e entregá-los de forma que o cliente possa utilizá-los, seja através de uma consulta simples ao banco de dados ou uma chamada a um microserviço.

O padrão Resolver foi uma solução natural para o problema de desacoplar a lógica de recuperação de dados da lógica de consulta, permitindo maior flexibilidade na estruturação da API.

Com essa experiência inicial, vi o potencial do padrão em outras linguagens, como o Golang, onde podemos aplicá-lo para resolver chamadas dinâmicas em serviços de API, injeção de dependências ou até mesmo na escolha de rotas em sistemas mais complexos.

Neste artigo, exploraremos como aplicar o padrão resolver no Golang, através de um exemplo prático.

Caso você tenha interesse de clonar a versão final do projeto que será apresentado neste artigo, segue o seu link no meu Github: go-resolver

Analisando este projeto nós temos 3 arquivos:

service.go

package main

import "fmt"

type Service struct{}

func (s *Service) MakeRequest() {
fmt.Println("Fazendo uma requisição!")
}

Aqui nós temos uma serviço simples que contém o método MakeRequest, que apenas imprime uma mensagem simulando a execução de uma requisição.

resolver.go

package main

import (
"fmt"
"sync"
)

type Resolver struct {
service *Service
once sync.Once
}

func (r *Resolver) ResolveService() *Service {
r.once.Do(func() {
fmt.Println("Inicializando o serviço...")
r.service = &Service{}
})
return r.service
}

Aqui nós temos:

  • Uma estrutura simples que é usada para resolver (ou inicializar) o serviço de forma "lazy", ou seja, o serviço só será inicializado quando for realmente necessário, e a inicialização será feita apenas uma vez.
  • O campo once é do tipo sync.Once, que é uma funcionalidade do Go usada para garantir que um determinado bloco de código seja executado uma única vez, mesmo em ambientes concorrentes (com múltiplas goroutines).
  • O método ResolveService utiliza once.Do() para verificar se o serviço já foi inicializado. Se não foi, ele executa o código que inicializa o serviço e atribui a instância ao campo service.

main.go

package main

func main() {
resolver := &Resolver{}

service := resolver.ResolveService()
service.MakeRequest()

serviceAgain := resolver.ResolveService()
serviceAgain.MakeRequest()
}

Analisando este arquivo nós temos:

  • Criamos uma instância do Resolver, que será responsável por inicializar o serviço de forma controlada.
  • A primeira chamada a resolver.ResolveService() inicializa o serviço (já que ele ainda não foi criado) e, em seguida, chama o método MakeRequest, que imprime a mensagem "Fazendo uma requisição!".
  • A segunda chamada a resolver.ResolveService() não inicializa o serviço novamente, pois o serviço já foi instanciado na primeira chamada. Ele apenas retorna a mesma instância já criada, demonstrando o comportamento de inicialização única garantido por sync.Once.

Executando este código nós temos o seguinte resultado:

teste resolver no Golang

Até aqui tranquilo, né? O código demonstra como o Resolver pode ser utilizado para garantir a inicialização controlada e única de um serviço usando o sync.Once, o que é especialmente útil em cenários onde múltiplas goroutines podem tentar acessar o serviço ao mesmo tempo.

Bom, para ilustrar ainda mais o poder desta padrão, vamos atualizar o nosso código para lidar com fluxos de investimentos.

Para isso, imagine que em vez de inicializar um serviço simples, agora queremos que o Resolver escolha dinamicamente qual tipo de cálculo de investimento será feito com base no tipo de investimento que recebemos como input.

Atualizando a nossa service para que ela simule alguns retornos para diferentes tipos de investimentos.

package main

import "fmt"

type InvestmentService struct{}

func (s *InvestmentService) CalculateInvestment(investmentType string, amount float64) {
switch investmentType {
case "renda_fixa":
fmt.Printf("Calculando investimento em Renda Fixa. Valor final: %.2f\n", amount*1.05)
case "acoes":
fmt.Printf("Calculando investimento em Ações. Valor final: %.2f\n", amount*1.20)
default:
fmt.Println("Tipo de investimento não reconhecido.")
}
}

Agora, o Resolver que será responsável por inicializar o InvestmentService e garantir que ele seja inicializado apenas uma vez.

package main

import (
"fmt"
"sync"
)

type InvestmentResolver struct {
service *InvestmentService
once sync.Once
}

func (r *InvestmentResolver) ResolveService() *InvestmentService {
r.once.Do(func() {
fmt.Println("Inicializando o serviço de investimento...")
r.service = &InvestmentService{}
})
return r.service
}

por fim, na nossamain.go, vamos simular o uso do InvestmentResolver para realizar cálculos de diferentes tipos de investimento.

package main

func main() {
resolver := &InvestmentResolver{}

service := resolver.ResolveService()
service.CalculateInvestment("renda_fixa", 1000.0)

serviceAgain := resolver.ResolveService()
serviceAgain.CalculateInvestment("acoes", 1500.0)
}

Executando esse código nós temos:

Note que com esse novo exemplo, nós podemos ver como o padrão Resolver é uma abordagem poderosa para controlar a inicialização e o uso de serviços em sistemas que exigem flexibilidade e modularidade.

Bom, com isso finalizo mais este artigo pessoal.

--

--

No responses yet