Entendendo a diferença entre Injeção de Dependência e Inversão de Dependência em .NET 6

Na programação orientada a objetos, dois conceitos fundamentais são frequentemente mencionados: Injeção de Dependência (DI — Dependency Injection) e Inversão de Dependência (IoC — Inversion of Control). Ambos são essenciais no desenvolvimento de software moderno, especialmente ao lidar com a manutenção, testabilidade e escalabilidade do código. No contexto do .NET 6, vamos mergulhar mais fundo nesses conceitos e entender como eles são aplicados.

Inversão de Dependência (IoC):

A Inversão de Dependência é um princípio que visa inverter o fluxo de controle no seu código. Em vez de um componente de alto nível depender diretamente de componentes de baixo nível, a IoC propõe que as dependências sejam gerenciadas por um componente externo. Em outras palavras, as classes de nível superior não devem criar ou gerenciar suas próprias dependências, mas sim recebê-las de uma fonte externa.

Um exemplo de implementação disso em .NET 6 seria usar interfaces para definir contratos e, em seguida, usar essas interfaces em vez de classes concretas. Aqui está um exemplo simples:

// Interface que define um serviço
public interface IExemploServico
{
    void RealizarAcao();
}

// Implementação do serviço
public class ExemploServico : IExemploServico
{
    public void RealizarAcao()
    {
        Console.WriteLine("Ação realizada pelo serviço exemplo.");
    }
}

// Classe que depende do serviço usando IoC
public class ClasseQueUsaServico
{
    private readonly IExemploServico _servico;
    // Perceba que aqui eu não estou instanciando a classe em um novo 
    //objeto usando a palavra chave new e sim injetando a inferface extendida
    //pela classe. Ex. MinhaClasse : IMinhaClasse
    public ClasseQueUsaServico(IExemploServico servico)
    {
        _servico = servico;
    }

    public void ExecutarAcaoDoServico()
    {
        _servico.RealizarAcao();
    }
}

Em .Net é importante entender onde essas classes são instanciadas, como são gerenciados os ciclos de vida dessas instancias e como elas são despejadas (não sei se o termo certo seria esse). No exemplo acima para usar a interface IExemploServico de forma a ser injetado na classe ao qual ela será usada. Normalmente adicionadas na class Startup ou na classe Program nas versões mais novas. Na classe Program de uma api .Net6 por exemplo o escopo das instâncias das classes referenciadas pelas interfaces na injeção de decência definem como serão usadas em relação ao gerenciamento do uso dessa interface na aplicação, por exemplo.

services.AddSingleton<IExemploServico, ExemploServico>();
services.AddTransient<IExemploServico, ExemploServico();
services.AddScoped<IExemploServico, ExemploServico>();
  • Singleton: é criada uma única instância para todas requisições. Em outras palavras, é criada uma instância a primeira vez que é solicitada e todas as vezes seguintes a mesma instância é usada (design pattern singleton);
  • Scoped: é criada uma única instância por requisição. Ou seja, usando como exemplo de uma aplicação Web, quando se recebe uma nova requisição, por exemplo um click num botão do outro lado do navegador, é criada uma instância, onde o escopo é essa requisição. Então se for necessária a dependência (serviço registrado) múltiplas vezes na mesma requisição a mesma instância será usada. Seria como um “Singleton para uma requisição”;
  • Transient: sempre é criada uma nova instância cada vez que for necessário, independente da requisição, basicamente um new XXX cada vez que for necessário usar a dependência.

Um pequeno quadro para ilustrar:

O impacto no ciclo de vida é o que foi comentado acima, ou seja, com exceção do Singleton, o Scoped e Transient são impactados pelo número de requisições.

Aqui vale notar que, num serviço sem estado (stateless) ou uma aplicação sem contexto de requisição, como um “Console” por exemplo, Scoped pode ter o mesmo comportamento de Transient, uma vez que se não for possível validar se está numa mesma requisição, sempre uma nova instância será criada.

public class Program{
  public void Main()
  {
   var init = new StartUp();
   var applicationService = init.Services.GetService<IApplicationService>();
   
   applicationService.FirstMethod();
   applicationService.SecondMethod();
  }
}


public class StartUp
{
 private ServiceCollection _services;
 
 public StartUp()
 {
  _services = new ServiceCollection();
  RegisterServices(_services);
  
  var serviceProvider = _services.BuildServiceProvider();
  var service = serviceProvider.GetService<IMyClass>();
 }
 
 private void RegisterServices(IServiceCollection services)
 {
  services.AddTransient<IMyClass2, MyClass2>();
  services.AddScoped<IMyClass, MyClass>();
  
  services.AddScoped<IApplicationService, ApplicationService>();
 }
 
 public ServiceProvider Services
 { 
  get { 
  return _services.BuildServiceProvider();
 }
}

public class ApplicationService : IApplicationService
{
 private IMyClass _myClass;
 private IMyClass2 _myClass2;
 
 public ApplicationService(IMyClass myClass, IMyClass2 myClass2)
 {
  _myClass = myClass;
  _myClass2 = myClass2;
 }
 
 public void FirstMethod()
 {
  Console.WriteLine(_myClass.GetNome());
 }
 
 public async Task SecondMethod()
 {
  var result = await Task.FromResult(_myClass2.GetNome());
  Console.WriteLine(result);
 }
}


public interface IMyClass
{
 string GetNome();
}

public interface IMyClass2
{ 
 string GetNome();
}

public interface IApplicationService
{
 void FirstMethod();
 Task SecondMethod();
}

public class MyClass : IMyClass
{
 public string GetNome()
 {
  return "John Doe";
 }
}

//Os objetos serão limpos quando não estiverem mais em uso e quando o garbage collector achar conveniente.
public class MyClass2 : IMyClass2, IDisposable
{
 public string GetNome()
 {
  return "Jonh Doe";
 }
 
 public void Dispose()
    {
        Dispose();
        GC.SuppressFinalize(this);
    }
}

Nesse exemplo foram criados duas classes simples com um único método, elas poderia representar os serviços chamados por uma api e seus ciclos de vida seriam tratados de forma diferente, no services.scoped<IMyClass, MyClass> sempre que houver a necessidade de se utilizar uma instancia dessa classe ela será injetada na classe, usada e despejada em seguida, no caso da classe definida como service.transiente<IMyClass2, MyClass> ela seria injetada no construtor da classe, nesse exemplo a ApplicationService e toda vez que fosse feito uma solicitação uma nova instância seria criada e senão despejada ocupará o espaço na memória dessa nova instância, na opção service.Singleton<IMyClass2, MyClass> uma instância será criada no inicio da execução da aplicação e seu ciclo de vida se manterá até que a aplicação deixe de ser executada. 

Padronização dos commits

03/11/2023

Padronização de commit com Commitlint

Conventional Commits na prática

O commit tem que seguir a seguinte estrutura:

<tipo>[escopo opcional]: <descrição>

[corpo opcional]

[rodapé(s) opcional(is)]

A mensagem deve ser escrita com letras minúsculas, com um espaço entre o dois pontos e a descrição e sem ponto final.

Geralmente eu escrevo apenas a primeira linha, vou colocar a lista com os tipos que podemos utilizar:

  • chore: Atualização de tarefas que não ocasionam alteração no código de produção, mas mudanças de ferramentas, mudanças de configuração e bibliotecas.
  • feat: São adições de novas funcionalidades ou de quaisquer outras novas implantações ao código.
  • fix: Essencialmente definem o tratamento de correções de bugs.
  • refactor: Utilizado em quaisquer mudanças que sejam executados no código, porém não alterem a funcionalidade final da tarefa impactada.
  • docs: Inclusão ou alteração somente de arquivos de documentação.
  • perf: Uma alteração de código que melhora o desempenho.
  • style: Alterações referentes a formatações na apresentação do código que não afetam o significado do código, como por exemplo: espaço em branco, formatação, ponto e vírgula ausente etc.
  • test: Adicionando testes ausentes ou corrigindo testes existentes nos processos de testes automatizados (TDD).
  • build: Alterações que afetam o sistema de construção ou dependências externas (escopos de exemplo: gulp, broccoli, npm).
  • ci: Mudanças em nossos arquivos e scripts de configuração de CI (exemplo de escopos: Travis, Circle, BrowserStack, SauceLabs).
  • env: Utilizado na descrição de modificações ou adições em arquivos de configuração em processos e métodos de integração contínua (CI), como parâmetros em arquivos de configuração de containers.

Exemplos de Commits:

  • chore: add commitlint e husky
  • chore(eslint): obrigar o uso de aspas duplas no jsx
  • refactor: refatorando a tipagem
  • feat: add axios / buscando e tratando os dados
  • feat(page/home): criando o roteamentento no next

Dessa maneira fica muito mais fácil a leitura do histórico de commits e o entendimento do que foi feito no código. Se você trabalha sozinho no projeto, experimente ficar 6 meses sem mexer no projeto. Com os commits padronizados, ao voltar a mexer nele, fica muito mais fácil lembrar quais foram suas últimas alterações.

OBS: eu já fiquei perdido no meu próprio código em projetos mais antigos e isso acontece porque ao longo do tempo evoluímos nosso código e adotamos novos hábitos de escrita, ao ponto de não reconhecermos nosso próprio código de meses atrás.

Acabei resumindo o Conventional Commits focando na parte que é mais utilizada, mas separei o link da convenção e de um artigo mais completo sobre o assunto, não deixe de ler:

By Vitor DevSP
link

Cola de comandos git

Toure Holder ESPECIALISTA DE TECNOLOGIA

Uma lista de comandos git usados com muita frequência com exemplos e dicas.

  • Criando e alterando repositórios e branches
    • git clone
    • git checkout
    • git add
    • git commit
    • git push
    • git pull
  • Visualizando estado atual e histórico
    • git status
    • git log
  • Alterando o histórico
    • git rebase
    • Como mudar a mensagem de um commit
      • Mudar a mensagem do último commit
      • Mudar a mensagem de um commit específico
    • Como mudar a ordem de alguns commits
    • push –force
  • BÔNUS: Pro-tips
    • Dicas para manter um histórico de git sucinto, limpo (e bonito)
      • Como adicionar mudanças ao último commit
      • Como transformar X commits em 1 commit
    • Uma dica para encontrar um commit que introduziu um bug rapidamente

Criando e alterando repositórios e branches

git clone

Clonar um respositório em uma nova pasta. Veja mais

Exemplo:

git clone git@ssh.dev.azure.com:v3/wizsolucoes/SquadUnidade/meuproduto-web

git checkout

Mudar a branch local. Veja mais

Exemplo:

# Mudar para branch local chamada master.
git checkout master
 
# Criar uma nova branch local e mudar para essa branch.
git checkout -b 'feature/login'

git add

Adicionar arquivos ao index do git. (Fazer o “stage“ de arquivos.) Veja mais

Exemplos:

# Adicionar um arquivo específico
git add src/myfile.ts
 
# Adicionar arquivos somente modificados, mas não adiciona arquivos novos.
git add -u
 
# Adicionar todos os arquivos novos e modificados
git add -A

git commit

Gravar mudanças no repositório. Criar/modificar commits. Veja mais

Exemplos:

# Criar um commit com os arquivos adicionados ao index e escrever a mensagem em seguida no editor de texto.
git commit
 
# Criar um commit com os arquivos adicionados ao index com a mensagem de commit
git commit -m 'feat: implementar login'
 
# Modificar commit mais recente, adicionado alterações indexadas e/ou alterando a mensagem de commit
git commit --amend

git push

Enviar mudanças no repositório local para o repositório remoto. Veja mais

Exemplos:

# Desde Git 2.0, por default envia as mudanças da branch local atual para a branch remota associada. Antes do Git 2.0, por default envia mudanças de todas as branches locais
git push
 
# Enviar as mudanças da branch local atual para a branch remota associada
git push origin 'feature/login'
 
# Criar uma branch remote, setar como upstream, e envia da branch local atual
git push -u origin 'feature/signup'
 
# Substituir o hitórico de commits remoto com o histórico de commits local. USAR COM CUIDADO.
git push -f origin 'feature/signup'

git pull

Buscar e integrar mudanças de um repositório remoto ou outra branch local. Veja mais

Exemplos:

 
# Atualizar todas as branches existentes no repositório remoto
git pull
 
# Especificar a branch da qual deseja puxas as alterações
git pull origin master
 
# Buscar mudanças de uma branch remota e inserí-las ANTES dos commits locasl. O USO É ALTAMENTE RECOMENDADO
git pull --rebase origin master

Visualizando estado atual e histórico

git status

Mostrar o status atual do repositório local. Veja mais

Exemplo:

git status

git log

Mostrar o histórico de commits do repositório local. Veja mais

Exemplo:

git log
 
# Mostrar histórico resumido. Cada commit ocupa apenas 1 linha.
git log --oneline

Alterando o histórico

git rebase

Rebase é o processo de mudar ou combinar uma sequência de commits. Comumente usado para alterar o histórico do git. Veja mais aqui e aqui. Veja casos de uso e exemplos a seguir.

Como mudar a mensagem de um commit

Mudar a mensagem do último commit

Se você precisar mudar a mensagem do último commit, basta fazer um commit com amend e com uma nova mensagem. Não é preciso fazer um rebase neste caso.

Exemplo:

Suponha o seguinte histórico de commits:

$ git log --oneline
f76775a implementar login
74517e7 docs: readme

Você pode alterar a mensagem do último commit (f76775a) assim:

git commit --amend -m "feat: implementar login"
Mudar a mensagem de um commit específico

Se você precisa alterar a mensagem de um commit que não seja o último. É preciso fazer uma rebase interativa. O comando para iniciar a rebase git rebase.

Exemplo:

Suponha o seguinte histórico de commits:

$ git log --oneline
6d8772f test: escrever testes para login
f76775a implementar login
74517e7 docs: readme

Iniciamos a rebase interativa usando o commit ANTES dos que queremos alterar como base.

# Repare que a base é o commit antes dos commits que queremos alterar
git rebase -i 74517e7

No editor configurado pelo git vemos o seguinte (truncado):

pick f76775a implementar login
pick 6d8772f test: escrever testes para login
 
# Rebase 74517e7..6d8772f onto 74517e7 (2 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
.
.
.

Para modificar a mensagem de um commit precisamos alterar o comando pick para reword ou r. E salvar e fechar a janela do editor.

r f76775a implementar login
pick 6d8772f test: escrever testes para login
 
# Rebase 74517e7..6d8772f onto 74517e7 (2 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
.
.
.

Git irá abrir o editor novamente para que você possa escrever a nova mensagem de commit.

feat: implementar login
 
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Fri Jan 22 08:51:27 2021 -0300
#
# interactive rebase in progress; onto 74517e7
# Last command done (1 command done):
#    reword f76775a implementar login
# Next command to do (1 remaining command):
#    pick 3420fa7 test: escrever testes para login
# You are currently editing a commit while rebasing branch 'feature/login' on '74517e7'.

Salve e feche a janela do editor para finalizar a rebase.

Como mudar a ordem de alguns commits

Se você precisa mudar a ordem de commit no histórico. É preciso fazer um rebase interativa. O comando para iniciar a rebase git rebase.

Exemplo: Suponha o seguinte histórico de commits:

$ git log --oneline
169dbd1 test: escrever testes para login
0e6d5f4 feat: implementar login
74517e7 docs: readme

Iniciamos a rebase interativa usando o commit ANTES dos que queremos alterar como base.

# Repare que a base é o commit antes dos commits que queremos alterar
git rebase -i 74517e7

No editor configurado pelo git vemos o seguinte (truncado):

pick 0e6d5f4 feat: implementar login
pick 169dbd1 test: escrever testes para login
 
# Rebase 74517e7..169dbd1 onto 74517e7 (2 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit

Para mudar a ordem dos commits, basta reordenar as linhas, salvar e fechar a janela do editor.

pick 169dbd1 test: escrever testes para login
pick 0e6d5f4 feat: implementar login
 
# Rebase 74517e7..6d8772f onto 74517e7 (2 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
.
.
.

Git irá finalizar a rebase sem a necessidade de mais interação.

push –force

Depois de uma rebase interativa, o histórico de commits local pode ficar dessincronizado com o histórico remoto. Quando isso acontece, é necessário fazer um push forçado com a flag --force ou -f para sobrescrever o histórico remoto.

Exemplo:

git push -f origin 'feature/login'

BÔNUS: Pro-tips

Dicas para manter um histórico de git sucinto, limpo (e bonito)

Manter um histórico de git sucinto com apenas commits significativos facilita o entendimento das alterações feitas em um repositório e facilita encontrar e reverter commits que introduziram bugs. Seguem algumas formas de criar commits atómicos que seguem a regra: “One change, one commit”.

Como adicionar mudanças ao último commit

Sabia que é super fácil adicionar suas alterações ao último commit do histórico? Basta adicionar as alterações com git add e fazer o commit com a flag --amend.

Experimente:

git add -u
 
git commit --amend
Como transformar X commits em 1 commit

Um dos comandos disponíveis na rebase interativa é squash e é muito útil para transformar X commits em 1 commit.

Suponha o seguinte histórico de commits:

3b0320a corrigir um problema na tela de login
e880de1 corrigir typo na tela de login
43faa39 integrar com api de login
416a586 escrever a lógica da ui de login
ad9bd7d criar ui de login
6ce4688 test: escrever testes para login

Com uma rebase interativa podemos juntar os últimos 5 commits (antes de criar um PR, por exemplo):

# Usamos o commit ANTES dos que queremos alterar como base.
git rebase -i 6ce4688

No editor configurado pelo git vemos o seguinte (truncado):

pick ad9bd7d criar ui de login
pick 416a586 escrever a lógica da ui de login
pick 43faa39 integrar com api de login
pick e880de1 corrigir typo na tela de login
pick b003267 corrigir um problema na tela de login
 
# Rebase 6ce4688..b003267 onto e880de1 (5 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit

Com o comando squash ou s, podemos juntar um commit com seu commit anterior. Para juntar múltiplos commits podem fazer assim:

pick ad9bd7d criar ui de login
s 416a586 escrever a lógica da ui de login
s 43faa39 integrar com api de login
s e880de1 corrigir typo na tela de login
s b003267 corrigir um problema na tela de login
 
# Rebase 6ce4688..b003267 onto e880de1 (5 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit

Repare que é necessário usar o primeiro commit, mantendo o pick.

Git irá abrir o editor novamente para que você possa escrever a nova mensagem de commit. Depois de salvar a nova mensagem de fechar o editor, o commando git log revela que os 5 commits foram transformados em 1.

$ git log --oneline
9f3f545 feat: implementar login
6ce4688 test: escrever testes para login

Uma dica para encontrar um commit que introduziu um bug rapidamente

O time percebeu que alguma funcionalidade da aplicação que estava funcionando não está funcionando mais e ninguém sabe quando parou de funcionar?

git bisect é seu amigão.

Vejam estes links para conhecer melhor e aprender a encontrar commits ruins rapidamente.