Inversion of Control - IoC

Inversion of Control

IoC é a melhor estratégia para desacoplar um sistema. Facilita a criação de testes, separa as dependências e isola o domínio.

O que é

De acordo com Wikipédia “Inversão de controle, IoC, é um princípio abstrato que descreve o aspecto do design e arquitetura de software no qual o fluxo de controle do sistema é invertido em comparação com a programação procedural”.

Com o IoC é possível manter o domínio limpo. Sem necessidade de dependências diretas. Isola o domínio separando “o que de deve ser feito” do “como deve ser feito”. Melhora a legibilidade do código. Deixa o código flexível para as futuras modificações e facilita na criacao dos testes unitários.

Por que?

As melhores práticas de engenharia e design de software defendem a utilização do princípio SOLID, sendo inversão de dependência um destes princípios.

Seguir melhores práticas é como cortar um atalho. Existem outros caminhos, mas seguir por este aumenta as chances de sucesso. Os benefícios a longo prazo traz ganhos na qualidade do software. Há uma relação direta entre a criação de testes e a diminuição na incidência de bugs. Consequentemente há uma queda na quantidade de esforço gasto na manutenção, um benefício econômico para empresa, além de melhorar a percepção do cliente com um sistema estável.

Cenário

A seguir o consumidor, domínio, necessita da classe repositório.

Aparentemente não há problemas neste desenho, mas há uma reflexão, o domínio precisa da classe repositório?

O domínio precisa salvar no banco (O que deve ser feito). É desnecessário o conhecimento dos métodos, implementação e as demais propriedades da classe repositório (como deve ser feito).

Para resolver é necessário abstrair a definição do método “Salvar” do repositório em uma interface, ilustrado abaixo.

Na ilustração acima o serviço de repositório implementa a interface e o domínio usa uma instância da interface. O domínio continua utilizando o serviço de repositório. No entanto o domínio não sabe quem tem esse conhecimento. No exemplo foi utilizado uma classe chamada PedidoRepositorySql, mas que pode ser facilmente substituído por uma biblioteca Oracle ou MySql. Desde que mantenha a assinatura da interface.

Beneficios

O domínio tornou-se independente do PedidoRepositorySql. Portanto é menos provável que haja a necessidade de fazer alterações no domínio quando houver alterações no PedidoRepositorySql. No formato que foi concebido é improvável que uma modificação no domínio implique em implementações no Repository.

Outro benefício é que o código do domínio está isolado do servico de banco de dado, facilitando a criação de testes unitários. Na primeira abordagem para criar um teste seria necessário também configurar o banco de dados.

Da teoria à prática

Seguindo o cenário anterior, será criado uma classe de domínio que precisa enviar e-mail e salvar no banco.

O código será refatorado passo a passo. Até chegar na inversão de dependência.

1. Código espaguete


    public class PedidosService
    {
        public void AnalisarPedido(Pedido pedido)
        {
            if (pedido.Total > 10m)
            {
                pedido.Suspeito();
                this.EnviarEmailAlerta(pedido);
            }

            new PedidoRepositorySql().SalvarPedido(pedido);
        }

        private void EnviarEmailAlerta(Pedido pedido)
        {
            // Codigo para enviar e-mail
        }
    }

Pelo princípio do SOLID, o envio de e-mail deveria ser responsabilidade de outra classe. Respeitando também os padrões do DDD, o envio de e-mail e o Repositório deveriam estar em outra camada (projeto).

2. Aplicando a IoC.

Repare que no código abaixo o PedidoService desconhece como criar o repositório, desconhece como enviar e-mail, apenas sabe que deve fazê-lo, mas não sabe como e tampouco detalhes da implementação.


    public class PedidosService
    {
        private readonly IPedidoRepository _pedidoRepository;
        private readonly IEmailService _emailService;

        public PedidosService(
            IPedidoRepository pedidoRepository,
            IEmailService emailService)
        {
            _pedidoRepository = pedidoRepository;
            _emailService = emailService;
        }

        public void AnalisarPedido(Pedido pedido)
        {
            if (pedido.Total > 10m)
            {
                pedido.Suspeito();
                _emailService.EnviarEmailAlerta(pedido);
            }

            _pedidoRepository.SalvarPedido(pedido);
        }
    }

Para que funcione será necessário criar classes que implemente as interfaces.


    public interface IEmailService
    {
        void EnviarEmailAlerta(Pedido pedido);
    }

    public interface IPedidoRepository
    {
        void SalvarPedido(Pedido pedido);
    }

    public class PedidoRepositorySql : IPedidoRepository
    {
        public void SalvarPedido(Pedido pedido) { }
    }

    public  class  EmailGoogleService: IEmailService
    {
        public void EnviarEmailAlerta(Pedido pedido) { }
    }
    

As implementações dos códigos acima poderia estar em outra classe, em outro projeto ou até mesmo numa referência do nuget

Flexibilidade

Com este padrão, o PedidoService é independente, não conhece os detalhes dos serviços que utiliza. Torna-se flexível, abrindo o cenário para novas possibilidades, abaixo algumas delas.


    // repositorio Sql, email do Google
    var pedidoService = new PedidosService(new PedidoRepositorySql(), new EmailGoogleService());

    // repositorio oracle, email do Google 
    var pedidoService = new PedidosService(new PedidoRepositoryOracle(), new EmailGoogleService());

    // repositorio MySql, email do Google
    var pedidoService = new PedidosService(new PedidoRepositoryMySql(), new EmailGoogleService());

    // repositorio Mongo, e-mail chimp
    var pedidoService = new PedidosService(new PedidoRepositoryMongoDb(), new MaailChimpService());

Basta as classes implementarem a interface. E como um passe de mágica, um comportamento totalmente novo. Sem a necessidade de nenhuma alteração no serviço.

Conclusão

O artigo aborda o conceito de IoC.

IoC e DI andam lado a lado. São irmãos, quase, inseparáveis. Este artigo apresentou apenas o IoC. Em outro momento apresentarei o DI.

Espero que tenham gostado, comentem, deem sugestões. Vamos bater um papo!