As vezes sou questionado sobre meus desenhos de arquitetura e porque criar tantas abstrações, tanta configuração e tantas dependências e frameworks de terceiros, alguns que só eu e um tibetano conhecemos. No post Como definir Arquitetura de Software, cito quais são os pensamentos primários necessários para se desenhar um arquitetura, mas vamos aplicar isso a um contexto real para exemplificar.

Tudo começa com minha chegada na atual empresa, onde encontrei um cenário extremamente complexo e difícil de se trabalhar.

  • Umas 8 bases de dados, entre SQL Server e MySQL, cada uma segundo dois ou mais padrões antagônicos (sim, na mesma base), diversos padrões nomenclatura. As bases não possuíam um responsável. Eram “da empresa”, não havia um dono.  O acoplamento do modelo de dados era entre database/tecnologia. Isso quer dizer que um dado em uma base SQL só faz sentido e vira informação em conjunto com dados que estavam em outras bases mysql, por exemplo.
  • Alto acoplamento entre as diversas aplicações e suas bases de dados.
  • Um parque de aplicações baseadas em .net framework 2.0, usando ADO.NET puro para escrever o acesso a dados: Manualmente. Uma quantidade infinita de código de acesso a dados realizando N+1 Anti-Pattern por causa da forma como o acesso a dados era escrito.
  • Dezenas de micro aplicações, forks de uma aplicação inicial que realizava um processo batch de importação de conteúdo, extremamente importantes para o business.
  • Gerência de configuração inexistente, havia um SVN, mas mal era usado para versionador de código.
  • Nenhuma gestão de releases.
  • Centenas de micro-aplicações responsáveis por micro-tarefas, sem documentação, sem identificação e algumas sem sequer os fontes. Sem falar que muitos dos fontes sequer eram versionados ou estavam defasados no repositório de código.
  • Todo o conhecimento tecnológico dependia exclusivamente da memória do time ou daqueles que passaram pela equipe e ainda estavam na empresa.

Dá para ver que há muito a se trabalhar nesse lugar. Né?! Ok, assim começamos.

Primeiro passo foi definir um fluxo geral de gerência de configuração: A partir de então 100% do que fazíamos era versionado. 100% do que conhecemos também precisa ser. Agora com algum alívio, por não ter mais medo de que HD’s queimados me tirassem o sono, poderíamos partir para a identificação e melhoria da vida das pessoas.

  1. Identificação do cenário atual

Um dos processos, que considero dos mais importantes da empresa é a ingestão de conteúdo, por ele recebíamos milhares de XML’s e mídias(WAV, AVI) todos os dias, são diversos formatos e padrões de integração, o que nos demandava uma quantidade razoável de storage, alguns petabytes. Esse processo era composto por diversas micro-aplicações, onde cada uma era um fork de um projeto base, e nela tínhamos um desenvolvedor gerindo esse código, uma loucura para uma pessoa só. Óbvio que daria merda em algum momento. Fiz o trabalho de entender o cenário de negócio e compreender quais são os gaps do processo atual. Entre eles estão:

  • Alta segmentação das versões da base de código, cada uma com ligeiras mudanças para atender ligeiras diferenças entre parceiros.
  • Alta complexidade em cada uma das aplicações que possuíam formatos de XML diferentes.
  • Baixa completude no processamento do conteúdo recebido (boa parte das aplicações, ou precisava de tapas na hora de processar, ou só processava parte do XML que recebíamos)
  • Alta complexidade na gestão do processo
  • Falta de visibilidade do estágio do processo
  • Nenhuma escalabilidade
  • Paralelismo Moleculá (o moleco lá tentava paralelizar com braço esquerdo e direito)
  • Equipe tecnicamente desatualizada, que vivia uma era inteira apagando incêndios
  • Equipe reduzidíssima

Como resolver essa questão:

1) Durante as discussões sobre os formatos de XML, vimos que muitos parceiros, e o mercado em si, estavam convergindo para um padrão chamado DDEX. Esse standard serve para qualquer integração imaginável (programas, músicas, vídeos, ringtones, wallpapers etc). Apostar nesse formato foi uma escolha ousada, mas trouxe muito resultado. Como o padrão é extremamente amplo e aberto para diversas finalidades, há campos demais para dados de menos. O resultado é a possibilidade de interpretação particular de cada parceiro sobre cada uma das áreas do XML. São 8 formas diferentes de se tirar um conteúdo do ar, de forma implícita ou explícita entre outras variações bem peculiares. Mesmo com essa complexidade, unificar o modelo de entrega nesse formato nos trouxe a segurança para criar uma única aplicação só para processar esse conteúdo, então conseguimos ter uma visão unificada do processo, e ainda escalável.

2) Viabilizar a análise eficiente dos XML`s

Primeiro pensei em um database SQL para armazenar o conteúdo do XML, mas quando estávamos próximos de modelar 100 tabelas, e não estávamos sequer na metade do mapeamento, pedi que parassem a atividade para que pudesse repensar. Já haviam empenhado algum esforço nessa mesma iniciativa no passado, mas ignorei. Acreditei que com alguma supervisão mais técnica seria possível: Não. não era! Subestimei o padrão “dos infernos”. Era surreal, se modelássemos, seriam pelo menos 300 tabelas para gerir. Era necessário encontrar uma alternativa.

Após a desistência de bases SQL, pensei em usar uma base XML, mas nada me agradou: nem bancos, nem tecnologias, nada. Então pensando muito na possibilidade, optei por usar o MongoDB para realizar essa tarefa. Como MongoDB não trabalha com XML, geramos conjunto de classes com base no XSD, fazendo o parser direto de XML para .Net, que em seguida é persistido diretamente os objetos no MongoDB. O resultado foi um sucesso, extremamente eficiente, agora permitindo consultas nos dados que eram do XML, e agora são BSON direto no MongoDB. Foi uma excelente decisão, poupando muito tempo e facilitando muito o trabalho. Pelo fato das classes terem sido geradas com base no XSD, as garantias de formato estão todas presentes, então tenho o melhor dos mundos!

3) A falta de visibilidade dos pacotes (pacotes são entregas de 1 XML de metadados com suas N mídias) e a pulverização do FileSystem era problema do passado e do presente, agravante e complicómetro para o processo. Analisar diretórios de servidores de produção era tarefa diária. Entenda, eram pelo menos 16 storages, onde cada pacote poderia estar em qualquer um deles. O processo de gestão de espaço em disco era apontar o FTP para um path relativo ao root do disco que todos conheciam. Todos os 16 storages possuíam os mesmos paths, e o endpoint do FTP hora apontava para qualquer um desses 16 storages, de acordo com a disponibilidade de discos.

Gerir esse caos, era complicado, pois o acesso a disco é muito custoso. Nesses storages onde petabytes eram armazenados em poucas pastas, era surreal fazer qualquer busca ou leitura. Não havia eficiência na leitura desses discos, portanto, eu precisava de alguma representação desse file system, mais rápida e eficiente. Algo como um file system virtual que representasse o file system físico. Pois diversas análises precisam ser feitas com base na estrutura, nos nomes do arquivos, para que pudesse tomar uma decisão sistêmica. Na prática eu precisava consolidar os pacotes fragmentados, para que no final do processo, fosse eliminada a fragmentação.

Criei uma base SQL para gerir o processo com uma visão semelhante à do FileSystem (Diretórios, arquivos, tamanho), tudo exceto o conteúdo do arquivo em si, permitindo com queries determinar exatamente o que cada disco possui. Agora não precisamos estar em contato direto com o File System, queries em um banco SQL mostravam todo a estrutura do File System. Um processo simples fazia a sincronização periodicamente. Uma característica legal é que por mais que o pessoal de infraestrutura cuidasse do FTP da mesma forma como sempre fizeram, meu trabalho poderia ser feito de forma coesa, reduzindo os impactos dessas decisões. Por outro lado, para o processamento das tarefas, havia uma escolha de prioridade entre os discos, isso permitia que eu mesmo não sofresse com as necessidades específicas de gerir storages tão grandes.

4) Criação de uma infraestrutura de arquitetura robusta que facilitasse e promovesse o refactoring contínuo.

Geração de Código

O Oragon Architecture foi fundamental para a criação desse modelo. Com ele geramos código de acesso a dados, para todas as bases necessárias, gerando código e configuração para NHibernate e FluentNHibernate. Trabalhando com MySQL e SQL Server side-by-side, com múltiplos databases, hora em contexto transacional, hora somente read-only. Agora, a ausência de um owner de cada database, não representa mais um problema. O banco muda, mas periodicamente geramos código a partir do banco, e é comum ver coisas mudando. O que nos afetar, diretamente e for incompatível, gera uma quebra no build: automaticamente, aumentando a segurança e resiliência na solução. Da mesma forma que geramos código com uma grande riqueza de detalhes, estamos aptos a regerar os bancos a qualquer momento, e identificar o que mudou estruturalmente no banco, bastando analisar nosso mapping com a ajuda de históricos do scm.

AOP

Sob o framework de AOP do Spring.Net, o Oragon Architecture implementa uma série de aspectos que torna viável trabalhar com Redis, MongoDB, MySQL, DB2 e SQL Server de forma transparente e simples. Um único template é necessário para todos os bancos SQL citados. Para o NoSQL, como é schemaless (não é mais), não havia como gerar código, mas os aspectos são úteis por definir a conectividade da mesma forma para todas as soluções. Do ponto de vista do código, nenhum OpenSession ou Connect é visível, apenas precisa-se marcar os métodos com os atributos corretos para que a infraestrutura cuide de todo o resto. Outro ganho direto é a resolução de connection leaks.

Ainda falando do Spring.Net, jobs agendados foram criados com Quartz.Net para iniciar operações de negócio agendadas, como emissão de relatórios que anteriormente eram criados manualmente periodicamente entre outras operações recorrentes agendadas. A sincronização do File System com o banco SQL que citei acima, também era agendada aqui.

Mensageria e Exception Handling

Para escalar a solução, um pipeline foi criado em cima do RabbitMQ para processar todo o fluxo de trabalho das importações, desde a gestão (monitoramento) do file system, até a ingestão propriamente dita. Nesse modelo o Oragon fornece as abstrações para que você só implemente as operações necessárias em cada passo do pipeline, sem que seu código saiba sequer que faz parte de um. Assim basta programar uma operação de negócio e plugar no pipeline, caso haja erro no processamento, lance suas exceptions e o próprio orquestrador (Oragon) se encarregará de colocar a mensagem na fila de erro, para que a mensagem não seja reprocessada infinitamente, com o mesmo status de erro. Geralmente os erros são de duas naturezas, ou o cliente errou no contrato ou o código encontrou uma situação não prevista, há um terceiro cenário, pouco comum que é uma falha em validação de negócio. Para essa temos um fluxo de tratamento diferenciado.

Em todos os casos, as exceptions são logadas, automaticamente, pelos aspectos Oragon Architecture, em filas RabbitMQ, isso com ajuda do pacote Spring.NET AMQP, posteriormente são armazenadas no Sentry sem causar gargalos na aplicação. Mais tarde, após inúmeros problemas, substituímos o Sentry por ElasticSearch.

Worker Process

Agora, estamos aptos a iniciar diversos hosts de processos, para consumir uma infinidade de filas, segmentando cada operação por parceiro se assim nos interessar. Conseguimos trabalhar com diversos processos em diversas máquinas, escalando até quanto o hardware e os databases suportarem.

A arquitetura, cheia de abstrações viabilizou tomar as decisões que envolviam estratégia de deploy o mais tarde possível. Esse é o ganho trazido por uma aplicação extremamente configurável. Mas não há almoço grátis. As configurações são tão relevantes quanto o código e são extensas. No código, não precisamos ver sequer um try-catch, e não há nada de errado nisso. Os Aspectos são responsáveis por pela gestão e tratamento das mensagens. As operações transacionais, são geridas pelos aspectos e eles são responsáveis pelo rollback em caso de erro. As configurações são muito mutáveis e maleáveis, mas são de extrema importância para o fluxo. Enfim, toda escolha, uma renúncia.

Continous Integration e Continuous Deploy

Para o processo de build e deploy, havíamos configurado o Jenkins, mas agora estou apanhando um bocado do TeamCity. mas em breve teremos novamente um processo de build e deploy automatizado nele.

Esse foi o roadmap de um ano de trabalho. É realmente muito excitante trabalhar com essas tecnologias, mas também bem relevante conseguir atender e endereçar todas essas demandas.

Hoje a maior dificuldade está no Business. Compreender e estabilizar as expectativas quanto ao business, que no caso de um processo de importação de metadados, é preencher as devidas tabelas, garantindo retro-compatibilidade com um parque de aplicações complexo e infinito.

Resumo

Bom, novamente, os frameworks utilizados foram utilizados para sanar necessidades reais.

Spring.Net para IoC, DI e AOP

Oragon Architecture para AOP (implementação)

Quartz.Net para Agendamentos

TopShelf para Serviços Windows (joguei um monte de código fonte fora, depois que achei esse framework)

T4 para os templates de geração de código

Spring AMQP para abstrações com RabbitMQ

Lembre-se das premissas:

Equipe:

Equipe reduzida.

Contexto:

GAP técnico, falta de Gerência de Configuração, e segmentação do código. Falta de padronização de recebimento de conteúdo e dificuldade de gestão de código, plataforma e manutenção.

Enfim, Arquitetura

A correlação entre as diversas peças que irão compor a solução, cada uma oferecendo uma feature de negócio.

Espero que tenha gostado!

 

[03/04/2018] – Revisão do Texto para melhorar a explicação sobre algumas necessidades.




Mautic Tags

Gostou, quer saber mais sobre isso? Deixe sua opinião, dúvida ou simplesmente comente!