Já se passaram seis meses desde que comecei a trabalhar em uma empresa construindo um novo aplicativo corporativo. Minha nova equipe segue algumas práticas ágeis, como programação em par e Desenvolvimento Orientado a Testes (TDD), e tenho a sincera sensação de que o sol está brilhando para nós!
Bem, quase.
Há alguns problemas que tenho enfrentado agora e em experiências industriais anteriores sobre o tema da criação de aplicativos corporativos sólidos que gostaria de explicar a você neste artigo.
Além disso, também proporei uma metodologia simples baseada em testes iniciais para criar aplicativos corporativos que melhorem a comunicação da equipe e promovam entregas de código de alta qualidade mais rápidas.
Sem mais delongas, vamos ao que interessa!
As práticas ágeis são altamente benéficas para a prototipagem rápida de software. O TDD está no centro de tais práticas, fornecendo ao software uma propriedade primordial: robustez. Escrever testes antecipadamente força os desenvolvedores a pensar sobre o comportamento esperado e excepcional dos componentes de software que eles constroem, aprimorando a qualidade do código e garantindo o cumprimento dos requisitos funcionais.
Além disso, o TDD é uma prática poderosa que permite que os desenvolvedores não tenham medo de corrigir, limpar e adaptar seu código às atualizações de requisitos funcionais . E isso é ótimo. No entanto, não é tão fácil trazer o TDD para a prática.
No início da construção de qualquer nova aplicação corporativa, nós (desenvolvedores) reunimos diversos requisitos funcionais. Em seguida, derivamos um conjunto de casos de uso que satisfará tais requisitos funcionais. Depois disso, desenvolvemos componentes de software situados em diferentes camadas, da mais alta à mais baixa, indo até o coração do aplicativo, ou seja, seu modelo de domínio. Essa é a definição do processo de desenvolvimento de software de cima para baixo .
No entanto, o processo de desenvolvimento de software de baixo para cima se encaixa melhor com o TDD. Comparado com a alternativa top-down, bottom-up é uma abordagem mais pragmática, pois permite que nós, desenvolvedores, cubramos gradualmente todos os níveis de indireção começando com o mais básico (ou seja, o modelo de domínio) e movendo gradualmente para camadas mais altas de abstração. Permite a produção de bases de código de aplicação mais sólidas, o que nos faz ganhar muita confiança em nosso trabalho. Para aplicar o TDD em uma abordagem de cima para baixo, no entanto, deve-se primeiro escrever testes para componentes de software localizados nas camadas superiores. Dessa forma, os desenvolvedores não precisam zombar de nenhuma dependência de nenhum componente da camada inferior, pois eles simplesmente ainda não existem.
A necessidade de criar tais dependências já é um problema porque zombar de componentes da camada inferior nem sempre é possível ou, na melhor das hipóteses, parece contra-intuitivo, por exemplo, imagine-se tendo que zombar da lógica de um objeto de domínio para produzir um teste de componente de serviço.
Além disso, pessoalmente duvido que isso traria algum valor, pois acho que a validação de componentes intermediários deve sempre exercer todas as dependências, exceto aquelas para serviços externos (por exemplo, um banco de dados), que podem ser ridicularizadas. Mais importante, devido à complexidade inerente para realizar requisitos funcionais não triviais como código, não se entende completamente as implicações técnicas que alguns requisitos funcionais têm no modelo de domínio até que se comece a raciocinar sobre eles durante a implementação do modelo de domínio.
Mais uma vez, começar a escrever testes de componentes de software intermediários não traz muito valor, já que muitos desses testes (se não todos) provavelmente serão jogados na lata de lixo assim que os artefatos de software da camada inferior forem realmente implementados.
Além disso, tenho visto desenvolvedores de software (especialmente colegas de equipe júnior) desistindo e implementando alguma prova de conceito para o caso de uso em questão sem escrever nenhuma lógica de validação. Isso está usando a prática do primeiro código, que anula o propósito do TDD. Além disso, sem seguir as práticas adequadas de Entrega Contínua, há um alto risco de acabar enviando código não validado para nosso repositório de controle de versão.
Então, como poderíamos aplicar o TDD de forma eficaz na produção de aplicativos corporativos, dado um conjunto de requisitos funcionais?
Há muita literatura sobre Arquitetura Hexagonal na Internet. Eu recomendaria especialmente a leitura do white paper sobre o assunto escrito por Alistair Cockburn.
Para o propósito do presente artigo, permita-me contar uma pequena história da vida real com o objetivo de explicar brevemente a motivação e os principais benefícios da Arquitetura Hexagonal: Nos muitos anos em que desenvolvo aplicativos corporativos, tenho visto muitas pessoas ( inclusive eu) iniciando novos projetos com foco em outros temas e não em nossa verdadeira missão. Tal missão consiste em agregar valor real às empresas para as quais trabalhamos. Esse valor está na lógica de domínio de nossos aplicativos.
Parafraseando Uncle Bob em seu livro Clean Architecture, todo o resto é uma distração, um detalhe de implementação que pode (e deve) ser adiado, idealmente para o final do desenvolvimento. Exemplos de detalhes de implementação são tecnologias de banco de dados, lógica de controlador ou tecnologia de front-end. Mesmo a estrutura de back-end é um detalhe de implementação que poderíamos escolher mais tarde no processo de desenvolvimento, se realmente quiséssemos. A arquitetura hexagonal, também chamada de portas e adaptadores , é um padrão de arquitetura destinado a desacoplar a lógica principal do aplicativo de software dos detalhes externos da implementação.
Nós, desenvolvedores, devemos nos concentrar na lógica central dos aplicativos corporativos e adiar a implementação da lógica necessária para se comunicar com serviços externos. Para atingir esse objetivo, tudo o que realmente precisamos fazer é escrever algumas interfaces (as chamadas portas ) e simular os componentes (os chamados adaptadores ) que realmente se comunicam com os serviços externos. Assim, a implementação do adaptador pode ocorrer posteriormente no processo de desenvolvimento. E quanto mais tarde eles vierem, melhor, já que todos os insights que obtemos enquanto produzimos a lógica central são realmente úteis durante a tomada de decisão sobre quais tecnologias escolher.
A imagem a seguir é uma das muitas ilustrações existentes da Arquitetura Hexagonal:
Considere os elementos que compõem o hexágono interno. Além do modelo de domínio, há também uma camada de serviços de aplicativos . Esses componentes de software não especificam nenhuma lógica de domínio. Em vez disso, eles são simples coordenadores de adaptador e lógica de modelo de domínio. Um serviço de aplicativo realiza um caso de uso que cuida de um subconjunto de requisitos funcionais para o aplicativo corporativo. Este é um dado importante a ter em mente para o que vem a seguir.
Como afirmei anteriormente, a aplicação do TDD é mais fácil ao seguir o processo de desenvolvimento de software de baixo para cima. No entanto, muitos de nós acham mais fácil raciocinar sobre um projeto de sistema seguindo a abordagem de cima para baixo. E embora pareça que estamos incorrendo em um conflito de interesses, tudo bem porque podemos começar a projetar (ou seja, esboçar em pseudocódigo ou algum diagrama UML) nossos serviços de aplicativo de cima para baixo sem escrever uma única linha de código para eles; não até concluirmos a implementação do modelo de domínio.
Antes de começar a codificar, podemos interpretar os serviços de aplicativos como algumas diretrizes de design de software para executar fatias verticais dos aplicativos corporativos que devemos construir. Cada fatia vertical representa todo o caminho de execução de um caso de uso, desde a ação executada por um serviço externo upstream ou um usuário em uma interface do usuário até qualquer operação executada em um serviço externo downstream. Quando terminamos o design de um serviço de aplicativo, identificamos quais adaptadores e componentes de domínio precisamos implementar. Agora que temos visibilidade do modelo de domínio, podemos implementar seus componentes fundamentais aplicando o TDD.
Em seguida, podemos implementar o serviço de aplicativo seguindo a abordagem test-first, criando uma porta para qualquer adaptador de serviço externo e simulando sua implementação real. Quando terminamos a implementação do serviço de aplicativo e do modelo de domínio relacionado, podemos verificar se essa implementação é válida, ou seja, livre de bugs e compatível com seus requisitos funcionais. Por fim, podemos implementar a lógica do adaptador, aplicando também o TDD.
Essa metodologia permite a implementação rápida e gradual de aplicativos corporativos, permitindo que os desenvolvedores ganhem confiança na validade de todos os componentes que criam sem jogar nenhum teste fora. Além disso, não impõe nenhuma restrição às atualizações de requisitos funcionais.
O diagrama de fluxo a seguir descreve a metodologia para escrever a lógica de um caso de uso. Observe que não falo sobre tipos de teste específicos, pois esse é um assunto bastante controverso, embora eu recomende seguir as convenções e a terminologia usadas no artigo The Practical Test Pyramid .
A metodologia proposta simplifica a distribuição de tarefas da equipe, independentemente de trabalharem sozinhos ou em duplas. Algumas de suas etapas podem ser feitas em paralelo, por exemplo, um colega de equipe/par pode construir a lógica do núcleo zombando de qualquer referência a uma dependência externa, enquanto outro pode estar trabalhando no desenvolvimento do código de integração com essa dependência externa, pois essas duas partes são desacoplados, encurtando assim o tempo de entrega. Tudo o que eles precisam fazer é transmitir alguma API para a dependência externa realizada como uma porta. Tanto a simulação quanto a dependência externa real precisam implementar a interface de porta mencionada anteriormente. Da mesma forma, outra equipe/colega de equipe/par pode implementar a parte de front-end para o caso de uso, se isso for exigido pelo aplicativo corporativo.
Por outro lado, devido à sua natureza estruturada, é fácil comunicar o estado de desenvolvimento de um caso de uso concreto entre pares. Ao entregar o desenvolvimento de um caso de uso, a entidade que sai pode simplesmente apontar a nova para o serviço de aplicativo atual que estava projetando ou implementando. A nova entidade pode simplesmente rastrear qualquer coisa que já tenha sido criada no adaptador ou na lógica de domínio na base de código.
Uma observação final sobre detalhes de implementação: poderíamos atualizar nosso modelo de domínio para especificar alguns desses detalhes escrevendo anotações/decoradores da tecnologia de banco de dados e inserindo recursos de validação de dados da estrutura subjacente que melhor se ajusta à natureza de nosso aplicativo. No entanto, eu desaconselho isso, pois isso seria vazar detalhes de implementação em nosso modelo de domínio, o que não é a melhor prática, pois os detalhes de implementação e o modelo de domínio tendem a mudar por motivos e frequência diferentes. Por outro lado, conforme explicado por Vaughn Vernon em seu livro Implementing Domain Driven Design (DDD), a Arquitetura Hexagonal está intimamente relacionada ao DDD. No entanto, você não precisa seguir o conjunto de práticas e padrões de DDD para criar aplicativos corporativos complexos baseados na arquitetura hexagonal, embora eu recomende fortemente que você o faça. Mas essas decisões, afinal, dependem inteiramente de você.
Test-Driven Development é uma prática poderosa na construção de aplicativos corporativos robustos. O TDD é melhor aplicado seguindo o processo de desenvolvimento de software de baixo para cima. No entanto, como os desenvolvedores estão acostumados a raciocinar sobre esses aplicativos seguindo uma abordagem de cima para baixo, aplicá-la na prática é um desafio. Este artigo expõe uma metodologia simples para ajudar os desenvolvedores a aplicar o TDD de forma eficaz no desenvolvimento de aplicativos corporativos.