Hanami em production: 2 anos depois – Parte 2

Continuando a minissérie comemorativa de 2 anos do Hanami em production, vou detalhar como foi nosso processo com o Domain-Model. Antes, falei sobre Entrypoint, Presentation e Application Layers, mostrando até um pouco do código fonte.

O simples Hanami Model

O framework, no auge da sua versão beta (< v0.7), definia o mínimo imaginável para algo funcional e estável, apesar da versão. Tínhamos um mapeamento (entity <-> db table), abstração do repositório que suportava até PORO (Plain Old Ruby Objects) como entity passível de mapeamento.

Em miúdos, era possível definir uma Entity com seu Repository mapeando em uma tabela do database rapidamente. Por definição, a Entity é mutável e pode/deve conter transformações de dados as quais sejam essa Entity responsável. Foi com estas features que decidimos tentar nossa POC para a nova plataforma (Core Application).

O Domain-model

Já na Domain Model Layer necessitávamos de definições/padrões além do suporte do framework. Precisávamos de delegação do modelo para camada de infraestrutura com elementos externos (Queues, External Applications, etc), transformação de dados de negócio, declaração de conceitos de negócio, separando-os em módulos, como por exemplo: Pré Qualificação do Cliente, Descarte de Lead, Atendimento, Análise de Crédito, etc.

Nossa primeira ideia foi: vamos manter tudo isso dentro de /domain que nós saberemos modelar corretamente caso a caso e dar os devidos tratamentos ou encapsulamentos necessários.

Passados três meses, lá estávamos, no epicentro do:

Domain Model Caos

Um princípio de caos começou a se formar dentro do Domain-Model, pois classes de transformação de dados, adaptadores para dependências externas e até as próprias dependências externas começaram a vazar para dentro das classes do domínio sem que tivéssemos sequer percebido. Como o time estava, na época, em processo de onboarding nesta nova plataforma, as coisas ficaram um tanto conturbadas. Precisávamos entender o que faltava.

Fronteiras

Nossa primeira medida foi definir uma fronteira real entre Domain e Infrastructure Layers. Entendemos que deixar explícito seria a forma mais simples para contornar nosso problema e seguir adiante.

Foi então que o /boundaries/infra/ surgiu. Neste ponto, havíamos a seguinte estrutura:

/core-application
  apps/
    web/
    api/
    representation/
    form_object/
    validations/
  domain/
    auto/
    refinancing/
  boundaries/
    infra/
    use_cases/

Desta forma, com a separação explícita ficou mais fácil usar isso como padrão e notificar todos os squads, propagando o conhecimento facilmente entre os times e futuros desenvolvedores.

O renomado lib

O Hanami por default utiliza o diretório /lib como dir-home para seu domain model. Optamos por burlar essa definição por achar domain mais legível. Outro motivo foi pelas questões históricas, onde o lib era um diretório famoso por servir como depósito de classes em outros projetos. Essa definição é facilmente configurável no próprio framework, sem a necessidade de lutar contra o Hanami.

Monolith vs modules

Nossa aplicação principal nasceu como um monolith application. Não entramos na onda dos microservices e preferimos ao invés, definir nossos contextos de domínio dentro do Domain Model, isolando cada “módulo” do sistema, buscando deixá-los autônomos e auto explicativos.

Para fazer a magia, nossa versão inicial usou o Façade Pattern em cada um dos módulos, criando uma espécie de API, protegendo o módulo de super exposição de suas classes internas em outros módulos, o que nos traria uma complexidade elevada.

A solução vem funcionando desde sua implementação no começo de Novembro/2015, porém algum acoplamento acabou passando, nos fazendo novamente parar para entender os motivos. Em nossa análise, entendemos que na verdade, em alguns destes pontos de acoplamento, tentamos de alguma forma “notificar” outro módulo quando alguma coisa importante ocorresse em nosso próprio. Isso por si, nos fez partir para outra modificação arquitetural dentro de nosso domínio.

Domain Events

Já em 2017, começamos um movimento para fazer os contextos (a.k.a modules) emitirem eventos de domínio quando coisas relevantes acontecerem. Assim, outros módulos, interessados no evento, assina para recebe-lo.

Começamos com uma POC entre 2 dos nossos Squads do produto de Auto Refinancing que precisavam notificar e receber notificações de outros contextos (Bounded Contexts). Fizemos uma versão inicial em uma semana e após mais alguns dias de experimentos, colocamos em produção. Rapidamente a demanda por mais eventos emergiu, levando-nos a implementar uma infraestrutura mais robusta.

Futuro

Nossa ideia agora é fortalecer nossos Contextos com mais eventos permitindo que os squads consigam decidir por sair do Monolith Application para sua própria infra e seguir suas próprias demandas. Com o aumento do time, manter tudo em um único Core Application acaba trazendo alguns problemas dos quais microservices trazem como benefício.

Concluindo

Saímos de um Core Application simples com um time de 10 pessoas há 2 anos atrás e conseguimos escalar até quase 35 desenvolvedores em alguns squads usando uma versão beta do Hanami Ruby. Foi um longo caminho de aprendizado e evolução. Todo o time de tecnologia conseguiu manter um software de domínio complexo de uma forma evolucionária até o presente momento.

Desenvolvedor de software há um tempinho. Interessou-se por Object-Oriented Design e Test-first antes do Eddy Merckx ser removido na organização da UCI Road World Championships. Escreve em seu blog sobre software e gatos sempre que possível.