Vá além dos globais: um guia prático para injeção de dependência
Ao criar uma aplicação no GO, é tentador buscar variáveis globais para compartilhar recursos como a configuração ou um madeireiro. É fácil, mas é um caminho carregado de dívida técnica. Neste artigo, tentaremos investigar a melhor abordagem para disponibilizar nossas dependências para todos os nossos manipuladores, é uma abordagem pela qual me apaixonei e uso agora em quase todos os meus projetos. {// Inicialize o Logger e Config … // Registre manipuladores …} Func HealthCheckHandler (W http.ResponseWriter, R *http.request) {// Este manipulador secretamente depende do estado global. // Não está claro na assinatura do que precisa ser executado. Dados: = mapa[string]String {“Status”: “Disponível”, “Ambiente”: AppConfig.env, // Usando o Global Config} // … Escreva Resposta …} Digite o modo de saída do modo de tela cheia. Este código funciona, mas oculta uma bomba -relógio. Cria dependências “mágicas” implícitas que tornam nossa aplicação: difícil de testar: como você testa um manipulador que se baseia no estado global? Você precisa manipular esses globais, o que pode levar a testes escamosos que interferem entre si. Difícil de raciocinar: Olhando para a assinatura do HealthCheckHandler, você não tem idéia de que ele precisa do AppConfig. A função não é honesta sobre seus requisitos. Menos reutilizável: o manipulador agora está fortemente acoplado ao pacote principal e não pode ser facilmente usado em outros lugares. Uma arquitetura mais limpa: injeção de dependência Vamos criar uma API simples para vê -la em ação, concentrando -se em três dependências comuns: uma estrutura de configuração para configurações de aplicação. Um logger de slog estruturado para registro consistente. Um conjunto de métodos auxiliares para o manuseio centralizado de erros JSON. Dependência nº 1: Configuração do aplicativo Quase todos os aplicativos precisam de configuração (porta, ambiente, versão etc.). Vamos definir uma estrutura para manter esses dados. // em main.go Tipo de configuração de configuração {port int Env string} Digite o modo de tela cheia de saída de tela cheia de dependência nº 2: a estrutura do aplicativo Esta estrutura é o nosso contêiner central de dependência. Começaremos adicionando campos para nossa configuração e um madeireiro estruturado. // em main.go Import (“Log/Slog”) TIPO APLICAÇÃO STRUCT {Config Config Logger *slog.logger} Digite o modo de tela fullcreen Sair Modo de tela cheia dependência nº 3: Métodos de dependência centralizados A injeção de dependência não é apenas para dados; É também para comportamento. Podemos definir métodos auxiliares diretamente em nossa estrutura de aplicativos. Isso é perfeito para tarefas como enviar respostas consistentes de erro JSON. Esses ajudantes terão acesso a outras dependências, como o nosso Logger! // No pacote helphers.go Importação principal (“Encoding/JSON” “net/http”) // O método ErRorreSponse envia uma mensagem de erro JSON-Formatted para o cliente // com um determinado código de status. Ele usa o nosso Application Logger! Func (App *Application) ERRRRESPONSEIRO (W http.ResponseWriter, r *http.request, status int, mensagem qualquer) {Env: = mapa[string]qualquer {“error”: message} js, err: = json.marshal (Env) se err! = nil {App.logger.error (“falhou em marechar a resposta do erro”, “error”, err) w.writeheader (http.statusInternalSerror) retornar} w.Header (). W.WriteHeader (Status) W.Write (JS)} // Um ajudante específico para erros do servidor (500). Func (App *Application) ServerErRRESPONSE (W http.ResponseWriter, r *http.request, erro error) {App.logger.error (“Erro do servidor”, “request_method”, R.Method, “solicitação_url”, r.url.string (), “erro”, err): ” r, http.statusInternalServerRorror, mensagem)} Digite o modo de saída do modo de tela cheia, centralizando isso, garantimos que todas as nossas respostas de erro tenham a mesma aparência e sejam devidamente registradas. Nossos manipuladores permanecem limpos e secos (não se repita). Registrando -o: escrevendo manipuladores como métodos, nossos manipuladores se tornam métodos sobre *aplicação, dando a eles acesso a tudo o que configuramos. // In Handlers.go Pacote principal Importação principal (“Encoding/JSON” “net/http”) // Este manipulador de HealthCheck agora pode acessar a configuração e o logger do aplicativo. func (app *aplicativo) HealthCheckHandler (w http.Responsewriter, r *http.request) {data: = mapa[string]String {“Status”: “Disponível”, “Ambiente”: App.config.env, “Port”: fmt.sprintf (“%d”, app.config.port),} w.Header (). *APLICATIVO) CREATESERHANDLER (W http.ResponseWriter, r *http.request) {var struct de entrada {name string `json:” name “` email string `json:” email “`} err: = json.newdecoder (r.body) .decode (& input) se err! App.erRRRESPONSE (W, R, http.statusbadrequest, “Body de solicitação inválida”) retornar} // … lógica de validação para nome e email … app.logger.info (“Criando novo usuário”, “nome”, input.name, “email, input. w.WriteHeader (http.statuscreated) // … Envie a resposta de sucesso …} Digite o modo de saída de tela cheia de tela cheia no final do main.Gofinalmente, inicializamos nossas dependências, “injete -as” em uma instância de aplicação e registre nossos métodos com o roteador. // em main.go pacote principal importação principal (“flag” “fmt” “log/slog” “net/http” “os”) // … definição de estrutura de configuração … // … definição de estrutura do aplicativo … fun main () {var cfg config // 1. “Desenvolvimento”, “Ambiente (Desenvolvimento | estadiamento | Produção)”) flag.parse () logger: = slog.new (slog.newjsonHandler (os.stdout, nil)) // 2. Crie e injete no aplicativo Struct App: = & Application {config: cfg: logger: logger,} // 3. mux.HandleFunc(“GET /v1/healthcheck”, app.healthcheckHandler) mux.HandleFunc(“POST /v1/users”, app.createUserHandler) logger.Info(“starting server”, “addr”, fmt.Sprintf(“:%d”, cfg.port), “env”, cfg.env) err := http.ListenAndServe (fmt.sprintf (“:%d”, cfg.port), mux) logger.error (“falha no servidor”, “error”, err) os.exit (1)} Digite o modo de tela cheia de tela completa. Dá: código explícito: suas assinaturas de função são honestas. É claro que seus manipuladores exigem que um contexto de aplicativo funcione. Testabilidade Superior: Você pode criar facilmente uma instância de aplicativo simulada em seus testes com um logger simulado e uma configuração fixa, permitindo testar a lógica do manipulador em perfeito isolamento. Escala sustentável: adicionar uma nova dependência compartilhada (como um limitador de taxa ou cliente de email) é tão simples quanto adicionar um campo à estrutura do aplicativo. Nenhuma refatoração confusa é necessária. Ao afastar -se das variáveis globais e adotar o padrão de estrutura do aplicativo para injeção de dependência, estamos fazendo mais do que apenas limpar nosso código – estamos estabelecendo uma base robusta para o futuro de nossa aplicação. A beleza desse padrão está em sua extensibilidade. Embora tenhamos focado na configuração, log e ajudantes, você pode ver com que facilidade isso pode se expandir para incluir outras dependências comuns: um pool de conexão com o banco de dados *sql.db. Um cache pré-compilado *template.template. Um cliente para um serviço externo (como Stripe ou um provedor de email). Essa abordagem fornece um ponto de partida fantástico para aplicativos de pequeno a médio porte. Experimente esse padrão no seu próximo projeto. Isso levará ao código mais limpo, mais profissional e um prazer de manter à medida que cresce. Obrigado e codificação feliz
Fonte
Publicar comentário