Hanami em prod: 2 anos depois – Parte 1

Começamos os protótipos quando o projeto se chamava Lotus-rb. Lá em Outubro de 2015, a ideia era validar se quem-sabe-talvez, Lotus-rb pudesse ser uma alternativa real ao então nosso principal projeto em Ruby on Rails.

Le Prototype

Como todo mundo, fizemos um ToDo app para validar. Experimentamos o model e o validation, pois já tinhamos em mente separar o Input e sua validação do domain-model. Queríamos isolar ao máximo nosso modelo, muito nos moldes do Clean Architecture do Uncle Bob / Ports & Adapters do Alistair Cockburn. A ideia era muito experimental e confiávamos na robustez por baixo do Lotus-rb: Sequel. O Sequel, diferente do Lotus-rb, era estável e bem conhecido no ecosistema Ruby. Pensavamos: se o Lotus cair, ficamos com uma camada de abstração do Sequel e migramos isso depois facilmente para um Sequel-model da vida.

Meses depois, nosso projeto Core foi ao ar, substituindo partes do nosso agora legado Rails app. Já com o nome de Hanami e algumas atualizações feitas, tudo ia bem, para nossas necessidades da época. O projeto é uma API com interface JSON API, com foco em separação em camadas, Domain-Driven Design e arquitetura Hexagonal.

Le Presentation Layer

Ao longo deste processo, testamos outras gems como Roar (do Apotonick), Representable, JSONApi-Serializers e Grape. Como dá para ver, nossa Presentation Layer demorou a ficar do jeito que imaginávamos. É a vida. Ela é assim e boas. Fizemos uma versão básica da Presentation Layer e depois de tudo pronto e algumas utilizações, repensamos como poderíamos deixa-la de facto isolada.

Nossa principal questão foi como lidar com a transformação do Domain-Model para entregar algo “burro” ao nosso Presentation Layer. Outro ponto era o ponto inverso: como transformar o payload do Request em um objeto de transferência para ser encaminhado a Application Layer – a.k.a Use Cases. Idas e vindas, começamos com um modesto fluxo endpoint -> form object -> application service -> domain model até chegarmos em algo com responsabilidades melhor definidas:

# Presentation Layer
endpoint
  form object
    optional triggers: Validation & Relationships (JSON API specifics)

# Application Layer
  use case
    orchestrates to:
      domain model or # domain model
      infrastructure service # application boundaries

Antes de ir para production, tivemos que refazer nossa ideia de Form Object, pois o modelo inicial, não continha uma forma plugável para adicionar Input Validation (Hanami Validation implementation). Era muito manual e repetitivo. Natural neste ponto do processo, pois tivemos que distribuir nosso foco de atenção em toda a arquitetura inicial.

Com o novo modelo, o FormObject delega para a validação usando Template Method, caso o tal FormObject dê include na Validation que deseja utilizar naquele endpoint. Isso deixou muito transparte como validamos as coisas, pois no final, tinhamos uma delegação muito boa entre as classes FormObject e Validation.

Como spoiler, se liga um exemplo de FormObject:

# apps/web/form_object/auto/financing/applications/patch.rb

require_relative 'validations'
require_relative 'relationships'

module FormObject
  module Auto
    module Financing
      module Applications
        class Patch
          include Validations
          include Relationships

          def initialize(params, current_user, overrides = {})
            @raw_params = params
            @current_user = current_user
            @update_application = overrides.fetch(:application_use_case) do
              ::UseCase::Auto::Financing::Application::Update.new
            end
            @repository = overrides.fetch(:applications_repository) do
              ::Auto::Financing::ApplicationsRepository
            end
          end

          def process
            validate_using(@raw_params)
          end

          private

          def validation_succeed(validated_params)
            @update_application.update_application_form_for(
              @current_user,
              entity_with_relationships_updated,
              validated_params
            )
          end

          def entity_with_relationships_updated
            update_relationships(find_application(@raw_params['id']))
          end

          def find_application(id)
            @repository.find(id)
          end
        end
      end
    end
  end
end

# apps/web/form_object/auto/financing/applications/validations.rb
# Classe de Validation citada acima

require 'form_object/definitions/validatable'
require 'validations/auto/financing/application_validator'

module FormObject
  module Auto
    module Financing
      module Applications
        module Validations
          include Definitions::Validatable

          def validator_class(params)
            ::Validations::Auto::Financing::ApplicationValidator.new(params)
          end
        end
      end
    end
  end
end

# apps/web/validations/auto/financing/application_validator.rb
# Hanami Validation class

module Validations
  module Auto
    module Financing
      class ApplicationValidator
        include Hanami::Validations

        attribute :requester_full_name, type: String, size: 0..200
        attribute :requester_cpf, type: CoerceTypes::Cpf, size: 11
        attribute :status_flow, type: String
        attribute :purchase_value, type: CoerceTypes::Nullable::Money
        attribute :down_payment, type: CoerceTypes::Nullable::Money
        attribute :loan_amount, type: CoerceTypes::Nullable::Money
      end
    end
  end
end

Le Application Layer

Nosso próximo desafio foi mudar nossos Application Services. Trocamos o nome de Application Service para Use Case. Isso por si, já deixou objetivo o que era esperado destas classes bem como da Application Layer inteira.

Use Case bem descrito passou a ser prioridade em meados de maio/2016, quando percebemos que a Application Layer precisava sair de um mero detalhe, para assumir a responsabilidade de orquestrar como nossos casos de uso eram controlados. Nosso objetivo era simples: conseguir com um tree boundaries/use_cases entender o que o software é capaz de fazer. Todo aquele papo de sempre sobre nomenclatura de classes, etc., mas ir além e pensar nos contextos (modules/namespaces) que os Use Cases fazem parte.

Como todo o projeto, nossos Use Cases são stateless e recebem suas dependências através de injeção de dependência no initialize. Podem controlar db transactions e em alguns casos, disparar Domain Events, como sugerido pelo Vaugh Verner em seu livro Implementing Domain-Driven Design.

No próximo post, vou descrever como foi lidar com a evolução de nosso domain-model e como conseguimos lidar com isso ao longo destes dois anos.

Hanami vs o mundo

Destaque

Coloque-se na situação em que você precisa desenvolver um software corp enterprise em Ruby. Qual a primeira coisa que pipoca em sua mente neste momento?

Pois é:

Domain-Driven Design

A hype (olá pessoal de FP!) ideia do Domain-Driven Design (DDD pros chegados), é trazer o contexto de negócio juntinho do camarada que faz o software funcionar. Por que isso é bom? O principal argumento é: entendimento.

Poderia resumir todo o blue book (Domain-Driven Design: Tackling Complexity in the Heart of Software – Eric Evans) com a frase acima. Obviamente que seus problemas começam juntamente com seu objetivo de deixar o conhecimento alinhado entre todos envolvidos na solução. É exatamente por isso que o livro é extenso e detalhado, mas isso é assunto pra outro papo.

Qual a outra coisa que você pensa quando se fala enterprise ? Aha!

Patterns of Enterprise Application Architecture

O livro vermelhão do (Martin) Fowler é sem dúvidas um ponto de partida para falarmos daquele papo maroto de Arquitetura. Foi lindo! Todo mundo queria ser Arquiteto de Software, lembra disso? Velhos tempos hahaha!

Passada essa fase, começamos todos a pensar: o que é arquitetura de software? Com isso, veio aquela conversa de Arquitetura Evolucionária, ideia que prega que a arquitetura do seu software deveria emergir de acordo com o passar do tempo e das necessidades do software em questão.

De certa forma isso deveria ter sido falado lá atrás, no começo do epicentro do Domain-Driven Design e da importância por uma Arquitetura de Software definida, por mais tosca que parecesse ser.

Motivo simples: muito bolovo teria sido evitado e o nível de frustação e demissão regulados.

Arquitetura de Software

Enfim, a tal da Arquitetura tem sua utilidade, apesar de eu ver poucas pessoas definindo legal o que significa isso.

Podemos definir arquitetura como uma camada do seu software que vista pela outra camada é percebida como um blackbox, tendo pontos de comunicação externos definidos e padronizados.

Outra definição válida é de que arquitetura é todo design de software que agrupado representa ambos requisitos funcionais e não funcionais de uma parte do software. É assim que Clements P, et al define arquitetura de software:

As Clements P, et al. point out, all architectures are designs but not all designs are architectures. Designs that need to be bound in order for the system to meet its functional and nonfunctional requirements and goals are architectural in nature.

@IBM – Developer Works: Documenting Software Architecture Part 1

Dito isso, poderíamos entrar na Deep Web dos tipos de arquitetura de software, como Layered Architecture, Ports & Adapters, Clean Architecture, Hexagonal Architecture, CQRS.. opa, esse não!

Many people have been getting confused over what CQRS is. They look at CQRS as being an architecture; it is not.

O próprio senhor Greg Young disse que esse papo de que CQRS é arquitetura é infudado. E ele fala mais:

CQRS is a very simple pattern that enables many opportunities for architecture that may otherwise not exist. CQRS is not eventual consistency, it is not eventing, it is not messaging, it is not having separated models for reading and writing, nor is it using event sourcing.

Olé!

e o Hanami?

hanami-logo

O que um framework web Ruby tem a ver com tudo isso?

Tudo, obviamente!

A ideia geral do Hanami é ser desacoplado. Independente. Rebeldia? Jamais! O papo é que o framework por definição é feito para colaborar e não definir sua arquitetura no pior estilo One Size fits All Architecture.

Só por não definir uma arquitetura ele já possibilita ao desenvolvedor maluco definir sua própria arquitetura. E mais: evolui-la de acordo com suas próprias necessidades. Epa! Isso traz a tona o que eu falei lá em cima sobre Arquitetura Evolucionária.

weMor1k.gif

Legal, né?

Ah, e se eu não quiser definir nada? Quero seguir como uma boa ovelha que sou!? Neste caso o Hanami, mesmo não definindo uma arquitetura, sugere um Hexagonal Architecture out of the box. Sem pensar, você terá camadas bem definidas, isolando seu modelo dos seus adapters de apresentação e integração. Boom!

Talk: Lidando com defeitos em Test-Driven Development

Em Setembro/2014, participei do evento #S7TDD (7 dias com Test-Driven Development). O evento foi muito proveitoso com excelentes reviews dos participantes.

Falei em algumas oportunidades, uma delas abordei como o Test-Driven Development poderia auxiliar no gerenciamento de “guerra”, ou seja, quando as coisas “dão ruim”. Abaixo, o vídeo completo da minha apresentação:

Lidando com defeitos em Test-Driven Development from Hélio Costa e Silva on Vimeo.

Foi uma experiência e tanto participar de um evento online como o #S7TDD. Quem sabe em breve eu não aparece em outro 😉

Planejando no Macro para entender o micro

Finalizei o último post deixando um suspense no ar. Se não viu, recomendo que leia! Pois bem, quando falei sobre planejar no macro e codificar no micro, deixava ali uma ideia útil de fato, muito bem abordada, que quase todo developer já ouviu pelo menos falar o nome e que porém, já foi muito mal utilizada.

A UML, pode ser um aliado forte no seu planejamento macro de uma funcionalidade do software. Calma! Nada de fechar a aba e ir me xingar no twitter. Estou afirmando para você que é desenvolvedor e cria seus próprios testes a esboçar numa visão macro o que planeja fazer e com a UML, você obtém esse rascunho rapidamente.

Porque a UML e não (coloque aqui seu outro método) ?

UML é uma linguagem de modelagem de diagramas e sua simplicidade (no uso simples dela) a torna uma vantagem no esboço de rascunhos macro de seu software. Outro ponto a favor da UML é o shareable que ela permite: mande-me um esboço UML que eu entenderei o que você quer me falar.

Esboço!

Não estou falando para chamar o arquiteto de software para ele criar todo o software em UML e mandar você fazer. Vou repetir: crie você seus esboços para ajudar no seu próprio entendimento do que pretende e como fará os relacionamentos. Geralmente, eu faço micro esboços que resolve uma e somente uma funcionalidade. Após criá-la, consigo ver se fará sentido aquela minha ideia do ponto de vista arquitetônico e daí então, crio meu teste de unidade e sigo adiante.

  • Eu não faço uma funcionalidade e depois ligo com outra que liga com outra;
  • Não guardo estes rascunhos depois que acreditei na solução macro e segui adiante com a micro (Teste de Unidade) ajustando meu design de código, e;
  • Não faço rascunho macro em UML para toda e qualquer nova feature. Apenas naquelas onde as fronteiras da classe alvo do teste estão meio obscuras para mim.

Isto quer dizer que se Order se relaciona com alguns atores (outras entidades/classes puras) e este relacionamento está estranho para mim, eu tenho subir o nível de abstração, saindo do micro e indo para o macro, buscando entender como dar-se-á o futuro relacionamento ou/e se ele faz mesmo sentido naquele contexto.

Tipos de macro-esboço

Geralmente eu apelo para o Diagrama de Objetos por ser algo simples: coloca dentro do retângulo o nome da classe e pronto. Onde ele por ser útil:
a) quando você está procurando quais as fronteiras seu objeto terá; (lembre-se de que todo e qualquer objeto deve ser planejado como se o mesmo fosse uma API provendo serviço através de métodos públicos a outros objetos do mesmo sistema – por isso uso muito o termo fronteira [boundary]).
b) quando você precisa entender se o objeto deverá ser Aggregate Root de algum outro via composição.
c) traçar fluxos possíveis dentro de um conjunto de objetos.

Quando a coisa aperta e eu fico desconfiado das responsabilidades de cada método em objeto(s) em uma feature, eu recorro ao Diagrama de Sequência. Ele torna-se muito útil nos cenários macro-micro (nome horrível), onde estou planejando o macro, mas entrando um pouco no detalhe da responsabilidade (SRP, alguém?) da feature. Ele te ajuda a lembrar sempre da Lei de Demeter com seu Tell Don’t Ask.

Já acabei utilizando raramente o Diagrama de Classe, onde rascunhava um pouco sobre os métodos de uma classe, mas no meu uso ele acabava não agregando na visão macro, por especular a visão micro sem conhecimento de causa.

Porque não usar a UML como documentação de software?

Quer tentar? Vá em frente (e quebre a cara). Eu e alguns amigos já tentamos manter um Diagrama Caso de Uso em um software anos atrás e sinceramente? Totalmente inútil. Precisava de atualização a todo instante; Ficando desatualizado tornava-se um peso a mais e não ajudava no design pois era um punhado de diagrama e não código executável que te prova que sua teoria no código de produção funciona ou não. Imagina você tendo que atualizar todo diagrama de sequência assim que resolve mudar o input ou output de um método. This is madness! – além de ser esforço inútil pelos motivos já citados (não exercita o código de produção).

Concluindo

Diagramas UML, rabiscos, círculos interligados, etc., ajudam você a planejar sua visão macro quando sente-se desconfortável com certa integração entre objetos. São valiosas ferramentas para esboçar sua ideia e reorganizar sua mente antes de seguir para seu próximo Teste de Unidade, que diferente do primeiro, exercita código de produção como ninguém.

Como manter sua suite de testes saudável

taporra

Depois de uma longa pausa por causa da demanda que o #S7TDD me gerou, retorno das sombras para continuar com as polêmicas do Test-first. Hoje, retomo com algo primordial, principal, essencial, o must have em Test-first: nada menos do que ele, a manutenção da suite de testes.

O que é a manutenção da suite?

Partindo do princípio de que sua suite está em constante crescimento, você uma hora ou outra irá se perguntar em como organizar a casa para evitar se perder e não se enrolar com seus testes de unidade.

Para tanto, a manutenção da suite é importante e, quanto mais você praticar Test-first, mais simples e eventual torna-se-á tal manutenção.

E para que preciso saber/fazer isso?

Como nem todo mundo está no nível de Robert Martin (@unclebob), se reorganizar torna-se importante para continuar vendo resultado no Test-First e principalmente, continuar entendendo como o software funciona só de olhar um punhado pequeno de testes de unidade.

Perder o controle da suite é relativamente fácil principalmente quando estamos começando com testes de unidade, pois como muitos questionam: “mas e as junções? você não une as coisas nunca?”. A vantagem do Teste de Unidade mora justamente no cerne apontado nesta frase, mas como tudo tem um viés, você precisa estar atento justamente com a manutenção deste monte de teste de unidade espalhados pelo seu /specs.

Sejamos pragmáticos aqui: sua suite está precisando de uma reorganizada quando você lê um teste e não entende o que aquilo está fazendo dentro de um contexto macro. Deixando a explicação fancy de lado, temos:

  1. Pegue um teste de unidade aleatoriamente;
  2. Leia e veja o que ele implementa (funcionabilidade);
  3. Tente reproduzir aquela situação em um Controller, Command, etc, ou seja, suponha que você viu o teste e deseja criar a funcionalidade numa Action de Controller qualquer;
  4. Ache uma outra funcionalidade que se relacione com essa primeira que você reproduziu;
  5. Leia o teste dela, tente reproduzi-la ligando a primeira funcionalidade na segunda.

Conseguiu entender ambos os testes e relacionar as funcionalidades? Parabéns, sua suite está saudável (por enquanto haha).

Restabelecendo a ordem na casa (dos testes)

A primeira coisa a se fazer caso os steps acima não tenham dado resultado é verificar a anatomia dos testes de unidade. Às vezes, você acumulou duas ou três funcionalidades em um mesmo teste de unidade sem perceber. Ao splitar estes testes em testes específicos para cada situação, a legibilidade irá saltar de um espaguete para algo compreensível. Costumo dizer que esta é a principal dica para quem quer arrumar a legibilidade dos testes.

Supondo que o problema da anatomia está resolvido, podemos melhorar a legibilidade seguindo o Ubiquitous Language descrita por Eric Evans em seu renomado livro Domain-Driven Design cujo já o mencionei aqui algumas vezes no passado. Dar nomes é algo complicado (veja pelas ruas das cidades). O nome é a coisa mais importante do seu código. Isto é o elo entre o código e a vida real, solicitada e falada o tempo todo por alguém de business. Quanto mais você incorporar os nomes que ouve no código, mais natural ficará as reuniões de planejamento/sprint.

Nosso item número três do check-list é o Princípio da Responsabilidade Única (SRP, sigla em inglês). Lê isso: seu método em teste precisa fazer apenas uma coisa. Ele pode entretanto, se relacionar com outros objeto#metódo para complementar sua atividade, pedindo uma espécie de suporte. Pode também delegar atividades para outros objeto#método. Pode inclusive, se passar como parâmetro para outros objeto#método.
Veja a quantidade de recursos em Design Orientado a Objetos que você tem a disposição para não violar a SRP e você aí fazendo 3, 4, 5 steps em um único método sem a menor necessidade. Vai entender, né?

Sério. SRP é TOP 3 não é por acaso. Ela é aqui na minha lista a primeira que trata de código e OOD. As outras duas são mais arquiteturais e convenções do que código na prática.

Acredito que estas três dicas farão o trabalho de reduzir o caos que sua suite possa estar enfrentando enquanto lê isso. Um chute nada estatístico seria 80~90% de problemas resolvidos.

Como evitar on the fly que isso aconteça?

Planeje no macro; Codifique no micro. Não entendeu? Segunda-feira que vem você entenderá o que isso quer dizer.

Destaque

Existe vida após o primeiro teste passar?

listen-to-tests

Conseguiu fazer seu primeiro teste passar. Está orgulhoso de seu novo achievement porém sabe que esse primeiro passo é apenas o início para conseguir mensurar uma evolução no seu processo de desenvolvimento usando Test-first como referência de design de código. Agora, tudo que você precisa é continuar neste ciclo.

O ciclo é importante para evitar se atrapalhar e perder o ritmo neste começo. Fato! Mas, ainda mais importante é ter uma ideia clara do que pretende fazer a seguir.

Saindo um pouco de Test-first, uma coisa legal que você pode fazer é ter uma ToDo list. São aquelas coisas simples onde você organiza suas tarefas. Vai de Remember The Milk até um Trello ou Pivotal Tracker . Obviamente cada um deles tem seus pontos de ataque, mas tangenciei o tema justamente para te alertar que uma mente organizada, ajuda e muito você manter um ritmo. Minha técnica é bem simples: ao final do dia, eu vejo meu progresso no software e estimo um novo ponto de parada para o próximo dia. Quando o dia chega, eu tenho em mente que meu objetivo do dia é “Conseguir enviar um Pedido com sucesso”, por exemplo.

Com a organização em dia, você sabe o que atacar agora. Assim sendo, podemos voltar ao Test-first exclusivamente.

Vida após o primeiro teste com Test-first

Qual a próxima feature importante do seu negócio? Veja, entenda como feature requisitos funcionais e não funcionais, ou seja, pode ser algo não necessariamente explicito pelo chefe/cliente, mas algo que você percebeu fazendo seu teste de unidade com Test-Driven Development. Acredite: isso acontece e muito!

Criar cenários sem final feliz é igualmente importante ao cenário feliz. Se você espera que seu código some dois números, o que acontece se você mandar dois null para ele somar? Ele deveria fazer algo? Se é importante que ele seja resiliente e lide com este tipo de coisa, você deve criar um teste de unidade para este cenário também!

É perfeitamente aceitável criar um teste de unidade que dado dois null ele deverá retornar uma exceção (Exception). O anormal, seria você criar um teste de unidade que não espera que uma exceção seja lançada. Neste caso, você deve esperar um valor de retorno ou que um mock seja chamado.

Muito interessante criar testes que exercitam diferentes cenários e configurações de seus inputs. Por exemplo, numa inscrição, você pode simular o comportamento do método que inscreve pessoas, utilizando pessoas cadastradas, pessoas elegíveis à inscrição e pessoas não elegíveis a inscrição. Desta forma, você teria testes assim:

it “fará a inscrição dado um usuário elegível”

it “não fará a inscrição ao receber um usuário expirado”

it “não fará a inscrição ao receber um usuário inelegível”

Na maioria dos casos, você pode perfeitamente possuir mais de um caso de teste para um mesmo requisito funcional que você pegou para fazer.

O código mudará

Acostume-se. Ele mudará! No seu primeiro teste você não tem nada no seu domínio. Após o primeiro teste, novas coisas surgirão. No segundo teste, você acabará mudando código de produção que afeta o primeiro teste. Isto é ruim? Muito pelo contrário: isto é excelente. Significa que você está literalmente Guided by Tests, pois está criando o código necessário para seu teste passar. Está com isso deixando o Test-first guiar seu design de código.

Problemas irão ocorrer. Em problema, o primeiro passo é não seguir adiante. Mantenha-se aonde está, exatamente no teste que você está com dificuldades. Tente entender o que o teste está lhe dizendo. No mês de agosto inteiro, dediquei os posts a tratar os problemas mais comuns ao tentar seguir adiante com TDD. Leia o Prelúdio da série “Rua sem saída” e veja se eu abordei sua dúvida. Isto te ajudará a voltar aos trilhos (Rails ;P) com Test-first.

Concluindo

Organização mental; Seguir passos palpáveis que dão retorno rápido em modificações; Lembrar-se do Red, Green, Refactor; Efetivamente fazer o teste antes do código de produção, pois o teste te auxilia a guiar o design do código; saber “ouvir” o teste; e, o mais importante: não ter medo de mudar seu código de produção devido a um novo teste de unidade são a chaves necessárias para abrir qualquer caminho travado.