Livro FullCycle-0.6
Livro FullCycle-0.6
Livro FullCycle-0.6
Wesley Willians
Esse livro está à venda em http://leanpub.com/fullcycle
Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
A mudança de perspectiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Full Cycle Developers @Netflix . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Devs com muitas responsabilidades . . . . . . . . . . . . . . . . . . . . . . . 4
Times de plataforma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Você é Full Cycle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Escalando software: vertical vs horizontal . . . . . . . . . . . . . . . . . . . 80
Escalando software: descentralização . . . . . . . . . . . . . . . . . . . . . . 82
Introdução à resiliência . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Service mesh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
A mudança de perspectiva
Naquela época, não muito diferente de outras organizações, eles possuíam papéis
extremamente bem definidos para o ciclo de desenvolvimento de um software.
Depois de muitos erros e acertos, perceberam que um dos principais pontos que
sem dúvidas mudaria o jogo, seria transferir totalmente a responsabilidade de
cada projeto para seus times de desenvolvimento, ou seja: agora os desenvolve-
dores fariam parte de todo o ciclo de desenvolvimento de suas aplicações. Da
arquitetura ao deploy e monitoramento.
O grande lema se tornou: “Operate what you build”, ou opere o que você mesmo
constrói. O raciocínio foi remover intermediários de todo processo e fazer com
que a equipe de dev fique 100% responsável por seu microsserviço sendo capaz
de trabalhar com feedbacks curtos de todo processo e aprender rapidamente com
isso.
Introdução 4
Foi exatamente essa pergunta que fiz para mim mesmo assim que estava na
metade do artigo. Porém, ao continuar com a leitura percebi que os autores
deram uma solução para minimizar esse fardo e ao mesmo empoderar devs
cansados e estressados com tantos conflitos que resultavam em idas e vindas
junto a área de operações para solucionar problemas em produção e para colocar
suas aplicações no ar.
Times de plataforma
Grande parte das empresas já possui times de plataforma, que têm o objetivo
de dar suporte e autonomia para as pessoas desenvolvedoras no dia a dia. Esses
times criam ferramentas, padronizam pipelines exatamente para que o processo
Introdução 6
A partir de agora, vamos partir para uma jornada completa do Software Develop-
ment Life Cycle. Vamos partir do mundo da arquitetura de software até o deploy
e monitoramento de aplicações de grande porte.
Introdução à Arquitetura de
Software
Neste capítulo explicaremos o que é a arquitetura de software. Os conhecimentos
sobre esse tema podem nos auxiliar desde o processo de desenvolvimento de uma
solução até sua sustentabilidade.
Outro ponto importante que precisamos entender é que o software, de modo ge-
ral, busca resolver uma “dor” que a organização tem. Então, utilizando o exemplo
do iFood, por ser uma empresa de tecnologia voltada a entrega de alimentos, algo
que poderia ser feito por meio de uma ligação telefônica, essa precisou evoluir
para assim ter diferenciais que fizessem com que a empresa pudesse se manter
no mercado. Por exemplo, seu software tem mais escalabilidade para ele seja
Introdução à Arquitetura de Software 9
O software pode ser considerado parte de uma empresa, ou seja, a medida que a
organização evoluiu esse software precisa evoluir também. Ele precisa se manter,
evoluindo, de forma que o custo dele seja muito menor do que o resultado que
ele está trazendo para a corporação. Desse modo, haverá um ponto de equilíbrio,
isto é, a solução conseguirá retornar seu investimento.
dia zero cada desenvolvedor, arquiteto, product owner, entre outros, de forma
intencional, pensarem em como fazer com que esse software fique no ar e
entregue valor por diversos anos.
Então, para que um software seja sustentável, ele precisa ser bem arquitetado,
assim poderá se manter mais tempo no ar e conseguir gerar mais retorno para
a organização. Ou seja, sem sustentabilidade no dia zero o software pode trazer
mais prejuízos do que valor e isso pode significar a razão da empresa continuar
ou não suas operações.
Modalidades arquiteturais
Arquitetura tecnológica
Neste tópico falaremos um pouco sobre quem é e o que faz uma pessoa arquiteta
tecnológica. Daremos exemplos para que se perceba qual sua importância dentro
de um negócio.
Assim, quando uma corporação opta por trabalhar com determinadas tecno-
logias, por suas complexidades, é evidente a necessidade do trabalho de uma
pessoa arquiteta tecnológica para que se possa alcançar êxito em seus projetos.
Arquitetura corporativa
A pessoa arquiteta corporativa, então, poderá fazer a avaliação dos custos que
toda a área de desenvolvimento e engenharia terá para desenvolver os projetos
que farão sentido para um negócio crescer. Esses custos podem ser com Devs,
verticais de desenvolvimento, tipos de tecnologias e licenças. Além disso, essa
pessoa está habilitada também para o planejamento de grandes implementações
tecnológicas. Por meio de sua análise é possível verificar se é necessário ou não
a migração de todos os setores para essa nova tecnologia implementada, por
exemplo. Assim, esse profissional poderá indicar uma possível padronização das
tecnologias dentro da empresa.
Por meio de alguns exemplos será possível compreender melhor a sua relevância
para uma organização. Pensemos em uma situação hipotética em que temos
Introdução à Arquitetura de Software 14
Outro exemplo prático é quando surge uma nova tecnologia, ou são lançadas
novas versões das ferramentas já existentes, e a empresa precisa analisar se a
migração faz sentido para aquela situação e contexto. Nesse sentido, a pessoa
arquiteta corporativa estará apta para que tal decisão seja tomada da melhor
forma possível.
Arquitetura de solução
O primeiro ponto que precisamos entender é que o trabalho dessa pessoa fica
entre a área de negócios e a área de software, ou seja, fica em uma área técnica
que busca transformar requisitos de negócio em soluções de software. Isto é, ela
vai enxergar as especificações e as necessidades da organização e fazer isso virar
software.
Outro papel que também pode ser atribuído a essa pessoa é o de analisar os
impactos comerciais em relação a uma escolha tecnológica. Por existir diversas
formas de solucionar uma necessidade dentro de uma empresa, ela escolherá,
Introdução à Arquitetura de Software 16
Vamos imaginar, por exemplo, que todo software legado de uma companhia
está utilizando a AWS, não fará sentido fazer esse cliente migrar toda sua
infraestrutura para o Google, somente por uma opinião de que o Google é
melhor, a não ser que exista, por exemplo, um ponto financeiro que será
impactado positivamente com a mudança. Ou seja, toda escolha vai depender
do contexto da organização. Outra situação, seria se toda base de dados de um
sistema de uma empresa estivesse utilizando Oracle; não podemos mudar para
SQL Server se não fizer muito sentido para o crescimento da corporação.
Além disso, essa pessoa poderá contribuir para que se possa desenvolver uma
solução que mantenha uma relação bem estruturada entre seus componentes,
fazendo com que esses atendam os objetivos do negócio. Isto é, ela poderá fazer
com que esses componentes, em conjunto, apesar das restrições, consigam gerar
uma solução de alta qualidade que irá atender as necessidades da corporação.
Outra definição mais formal é a da ISO/ IEC/IEEE 42010. Este instituto define
arquitetura de software como “…a relação de um sistema e seus componentes,
suas relações, seu ambiente, bem como os princípios que guiam seu design e
evolução”. Normalmente quando desenvolvemos um software não pensamos
apenas em curto prazo, pensamos, ou deveríamos pensar, em como esse software
vai evoluir a médio e a longo prazo. Logo, a pessoa arquiteta de software vai
pensar nos componentes atuais da corporação e em como esses poderão evoluir
dentro de uma solução. Ou seja, apresentará o desenho de um software, de fato,
sustentável.
A lei de Conway pode nos ajudar a entender um pouco mais como funciona a
arquitetura de software. Melvin Conway diz que “organizações que desenvolvem
sistemas de software tendem a produzir sistemas que são cópias das estruturas de
comunicação dessas empresas”. Vamos imaginar um exemplo prático: uma com-
panhia decide implementar um sistema de software. Nessa corporação, há um
Introdução à Arquitetura de Software 19
outras palavras, dependendo dos componentes que o software terá, essa pessoa
auxiliará na seleção dos profissionais que farão parte do projeto. Diferentemente
da pessoa arquiteta de solução que quase sempre aborda aspectos de alto nível,
sendo que raramente chegará a trabalhar diretamente com código.
comunicação entre esses dois profissionais, para que a aplicação seja produzida
do jeito esperado.
Em outras palavras, por ser cada vez mais natural que as equipes tenham
autonomia para tomar decisões de design e arquitetura, é muito evidente que a
grande pressão do dia a dia faça com que o software tome caminhos arquiteturais
diferentes do que se havia planejado para garantir sua sustentabilidade, assim
como na garantia do atendimento dos atributos de negócio; por conta disso, um
arquiteto ou arquiteta de software deve se fazer presente nos projetos.
Code reviews
Ter conhecimento sobre arquitetura permite navegar da visão macro para a visão
micro de um ou mais software. Dessa forma, é possível visualizar aspectos de
alto nível e de baixo nível dentro da solução. Com isso, podemos perceber que o
código é um componente que se relaciona com outros. Podemos garantir também
que esses componentes sejam construídos de modo que possam ser desacoplados
caso isso seja necessário para sua evolução.
Introdução à Arquitetura de Software 25
Ao aprender sobre a visão macro e micro de uma solução a pessoa poderá ter
mais clareza do impacto que o software possui na organização como um todo
e não apenas em uma área. Esse senso de pertencimento, ou seja, de saber
exatamente como seu trabalho afeta toda corporação, tem benefícios para a sua
carreira, bem como para manter sua motivação profissional por saber que seu
trabalho é significativo.
É importante saber também que aprender sobre arquitetura nos traz a necessi-
dade de mergulharmos em padrões de projetos e de desenvolvimento e suas boas
práticas. Ou seja, a arquitetura nos força estudar quais foram os padrões que
Introdução à Arquitetura de Software 27
Existe uma linha de pensamento que afirma que arquitetura e design de software
são a mesma coisa. Neste tópico, faremos algumas reflexões sobre essas duas
áreas e, assim, iremos perceber que, de certo modo, essas áreas podem ser
consideradas distintas.
Por exemplo, para que logs de um sistema possam ser centralizados e facilmente
recuperados, a Elastic Stack será utilizada. Podemos perceber que tal decisão
afetará a aplicação como um todo, além da possível contratação de mais
infraestrutura para que a Elastic Stack seja instalada, ou mesmo, uma eventual
contratação de um serviço gerenciado na Elastic Cloud. Decisões como essa
podem afetar todos os sistemas de uma organização, seu orçamento, o tempo
em que um possível bug pode ser corrigido, a forma com que cada time
trabalhará no dia a dia com a observabilidade, além do conhecimento básico na
Stack que será requerida por cada desenvolvedor e eventuais treinamentos que
os mesmos deverão receber para operar ferramentas. Decisões que impactam
diretamente em quais componentes e vendors que um projeto utilizará são
decisões arquiteturais.
Introdução à Arquitetura de Software 29
Por outro lado, quando tomamos decisões de quais patterns GoF (Gang of Four)
o projeto utilizará; SOLID, DRY, Clean code, a quantidade de camadas de uma
aplicação, estamos nos referindo fundamentalmente ao design do software.
Organização
Estruturação
Estruturar um software significa organizá-lo para que este seja de fácil evolução
e componentização. Além disso, a solução precisa atender os objetivos de
negócios, tendo componentes com estruturas claras. Sem isso não é possível criar
um software de qualidade e que consiga evoluir com o passar do tempo.
Introdução à Arquitetura de Software 30
Componentização
Dentro de uma corporação é comum que se tenha mais de um software. Por isso
é importante que, ao desenvolvermos uma aplicação, saibamos como preparar
seus componentes para que esses consigam se integrar de maneira eficiente
dentro de um processo maior. É necessário, então, observar se os protocolos
estão apropriados, se as redes estão sendo usadas de modo necessário e se as
regras de segurança estão sendo efetivas.
Introdução à Arquitetura de Software 31
Governança
Hoje em dia, com o modelo de Squad, isto é, cada time criando seu próprio
software e entendendo como as coisas funcionam, raramente vemos Ras de um
modo formalizado. Antigamente, trabalhávamos muito com vários documentos
do excel, um requisito por vez, ou seja, cada detalhe de todos os requisitos
arquiteturais separadamente e podíamos ver RAs formalmente dentro das
organizações.
Performance
Armazenamento de dados
Quando uma empresa, eventualmente fecha contrato com a AWS, por exemplo,
e a equipe precisa se adaptar para utilizar seus bancos de dados, inclusive
o DynamoDB. Dizemos que essa adaptação está relacionada a um requisito
arquitetural de armazenamento de dados.
Escalabilidade
Segurança
Legal
Como já foi dito, precisamos observar quais são os requisitos legais para que
nós possamos cumprir a legislação vigente de cada país. No Brasil, temos a
LGPD (Lei Geral de Proteção de Dados), extremamente necessária para que seja
possível construir mecanismos que evitem ao máximo o vazamento de dados.
Introdução à Arquitetura de Software 35
Audit
Marketing
No dia a dia, não vemos esse tipo de organização sendo feita com frequência.
Geralmente as coisas acontecem de maneira mais orgânica e flexível, mesmo em
corporações de grande porte com diversas restrições como os bancos. Porém, de
uma forma ou de outra, requisitos existem e muitas vezes ficam subentendidos.
O que devemos levar em consideração é que quanto mais clareza nós tivermos
do tipo de software que iremos criar, ou seja, dos requisitos arquiteturais que
teremos que utilizar, mais clareza teremos no processo de desenvolvimento.
Desse modo, poderemos evitar ruídos quando nossa aplicação estiver no ar.
Características Arquiteturais
Trabalhar baseado em uma intenção significa estar preparado para resolver de-
terminados problemas. Caso não tenhamos uma base de arquitetura no processo
de desenvolvimento talvez até consigamos resolver um problema indiretamente.
Porém, isso significaria contar com a sorte, e nisso não existe garantia de que o
nosso sistema irá funcionar conforme o esperado.
Por exemplo, sabemos que um software precisa ser resiliente, ou seja, ele precisa
se adaptar em momentos de crise e se recuperar rapidamente, ou ter um plano
“B” para que não deixe de funcionar diante das dificuldades (adversidades).
Vamos imaginar que quando estamos desenvolvendo um sistema não tenhamos
pensado em resiliência de maneira explícita, ou seja, de forma intencional. Mas
ao utilizarmos bibliotecas, com infraestrutura montada e com muitos aspectos
pré-definidos, essa resiliência acaba sendo feita, de maneira embutida e de graça,
podemos dizer de modo natural. A conclusão é que nosso sistema pode até ser
resiliente, mas isso foi conseguido de modo não intencional. Isso aconteceu
por sorte, funcionou mas poderia não ter funcionado. Nesse caso, sabemos
Introdução à Arquitetura de Software 38
Características Operacionais
que você seja um expert em criar subnets ou, ainda, um expert em gerenciar
backups, por exemplo. Porém, é importante que você saiba como facilitar essas
operações. Outro ponto importante, é que possamos compreender como um
possível backup pode afetar a performance de nossa aplicação. Resumidamente,
tudo que é operacional, normalmente, são coisas que não iremos fazer mas que
devemos permitir que sejam operadas em nosso software.
Disponibilidade
Sempre que vamos criar um software, podemos pensar em como garantir que
esse esteja disponível. Podemos pensar, por exemplo, se essa solução ficará no ar
24/7. O nível de SLA, que é o quanto combinamos com o cliente, e de SLO, que
são os objetivos que queremos garantir para o cliente.
seja, se hipoteticamente podemos ficar indisponíveis somente uma vez por ano
durante 1h. Caso fiquemos 20 min indisponíveis, logo teremos somente mais 40
min para ter essa indisponibilidade durante o restante do ano. Assim, precisamos
saber como verificar esses incidentes.
Recuperação de desastres
Outro exemplo é se, por algum motivo, caísse uma região AWS em que nosso
sistema está. O quanto estaríamos dispostos a pagar para trabalharmos com
multirregião ou para trabalharmos com multicloud. Todos esses aspectos devem
ser levados em conta.
Introdução à Arquitetura de Software 41
Performance
Ao projetarmos uma aplicação é importante que pensemos que esta precisa ser
performática. Nesse sentido, falaremos basicamente sobre throughput, que a
capacidade de receber e processar requisições. É essencial pensarmos intenci-
onalmente no quanto de performance queremos ter, ou seja, o quanto nosso
sistema precisa suportar. Por exemplo, digamos que temos duas situações, na
primeira um sistema que precisa suportar 5 mil requisições por segundo e na
segunda situação um que precisa suportar 50 requisições por segundo. Esses
sistemas precisam ser arquitetados de maneira diferente. Talvez, a segunda
situação não precise que trabalhemos com CQRS, mas na primeira situação
essa opção é necessária. Pensamos em performance principalmente quando
trabalhamos de forma intencional.
Recuperação (backup)
Pode existir uma dificuldade de pensar nesse aspecto, principalmente por ser
comum surgir a ideia de que a necessidade do backup está associada a algo ruim
que aconteceu em nossa aplicação. O problema é que quando não temos essa
reflexão podemos precisar do backup e este não estar disponível. Ultimamente
nós podemos ficar, de certo modo, tendenciosos a confiar na nuvem como que
sempre garantirá toda disponibilidade que você precisa, por outro lado, temos
que lembrar que trabalhar com computação em nuvem significa trabalharmos
Introdução à Arquitetura de Software 42
Confiabilidade e segurança
Quando pessoas tentam fazer brute force para entrar e fazer login em uma
plataforma, também podemos considerar como um exemplo de como aspectos
de segurança precisam ser pensados de maneira intencional. Essas pessoas
podem colocar robôs para rodar e tentar descobrir a senha dos usuários, o que
acaba gerando um número grande de requisições. Sabemos que a maioria dos
sites possuem regras e políticas de senhas fortes que dificultam a quebra de
senhas. Porém, quando essa pessoa mal intencionada faz vários brute force acaba
afetando o banco de dados, a velocidade, utilização da CPU, etc. Imaginemos
que uma situação semelhante a essa aconteça durante um evento, ou seja,
comecemos a receber vários acessos a ponto de percebermos que nosso POD
no kubernetes começou a escalar descontroladamente. Isso pode significar que
estamos recebendo milhões de requisições de robôs, vindo de vários lugares dife-
rentes. A primeira solução seria colocarmos um Captcha e de fato é uma ótima
solução. Porém essa alternativa não resolve tudo. Poderá resolver o problema de
segurança mas já teríamos recebido as milhares de requisições e nosso sistema já
estaria afetado. Imaginemos, então, um cenário mais complicado: se durante um
evento de vendas ficássemos indisponíveis, geraria um transtorno imenso para
empresa, por não ser possível realizar as vendas. Observe que nossa aplicação
precisa ter camadas adicionais de segurança para continuar funcionando mesmo
frente a esses tipos de ataques.
Introdução à Arquitetura de Software 44
Robustez
Escalabilidade
Neste tópico, vamos pensar um pouco no poder que nossa aplicação consegue
escalar. Temos, basicamente, duas formas de escalabilidade: vertical e horizontal.
É necessário que possamos garantir que nosso sistema ficará o mínimo escalável
possível, principalmente de forma horizontal. Para isso é importante trabalhar
de forma stateless, bem como seguir boas práticas no processo desenvolvimento
como por exemplo o famoso “The Twelve-Factor App”.
Características Estruturais
No tópico anterior falamos um pouco sobre como fazer para que nossa aplicação
seja operada mais facilmente, isto é, sobre as características operacionais.
Neste tópico, falaremos sobre as características estruturais, que estão mais
relacionadas aos pontos de atenção que devemos ter no software para que este
funcione de forma cada vez mais flexível.
Configurável
Extensibilidade
É fato que uma aplicação deve ser pensada para que consiga crescer. Ou seja, ela
precisa crescer de certo modo que as coisas possam ser “plugadas” nela.
Por exemplo, vamos imaginar que iremos utilizar a gateway de pagamento “X”
em nossa solução. Faremos, logicamente, a implementação dessa gateway em
nosso sistema. Porém, imaginemos que nosso superior solicite, posteriormente,
que façamos uma mudança para gateway “Y”. Agora, obviamente precisaremos
fazer uma nova implementação. Mas caso, durante essa nova implementação,
precisemos mudar pontos estruturais de nossa aplicação para conseguir adicio-
nar a nova gateway, é bem provável que tenhamos projetado nosso sistema de
maneira errada.
Precisamos conseguir trabalhar com interfaces, adaptadores etc. para que possa-
mos simplesmente adicionar coisas a nossa aplicação, de modo que não fiquemos
reféns dos vendors que trabalhamos.
Assim, nossa aplicação precisa ser extensível tanto nos lados de vendors, que vão
Introdução à Arquitetura de Software 48
Fácil instalação
Mas quais são os principais problemas que enfrentaremos para fazer a instalação
de uma solução?
Em segundo lugar, voltaremos a falar que a aplicação precisa ser de fato confi-
gurável, isto é, se for muito muito difícil de configurar seu sistema, obviamente,
será dificultoso fazer a instalação.
Introdução à Arquitetura de Software 49
Outro ponto que podemos abordar é que muitas vezes a aplicação tem depen-
dências que são extremamente complexas para se trabalhar. Um exemplo disso
é quando o sistema vai trabalhar com Elasticsearch, que é algo extremamente
complexo, principalmente em nível de infraestrutura. Nesse caso, podemos pen-
sar em alguns questionamentos: Como fazer para testar isso? Podemos trabalhar
com docker e com Elasticsearch? Quando formos trabalhar em produção, como
trabalhar com processos de configuração e conexão? Como vai ser a criação dos
índices? Serão criados pela aplicação ou manualmente no servidor de produção?
Ainda sobre essa reflexão podemos nos lembrar do RabbitMQ e a criação de filas,
criaremos a fila ou esta já vai vir?
Reuso de componentes
Usar componentes para facilitar nosso dia a dia, pode mudar completamente
a forma como iremos trabalhar. Porém devemos levar em consideração que
existem alguns aspectos que não são tão simples de lidar.
que uma grande vantagem desse sistema é que não existe latência de rede,
isto é, não temos problemas de conexão, pois tudo está dentro de um mesmo
sistema. E uma vez que estamos dentro de um mesmo sistema podemos ter
frameworks e boas bibliotecas para facilitar nosso trabalho. Quando temos um
mundo um pouco mais distribuído, com microsserviços e diversos sistemas,
muitas vezes as equipes acabam criando soluções iguais para resolver o mesmo
problema. Ou seja, digamos que a equipe “A” crie uma biblioteca de validação
e a equipe “B” crie outra biblioteca de validação; eventualmente o que poderá
acontecer é que teremos duas coisas para serem mantidas. Então, nesse momento
poderíamos pensar na possibilidade de ter uma vertical dentro da empresa, onde
adicionaremos todas as todas as bibliotecas que podem ser compartilhadas e
criaremos times paralelos para manter essas bibliotecas, assim todos podem
utilizá-las.
Internacionalização
Esse aspecto não é visto com tanta frequência no Brasil, pois não é comum que
precisemos internacionalizar nossos softwares. Obviamente, pode surgir esse
tipo de trabalho e, por isso, é importante que saibamos minimamente como
desenvolver nossa solução possibilitando sua internacionalização.
maior dificuldade pode ser outra. Por exemplo, na área do Frontend podemos
desenhar um layout de certa forma, mas quando precisarmos trocar a linguagem
do sistema, este poderá ficar totalmente desconfigurado e consequentemente as
coisas não irão funcionar do modo que planejamos. Outra situação, é o fato
da cultura de quem passará a usar, após internacionalização, ser totalmente
diferente. Estes aspectos dificultam o manuseio do software principalmente pela
tradução.
Então, podemos perceber que esse aspecto é extenso e demandaria muito tempo
para abordarmos de forma mais detalhada. Porém, o que podemos destacar é
que sempre que criarmos uma aplicação é importante pensarmos se existe a
possibilidade de internacionalização. Caso a resposta seja sim, devemos começar
Introdução à Arquitetura de Software 52
Portabilidade
A verdade é que nunca vai ser tranquilo mudar o banco de dados sem impactar
nossa solução. Mas podemos dizer que, tecnicamente, é possível mudar o banco
de dados sem impactar de uma forma muito significativa o código de nossa
solução. Por exemplo, se estivermos trabalhando com Elastic Stack e precisarmos
mudar para New Relic ou para Datadog, devemos pensar se esta mudança será
fácil para nosso sistema.
Outro questionamento, é se vale a pena trabalhar com open telemetry. Será que
poderá facilitar nosso trabalho com vendors? Além disso, saber se está fácil
mudar um gateway de pagamento também ajuda a pensar em portabilidade.
Assim, pensando na portabilidade, podemos fazer com que os sistemas fiquem
menos dependentes dos vendors.
Introdução à Arquitetura de Software 53
Diante de tudo isso, podemos trazer algumas dicas: primeiro, foque em obser-
vabilidade e em padrão de geração de logs. Cada framework tem um padrão de
logs, logo, é extremamente importante que tentemos consolidar os nossos logs
em um único padrão e, dessa maneira, ficará muito mais fácil todo processo de
operação.
Introdução à Arquitetura de Software 54
Características Cross-Cutting
Acessibilidade
Precisamos estar cientes de que nossa aplicação poderá ser acessada por um
grupo diversificado de pessoas, com necessidades de acessibilidade distintas.
Nesse contexto, mesmo havendo diversos padrões que podem nos ajudar, deve-
mos focar sempre em um ponto: outras pessoas conseguem acessar facilmente
nossa plataforma, isto é, pessoas com comorbidades, deficiência visual ou
auditiva e etc. conseguem ter acesso a nossa aplicação?
Então se nos questionarmos sobre o que temos de dados hoje, realmente esses
precisam existir a longo prazo? Caso a resposta seja positiva, como poderíamos
guardar esses dados? E como mantê-los?
Hoje em dia, existem técnicas muito interessantes para trabalharmos com dados.
Por exemplo, o que poderíamos fazer em caso de dados que precisamos com
frequência? Esses dados, mais conhecidos como dados quentes, estarão ali
em nosso banco de dados. Conforme tudo está rodando, vamos acessando e
consultando essas informações. Já dados menos acessados, ou seja, dados frios,
podem ser armazenados em um outro tipo de storage com um menor custo.
Autenticação e autorização
De um modo geral este tema parece algo simples, porém, se trabalhamos com
arquitetura distribuída existem muitas formas possíveis de autenticar e autorizar
Introdução à Arquitetura de Software 56
requisições. Por isso, de certo modo, torna-se algo mais complexo do que parece.
Outro ponto que merece nossa atenção é a possibilidade de utilizarmos API Ga-
teway em nossa solução, um serviço que poderá fazer o processo de autenticação.
Existem muitas empresas hoje em dia em que o sistema não tem mais validação
de autenticação, justamente porque a autenticação acontece na API Gateway.
Devido a isso sabemos que o usuário que está chegando naquele sistema já
passou por uma autenticação.
Por outro lado, quando trabalhamos com sistemas monolíticos esse processo
de autenticação e autorização do usuário é algo mais simples. Pois é possível
encontrarmos diversos frameworks que nos ajudarão a resolver desafios dessa
natureza. Quando estamos no mundo distribuído isso realmente será um pouco
mais complexo. Nesse segundo caso, é sempre importante pensarmos em um
Introdução à Arquitetura de Software 57
Legal
É importante termos em mente, além disso, que tudo o que acontece em nossa
aplicação precisa estar em conformidade com as leis do país onde ela está
rodando, inclusive temas relacionados ao uso de dados. Isso deve ser pensado
em todas as aplicações que formos desenvolver. Mas normalmente quando
uma organização está muito ligada a esse ponto é comum que essa corporação
transfira esses aspectos para nós desenvolvedores.
Privacidade
é uma cultura que nós, enquanto desenvolvedores, devemos ter. Por exemplo,
quando vamos testar uma aplicação é muito mais fácil testar se tivermos uma
cópia do banco de dados em produção para validar as coisas. Neste momento, é
importante sabermos que quando precisarmos de uma forma a mais para validar
vazamento de dados, mesmo nomes e emails já são considerados dados.
Existem muitas “manobras” que as empresas estão fazendo para ajudar nessa
questão. Por exemplo, todos os dados sensíveis de usuários estão sendo separados
em outro banco de dados, ou seja, em outro nível de serviço; mas eventualmente
podem ficar criptografados. Assim, o sistema principal só roda com dados muito
básicos para que o usuário consiga carregar. A organização faz isso porque
quando separamos a base de dados, podemos mitigar os riscos de vazamento.
Essa manobra pode garantir a privacidade do usuário, em conformidade com as
leis vigentes.
Nos dias atuais, falar em privacidade é algo muito crítico na maioria das
organizações. É possível, e provável, que tenhamos clientes que nos façam
assinar diversos contratos sobre políticas de privacidade. Isso ocorre pois a
empresa responde legalmente pelos dados dos usuários.
Segurança
servidor. Além disso, é interessante que trabalhemos com web firewall. Vamos,
assim, criar regras e mecanismos que consigam identificar os robôs para ficar
batendo em nossa aplicação. Dessa maneira, com o uso de web firewall já
conseguiremos barrar tentativas de SQL injection, XSS e provavelmente as
principais tentativas de exploração que podem ser encontradas na OWASP.
Outra sugestão muito importante nesse aspecto é que devemos usar preferenci-
almente padrões abertos em nosso software. Por exemplo, não é interessante
tentarmos criar formas próprias de criptografia. Resumidamente, é melhor
evitarmos criar qualquer coisa que envolva segurança. Ao invés disso, é sempre
mais indicado que usemos um padrão aberto. Pois esses padrões foram criados
por pessoas que se dedicaram durante muitos anos a pesquisas e especializações
para criar boas práticas.
Outra prática que pode nos auxiliar é a de manter o backup em outras redes.
Usabilidade
É consenso entre a maioria dos devs que para usabilidade no Frontend existem
diversas ferramentas e profissionais que podem nos ajudar a entender um pouco
melhor a navegação do usuário. Existem ferramentas que mostram o usuário
navegando, isto é, conseguimos ver o seu comportamento. Mas quando falamos
em usabilidade, não podemos nos limitar ao Frontend.
cisamos pensar: “Ela está organizada?”, “Como está organizada?”, “Tem do-
cumentação?”, “Como estamos documentando tudo?”, “É de fácil utilização?”,
“Estamos trabalhando com padrões OpenAPI?”, “Possuímos um contrato claro
da nossa API, que possa disponibilizar para outras pessoas?”, “Como estamos
documentando?”, “Temos um README?”.
Além de tudo isso, é necessário que nos perguntemos também: quem é nosso
cliente? Quem vai usar nossa aplicação? Vai ser outra aplicação? Como pode-
mos desenvolver de modo a facilitar o trabalho com nossa aplicação? Como
proporcionar a melhor experiência possível para o meu cliente?
Quem é de Frontend vai sim ter que pensar em muita coisa. Até dar nomes de
eventos para conseguirmos mapear e trackear os principais eventos, uma vez
que quando estamos trabalhando com algum APM (Application Performance
Monitoring) todas essas informações são armazenas e sem dúvidas vão nos
ajudar em eventuais comportamentos inesperados pelo lado do client.
Neste tópico, falaremos sobre algumas perspectivas que nós, como pessoas
desenvolvedoras, devemos ter para conseguirmos arquitetar um software de
qualidade. Nesse caso, nós podemos citar três perspectivas básicas que nos
Introdução à Arquitetura de Software 61
ajudam a perceber se nosso software está mais propenso a ter sucesso quando
for ao ar.
Performance
Precisamos ter em mente que nossa latência é afetada pelo tempo de proces-
samento da aplicação, rede e chamadas externas. Isso é algo extremamente
importante e não podemos deixar de levar em consideração. Por vezes ficamos
otimizando nossa aplicação, sem considerar o tempo da chamada que o usuário
faz até chegar em nossa aplicação. Quanto mais longe estiver do datacenter,
quanto pior for a rede, consequentemente pior será a performance da nossa
solução. Muitas vezes, nossa aplicação depende de chamadas externas para rodar.
Vamos imaginar que o usuário coloque um CEP para que possamos trazer o
seu endereço. Nessa situação, teremos que acessar a API dos Correios, mas
se essa API estiver muito lenta naquele momento, isso irá afetar diretamente
Introdução à Arquitetura de Software 64
Além disso, é essencial observarmos que se nossa aplicação não for boa, isto é,
não for bem feita, ela também deixará de ser performática.
Introdução à Arquitetura de Software 65
Se não soubermos para onde olhar, podemos acabar arriscando nas tentativas
e erros para realizar o aumento da performance em nosso sistema. Neste
tópico, queremos mostrar “o caminho das pedras” para que você saiba um
pouco mais sobre os pontos onde normalmente podemos encontrar dificuldades
em aumentar a performance, isto é, as principais possíveis razões para que a
performance de uma solução esteja baixa.
O próximo ponto que iremos observar é algo que está, muitas vezes, totalmente
Introdução à Arquitetura de Software 66
visível para os devs. Porém, algumas vezes pode não ser tão perceptível. Estamos
falando sobre trabalhar de forma bloqueante. Ainda é muito comum nos dias
de hoje ver linguagens de e abordagens de desenvolvimento que trabalham
essencialmente de forma bloqueante, gerando claramente uma barreira para
aumentar o throughput da aplicação.
Nós, como devs, temos que conseguir analisar cada um desses tópicos individual-
mente para sabermos onde está o problema. Não adiantaria fazermos o algoritmo
mais performático, se a todo momento estamos fazendo I/O com um disco lento.
Grande parte dos softwares são otimizados e passam por diversos procedimentos
durante seu desenvolvimento. Mas o obstáculo, na maioria das vezes, está no
banco de dados. É importante que saibamos como modelar e usar banco de
dados do modo de forma correta. Além disso, precisamos utilizar estratégias para
buscarmos por performance intencionalmente. Por exemplo, pensar se o nosso
banco de dados está com índice, fazer um “explain” em nossas querys para ver o
Introdução à Arquitetura de Software 68
tempo de execução. Fora isso, precisamos ter ferramentas de APM que realmente
nos mostram, no banco de dados, se nossa query está comprometendo a nossa
aplicação.
Outro ponto importante é o uso de caching. É essencial sabermos que isso tem
se tornado cada vez menos opcional se quisermos ter alta performance. Muitas
vezes, durante o processo de desenvolvimento de uma solução, processamos algo
apenas uma única vez e quando precisamos fazer a mesma consulta no banco de
dados, ou processar o mesmo template, ou fazer o mesmo algoritmo é possível
perceber que a resposta está pronta em um cache. Ele pode estar no disco ou na
memória, porém, em um servidor separado de nossa aplicação.
Por outro lado, temos a opção de escalar nossos sistemas de forma horizontal,
Introdução à Arquitetura de Software 69
Logo, podemos perceber que performance tem uma relação direta com escalabi-
lidade. Pois a escalabilidade tem um ponto muito claro em relação a aumentar
os recursos computacionais, fazendo assim com que a performance do sistema
de forma geral seja ampliada.
Podemos usar uma citação de Rob Pike para diferenciar esses dois termos:
“Concorrência é sobre lidar com muitas coisas ao mesmo tempo. Paralelismo é
fazer muitas coisas ao mesmo tempo”.
Além dessa citação, alguns exemplos práticos podem nos ajudar a compreender
o que é concorrência e paralelismo. Imagine que estamos mexendo no teclado de
nosso computador, depois passamos a organizar nosso microfone e em seguida
falamos com alguém, logo em seguida, após a ligação, ajustamos o teclado e
depois o microfone novamente. Podemos perceber, nesta situação, que estamos
realizando diversas tarefas, ou seja, um pouquinho por vez, mas diversas tarefas.
Isso é denominado como concorrência.
Vamos imaginar que temos um webserver e este tem um worker que trabalha
da seguinte forma: recebe cinco requisições, e cada requisição demora 10ms de
“response time”. Então, se tivermos cinco requisições, demoraremos 50ms para
conseguir realizar a tarefa. Nesta situação, temos um acesso serial. Podemos
considerar esse processo bloqueante. Pois fará cada ação de uma vez, isto é, cinco
requisições de forma serial.
Cache
O cache nos possibilita acessar itens (arquivos, imagens, etc) que já foram
processados e utilizá-los para trazer respostas, de maneira mais rápida, ao
Introdução à Arquitetura de Software 72
usuário final.
Existe um tipo de cache chamado de “cache na borda”. Este fará com que
o usuário não precise bater nem mesmo em nosso cloud provider. Para isso
trabalhamos com algo chamado de Edge Computing. Quando usamos o Edge
Computing, o usuário não bate em nossa máquina, isto é, em nosso kubernetes,
etc; porque ele nos trará um cache totalmente processado na borda ou seja, em
um servidor que fica antes do seu servidor principal.
A plataforma Full Cycle, por exemplo, trabalha com Edge Computing. Isso
significa que trabalhamos com cache de toda nossa plataforma fazendo com que
usuário que acessar nossa nosso frontend, antes mesmo de a requisição bater
em nosso kubernetes, vai bater no serviço CloudFlare Worker. Desse modo, o
browser do usuário fará o download de todo HTML, CSS, Javascript, imagens,
etc, do local mais próximo ao usuário; com isso existe a real possibilidade de que
os arquivos estejam sendo baixados de uma central telefônica do seu próprio
bairro, por exemplo.
Vamos imaginar que temos um algoritmo pesado. Esse algoritmo tem muitas
variáveis que mudam a cada meia hora. Se todas as vezes que o usuário fizer
uma requisição que vai chamar esse algoritmo nós precisarmos processá-lo do
zero, será algo extremamente custoso. Então, para evitar isso, podemos cachear
esse resultado pronto a cada meia hora.
Outro tipo de cache é o de objetos. Existem objetos que nossa solução terá
que criar o tempo inteiro para gerar processamento de alguma forma. Aqui
na Full Cycle nós temos um sistema de ORM que é chamado de Doctrine. Ele
mapeia classes com a estrutura de banco de dados e, baseado nisso, conseguimos
trabalhar com modelo de persistência. O problema é que a todo momento ele
precisa fazer essa correlação entre o ID da classe e o ID da tabela do banco
de dados. Esse parsing tem um custo e, por esse motivo, podemos cachear o
Introdução à Arquitetura de Software 74
objeto que tem toda essa relação com banco de dados, pois a estrutura não muda
com frequência. Isso quer dizer que sempre que formos trabalhar com o ORM,
podemos evitar esse tipo de processamento.
precisarei cachear tudo novamente. Caso exista uma máquina “C”, terei que
fazer o mesmo processo para cachear os dados. Sempre que precisarmos ter
uma sessão de usuário, teremos que repetir esse processo, o que provavelmente
nos prejudicará. Quando precisarmos que o resultado final para o usuário seja
personalizado, se isso estiver espalhado em diversas máquinas, teremos esse tipo
de problema.
Por outro lado, temos algo que chamamos de cache compartilhado. Este cache
tem uma latência maior, pois trabalha com uma espécie de cache central. Ou
seja, os dados estarão centralizados para o uso de todos que precisarem. Mas,
por estarem centralizados, haverá uma latência maior. Para chegarmos nesse
servidor de cache, apesar de ter uma latência maior, não há duplicação do
cache. Vamos imaginar que temos duas máquinas chamadas de “1” e “2”. Pela
máquina “1” acessamos um portal e a home deste site é cacheada. Quando
acessarmos a máquina “2”, esta ainda não estará cacheada. Então, precisaremos
cachear novamente essa home. Tivemos, assim, que fazer o cacheamento duas
vezes. Se tivermos o cache compartilhado para 100 máquinas, essas 100 não
precisarão gerar cache novamente, pois o cache já estará compartilhado entre
todas. Nesse caso, percebemos a possibilidade de ter maior latência, porque
precisamos fazer uma consulta externa, porém conseguimos utilizar esse cache
em muitas máquinas. Essa é a grande vantagem: não há duplicação do cache.
Poderemos, assim, compartilhar sessões, pois sempre que o usuário fizer login,
os dados dele estarão no servidor de cache. Então, não importa qual máquina
for acessar.
Introdução à Arquitetura de Software 76
Neste tópico veremos, por meio de alguns exemplos práticos, como o Edge
Computing pode nos ajudar em relação ao cache.
A “falta” do Edge Computing nos próximos anos pode fazer com que a internet
não funcione tão bem quanto esperado, por isso ele está em evidência nos dias
atuais e a cada dia se fala mais sobre essa solução.
A Netflix pode ser um bom exemplo para que possamos compreender como
o Edge funciona. Imaginemos a quantidade de acessos que a plataforma tem
diariamente e a quantidade de tráfego que esses acessos geram. Se esses dados
estivessem em um datacenter nos Estados Unidos e os seus usuários estivessem
no Brasil, consequentemente seria necessário que o dado saísse dos EUA para
bater no Brasil. Isso, provavelmente, faria a rede de internet congestionar,
pois simplesmente teríamos uma sobrecarga em todas as máquinas da Netflix
para conseguir movimentar esses terabytes de dados. Como resultado, essa
sobrecarga se estenderia à internet de forma geral.
O Edge pode nos ajudar fazendo com que a informação do usuário esteja
Introdução à Arquitetura de Software 77
mais perto. Assim evitamos que a sua requisição trafegue mais tempo pela
internet. Fora isso, ele consegue fornecer serviços, além de simplesmente uma
CDN (Content Delivery Network), que processam informações mais próximas
possível do usuário, evitando assim que ele bata em nosso servidor.
Lembrando que a internet não é ilimitada, isto é, a rede não é ilimitada. Quanto
mais pudermos evitar que o usuário fique longe da informação, será melhor em
diversos sentidos, tanto para a rede, quanto para o próprio usuário.
Hoje, trabalhamos com CDN na Full Cycle. E, mais uma vez, para exemplo
de algo que realmente utilizamos, podemos citar uma empresa que nos oferece
esses serviços: a Akamai. Ela é uma das maiores empresas que trabalham com
Edge Computing. Para termos uma ideia, a Akamai possui mais de 500 pontos
Introdução à Arquitetura de Software 78
Quanto maior o vídeo, quando mais longo e mais longe estiver, provavelmente
teremos que fazer download o tempo inteiro. Isso fará com que nossa latência
seja maior e a chance desse vídeo começar a travar em nosso computador,
consequentemente, será maior. Quanto mais próximo esse vídeo estiver, menor
vai ser a latência e teremos mais chances de conseguir baixar esse vídeo e gerar
um cache local em nosso computador e assisti-lo de uma forma muito mais
tranquila. A CDN permite isso. É um preço que precisamos pagar, mas que
facilitará muito a experiência que teremos com nossos usuários.
Introdução à Arquitetura de Software 79
Cloudflare Workers
Hoje em dia, além dela trazer diversos serviços - inclusive de WAF (Web
Application Firewall), também podemos encontrar na plataforma os Workers.
Este é um serviço que permite que façamos deploy de aplicações. Normalmente
essas aplicações são executadas em javascript. Eles conseguiram isolar cada
requisição em um “container” utilizando a Engine V8, que é a mesma usada
pelo Google Chrome e também no Node.js. Resumidamente, podemos dizer que
a Cloudflare conseguiu criar um mini container que consegue executar de uma
forma muito rápida as requisições de forma mais próxima do usuário.
Escalabilidade
Em seu livro, Elemar Jr. nos traz a definição de escalabilidade. Ele nos diz que:
“É a capacidade de sistemas suportarem o aumento (ou redução) dos workloads,
incrementando (ou reduzindo) o custo em menor ou igual proporção.” Ou seja,
dizer que um software é escalável significa que temos o “poder” de aumentar ou
diminuir o throughput, adicionando ou removendo a capacidade computacional.
É essencial termos essa definição bem clara, pois é muito comum que exista
uma mistura de conceitos, principalmente em relação a performance. Enquanto
performance tem o foco em reduzir a latência e aumentar o throughput; a es-
calabilidade visa termos a possibilidade de aumentar ou diminuir o throughput,
adicionando ou removendo a capacidade computacional.
seus os recursos computacionais como memória, CPU, disco, etc, temos o que
chamamos de escala vertical. Por outro lado, se aumentarmos esses recursos
através do aumento de máquinas em si, temos uma escala horizontal.
Por exemplo: ao invés de usarmos uma máquina de 64gb de ram, usamos quatro
máquinas de 16gb cada. Então, nesse exemplo, aumentamos as máquinas para
facilitar nosso dia a dia. Logicamente, será necessário colocar um proxy reverso
ou um load balancer para rotear as requisições feitas por essas máquinas.
Nos próximos tópicos, falaremos sobre os pontos que devemos observar para
garantir que tenhamos uma solução escalável horizontalmente.
Introdução à Arquitetura de Software 82
Vimos que, de certo modo, é impossível permanecer escalando uma solução ver-
ticalmente. Por isso, é necessário sabermos o que fazer para permitir que nossa
aplicação escale de modo horizontal. E, para que isto seja possível, precisamos ter
atenção em alguns pontos relacionados a descentralização de dados, de estrutura,
de arquitetura, etc. Pois o modo como nosso software foi desenvolvido afeta
diretamente se conseguiremos ou não escalar horizontalmente.
Precisamos desenvolver nosso software de modo que, caso precisemos escalá-lo,
possamos aumentar a quantidade de máquinas a qualquer momento. E, caso
precisemos desescalá-lo, possamos remover essas máquinas. Então podemos
perceber que as máquinas devem ser algo “descartável”, ou seja, não devemos ter
apego a uma máquina específica. Isso quer dizer que ela precisa ser facilmente
criada e removida sempre que for necessário e sem medo algum. Precisa ser algo
natural. Assim, devemos seguir alguns guidelines.
Falar sobre banco de dados é sempre muito complexo, pois é algo que muitos
de nós desenvolvedores temos dificuldade em trabalhar. Muitas vezes exige que
tenhamos um arquiteto tecnológico - uma pessoa especialista que seja um DBA
para nos auxiliar em alguns aspectos mais técnicos. Neste tópico, abordaremos
o mínimo que precisamos saber sobre escala de banco de dados e alguns pontos
fundamentais que precisamos levar em consideração sobre esse tema.
Introdução à Arquitetura de Software 85
Fora isso, podemos pensar também em como escalar nossa solução de forma
horizontal desde seu início. Às vezes, a quantidade de leitura está tão grande
que necessitamos, desde o início, adicionar várias máquinas de leitura ou,
eventualmente, até mudar o formato do banco de dados. Começamos a trabalhar
com diversos shards por exemplo.
Hoje em dia temos diversas opções de bancos de dados, por isso vale muito a
pena compreendermos qual tipo de aplicação vamos trabalhar. Isso possibilitará
uma escolha adequada, permitindo que possamos trabalhar bastante com deter-
minado banco de dados. Por exemplo: um banco que nos possibilite relacionar e
fazer consultas muito pesadas para gerar relações, um outro que nos auxilie no
trabalho com grafos. Caso precise pegar dados que nos ajudem a evitar milhares
Introdução à Arquitetura de Software 86
Hoje em dia muitas pessoas estão trabalhando de forma serverless, isso significa
que elas estão trabalhando de modo que, basicamente, não se “preocupam” mais
com o lado de servidores. Isto é, deixam seu cloud provider trabalhar por conta
própria, porque esses sistemas, normalmente, trabalham e criam bancos de dados
específicos para escalar de uma forma muito mais tranquila. Assim, podemos ler
documentos, mas não nos preocupamos de modo geral. Vale dizer que, quando
falamos em não nos preocuparmos, estamos nos referindo a esses desafios que
são bem complicados de enfrentar. Lembrando também que serverless não
significa apenas a lambda functions da AWS.
Muitas pessoas iniciam essa escala de qualquer forma, assim, deixam de olhar os
principais gargalos. Para evitar isso, é importante termos uma APM (Application
Performance Monitoring, pois com isso conseguiremos entender todas as queries
que estão rodando. É comum ouvirmos que o banco de dados está lento, mas será
que a pessoa está trabalhando com índice da forma correta? Ou está com medo
do banco de dados ficar mais lento por causa do índice?
que é uma intenção do usuário da query que é para fazer leitura de dados. Ou
seja, separa a leitura da escrita. Falamos um pouco sobre isso em um tópico
anteriormente.
Proxy Reverso
Existem, atualmente, três soluções em proxy reverso que podem ser consideradas
mais populares e por isso, vale a pena conhecermos. O Nginx, o HAProxy (HA =
High Availabillity) e o Traefik. Definitivamente, desses três, o mais conhecido é
o Nginx. Então é interessante que saibamos configurá-lo, assim, conseguiremos
dominar diversas ferramentas que são baseadas nele.
Introdução à resiliência
O conceito de resiliência pode nos ajudar a ter uma ideia inicial do que significa
desenvolver uma solução capaz de se adaptar em diversas situações do nosso
dia a dia. Podemos dizer que resiliência é um conjunto de estratégias adotadas
intencionalmente para a adaptação de um sistema quando uma falha ocorre.
Fora isso, existe uma frase popular que também nos ajuda a compreender melhor
o conceito do que significa ter uma aplicação resiliente: “ou você dobra, ou você
quebra”.
Introdução à Arquitetura de Software 89
temos que nos preservar para que quando eles precisem de nós consigamos
responder também.
Então podemos dizer que um sistema não pode ser egoísta a ponto de realizar
mais requisições em um outro sistema que está falhando. Se temos um sistema
“A” e precisamos de uma informação do sistema “B”, mas por algum motivo
ele demora a nos responder e, depois disso, precisamos fazer outra pergunta e
novamente ele demora, se ao invés de esperar um pouco mais, mandarmos a
pergunta por várias vezes seguidas, esse sistema provavelmente sairá do ar. A
consequência disso é que, com esse sistema fora do ar, todo ecossistema ficará
comprometido. Se estava difícil termos nossa resposta, agora ficou mais difícil
ainda. Por isso, um sistema não deve ser egoísta e enviar várias requisições segui-
das assim. Em relação a tudo isso, o que queremos destacar aqui é a importância
de existir harmonia entre os sistemas. Afinal, sempre vamos depender uns dos
outros em algum momento.
Um sistema lento no ar, muitas vezes, é pior do que um sistema fora do ar, pois
isso gera algo que chamamos de efeito dominó. Imaginemos a seguinte situação:
chamamos o sistema “A”, que chama o sistema “B”, que chama o sistema “C”.
Por algum motivo, o sistema “C” está lento, por isso o sistema “B” ficará travado
esperando sua resposta. Sabemos que nosso sistema “A” está dependendo do
sistema “B”. E quanto mais requisições chegam, terá um momento que o sistema
“B” não irá aguentar mais recebê-las por causa do “C”. E, nessa situação, o “A”
também poderá travar. No final, isso pode fazer com que todos os sistemas caiam.
Introdução à Arquitetura de Software 92
Assim, por vezes, seria melhor que o sistema “C” estivesse fora do ar. É melhor
dizer que não está aguentando mais lidar com tantas requisições do que não
retornar as respostas.
Podemos perceber que essa dificuldade não está relacionada a programação. Não
é sobre ser um bom programador de java ou de .net. O que estamos querendo
repassar são conceitos que nos farão trabalhar com excelência, por exemplo, em
um mundo distribuído.
Health Check
O trabalho com health check nos possibilita saber como está a saúde do
nosso software. Assim, conseguimos responder aos outros sistemas se temos
ou não condições de receber mais requisições. Essa é uma forma de fazer uma
checagem de saúde em nossa solução. Por ser um termo muito conhecido, é bem
provável que muitos de nós já tenhamos ouvido falar no trabalho com uso das
regras de health check. Apesar disso, é importante sempre vermos/revermos as
Introdução à Arquitetura de Software 93
possibilidades de verificação dos sinais vitais da nossa aplicação. Sem isso não é
possível saber como está a saúde de um sistema.
Um sistema que não está saudável possui uma chance de se recuperar caso o
tráfego pare de ser direcionado a ele temporariamente. Vamos imaginar que
temos um sistema que tem muito tráfego, quando ele tenta fazer uma consulta
ao banco de dados, tem um retorno muito lento. Depois disso, acaba travando.
Isso fez com que ele começasse a sobrepor várias requisições. Ele não consegue
Introdução à Arquitetura de Software 94
mais lidar com todas essas requisições, por isso continua muito lento, até que
em certo momento passa a dar timeout. Durante esse processo as requisições
continuavam chegando, o que prejudicava ainda mais o funcionamento desse
software. Por outro lado, vamos imaginar que os outros sistemas pararam de
mandar requisições assim que ele começou a ficar lento. Provavelmente, ele
iria pegar todas as requisições travadas, em algumas daria timeout e as que
restassem poderia começar a processá-las até ficar 100% novamente. Quando
isso acontecesse, passaria a receber novas requisições.
Muitos trabalham por padrão com health check da seguinte forma: o dev coloca
um “health” e a cada 10 segundos manda um ping acessar aquela URL. Caso esta
retorne, o dev chega à conclusão que o software está retornando com qualidade.
O problema é que existe uma diferença muito grande entre acessarmos uma
URL que retorna somente o HTML, de acessarmos uma que retorna uma URL
que pega a média do tempo das últimas requisições e faz uma consulta no banco
de dados. Pois toda vez que criamos uma URL para verificar a saúde do nosso
sistema, essa saúde não pode ser medida somente pelo arquivo de HTML, já que
se tiver um Nginex na frente, sempre terá um retorno incorreto, porque é muito
difícil que o Nginex caia. Assim, é importante criarmos esse arquivo de forma
Introdução à Arquitetura de Software 95
Rate limiting
Rate limiting é uma estratégia que protege o sistema de acordo com o que ele
foi projetado para suportar. Também é um ponto que se relaciona diretamente
com a resiliência da aplicação. Normalmente quando subimos um sistema no
ar temos uma ideia de quanto de requisições ele pode aguentar. Caso não
tenhamos, é recomendável fazermos um teste de stress. Além disso, podemos
ver na empresa quanto de orçamento em relação a quantidade de máquinas
está liberado para nossa solução. Mas é importante sabermos esse limite, pois
senão teremos complicações em nosso trabalho. Então, é essencial buscarmos
essas informações antecipadamente, antes mesmo do problema acontecer. Ao
sabermos esse limite, podemos, então, trabalhar com rate limiting. Assim, se o
sistema consegue responder 100 requisições por segundo, essa será a regra, esse
será o número que vamos trabalhar na estratégia.
No rate limiting podemos dizer que determinado sistema vai aguentar 100
requisições por segundo, passando disso retornará um “erro 500”, por exemplo.
Então o sistema poderá trafegar com qualidade, ou um nível de qualidade
mínima, até o ponto determinado, pois acima desse ponto começará a atrapalhar
os outros sistemas.
Para compreendermos como tudo isso funciona, vamos imaginar uma situação
hipotética: temos um cliente que utiliza nossa API e esse cliente faz em média
Introdução à Arquitetura de Software 96
Circuit breaker
Para compreendermos melhor como isso tudo funciona, vamos imaginar que
temos um circuito elétrico com um disjuntor em nossa casa e este disjuntor
vai servir para abrir o circuito caso venha uma sobrecarga. Assim, ao invés da
corrente elétrica continuar passando - o que poderá queimar nossos eletrodo-
Introdução à Arquitetura de Software 98
mésticos -, ele abrirá o circuito, fazendo com que essa corrente pare ali.
API Gateway
acontecendo em nossa aplicação.Ou seja, ela pode aplicar, logo na entrada, re-
gras, políticas, plugins etc. Assim, consegue perceber as necessidades individuais
de cada serviço. Baseado nisso, pode rejeitar, ou tomar diversas decisões que
favoreçam as aplicações.
Quando falamos em resiliência, esta solução nos ajuda a evitar uma série de
situações que poderiam prejudicar nossa aplicação. Por exemplo, imagine que
temos um sistema em que o usuário precisa ser autenticado para acessá-la.
Digamos que alguém crie um robô para “bater” nessa aplicação repetidamente,
nosso servidor tentará fazer a autenticação das requisições feitas por ele. Ou seja,
ao detectar usuário e senhas diferentes, nosso software vai retornar um “não”.
Isso, feito várias vezes, fará com que esse serviço processe inúmeros pedidos,
o que provavelmente prejudicará seu funcionamento. Em uma situação dessas,
a API Gateway tem condições de validar da seguinte forma: se alguém está
batendo em nossa máquina precisa fornecer um token JWT para ser autenticado,
por exemplo. Assim, se ele não conseguir ser validado logo na API, não passará
nem desta portaria. É como se morássemos em um condomínio fechado. Então,
se alguém quiser bater a campainha da nossa porta, ele terá que passar primeiro
pela portaria. De modo semelhante, o usuário terá que passar pela API Gateway
caso queira acessar nossa aplicação.
Tem sido cada vez mais comum ver empresas utilizando API Gateway. Pois as
grandes APIs do mercado tem recursos e plugins que auxiliam muito no dia a
dia das aplicações. Um bom exemplo de uma API famosa é a Kong. Ela pode
Introdução à Arquitetura de Software 100
Com os diversos plugins que a API Gateway nos oferece, podemos trabalhar com
rate limiting e health check. Ou seja, conseguimos dizer para a URL receber até
100 requisições por segundo, sendo que seriam reservadas 50 requisições para
os usuários autenticados e os restantes dos usuários ficariam com as outras 50.
Assim, conseguimos trabalhar com limites e prioridades. Além disso, é possível
fazermos a verificação da saúde de forma ativa para que possamos perceber
claramente se aquela aplicação está saudável. Isto é, a própria API Gateway faz
o apontamento da saúde da aplicação e retorna um “erro” para quem estiver
chegando, caso seja necessário. Por outro lado, se a solução estiver saudável,
coloca o usuário dentro do sistema.
Então, podemos perceber que recursos como esses podem facilitar nosso dia
a dia. A API Gateway tem tantas funcionalidades que é necessário tomarmos
cuidado para que ela não aplique, involuntariamente, regras de negócio. Por
exemplo, podemos colocar um plugin para que todas as vezes que recebermos
um XML, este seja transformado em um JSON, caso nosso programa não
consiga trabalhar com esse XML. Ou, eventualmente, se tivermos uma Lambda
Function e queremos que, quando o usuário acesse a “axpto.com.br/produtos”,
seja executada uma Lambda na AWS. Então, a API Gateway consegue fazer
esse tipo de tarefa.
Introdução à Arquitetura de Software 101
Service mesh
mesh toda comunicação de rede é efetuada via proxy. Assim, tudo que estamos
passando na rede consegue ser controlado e medido. Conseguimos pegar os
dados, saber quem manda/recebe as informações, como e o quanto de tempo
essa informação é processada, etc. É extremamente interessante saber isso tudo,
pois assim conseguimos entender o comportamento da nossa rede e controlá-la.
Ao fazermos isso, podemos dominar tudo o que está acontecendo.
Ainda sobre comunicação entre os sistemas, vamos imaginar algumas ações que
precisaremos fazer em nossa aplicação em algum momento. Primeiro vamos nos
lembrar do rate limiting. Como poderíamos fazer sua implantação? A resposta
é que teríamos que instalar uma biblioteca ou criar uma implementação em
nossa aplicação. E, nessa aplicação, guardaremos, no banco de dados, quantas
requisições estamos recebendo por segundo. Além disso, teríamos que separar
essas requisições por cliente, para ver quando seria necessário negá-las. Vamos
nos lembrar também de quando fazemos uma requisição e o sistema não nos
retorna. Nesta situação, precisamos fazer um processo chamado de retry, ou
seja, tentar novamente para verificar se aquele sistema estava fora do ar. Uma
alternativa de como fazer isso seria realizarmos algumas tentativas em nossa
biblioteca, uma vez, duas vezes, três vezes… ou quantas vezes forem necessárias.
Outra situação para pensarmos é na implementação do circuit breaker em nosso
projeto. Primeiro, seria necessário medir mais ou menos a saúde da nossa aplica-
ção. Depois, quantas requests ela está recebendo por segundo. A partir dessas
informações podemos começar a negar, ou seja, abrir o circuito. Com esses
apontamentos, podemos perceber que existem diversos comportamentos que
Introdução à Arquitetura de Software 103
A service mesh provê uma forma de conseguirmos olhar tudo o que está
acontecendo de comunicação entre seus sistemas. Além disso, possibilita aplicar
alguns comportamentos como rate limiting e circuit breaker direto na rede,
porque teremos acesso aos proxies. Então, se quisermos fazer um circuit breaker
antes da requisição bater em nossa solução, com uma mesh ela baterá em
nosso proxy. O nosso proxy saberá que estamos ruins, assim, abrirá o circuito,
impedindo que a requisição bata em nossa aplicação.
grandes seria uma tarefa muito complicada, porque trabalhamos com milhares
de microsserviços. Com uma service mesh, conseguimos isso imediatamente e
com poucas configurações. Então, é essencial considerarmos a importância de
conhecer ao menos o básico sobre essa solução. É importante dizermos que nosso
objetivo, neste curso, não é infraestrutura, por isso nossa finalidade não é fazer
com que os devs compreendam tudo sobre rede. Por outro lado, é essencial que
todos nós saibamos que existem tecnologias e soluções que podem suprir, muitas
vezes, papéis que em tese nós pensávamos ser da pessoa desenvolvedora, mas
que com os recursos que temos hoje não precisa mais ser.
chegando, é necessário que esses clientes fiquem em espera para fazer o seu
pagamento. Então, podemos perceber que entrar em uma fila para aguardar
uma solução é algo que já fazemos em diversas situações de nossas vidas,
em diversas ocasiões não temos nossos problemas resolvidos instantaneamente.
Porém, quando estamos trabalhando em sistemas, nós acabamos não pensando
dessa forma.
Caso uma aplicação esteja recebendo 100 requisições por minuto, quando
aguentaria somente 50 requisições nesse tempo, essas que estão sendo enviadas
a mais ficarão travadas e, eventualmente, perderemos uma ou outra requisição.
Isso não faz sentido, porque aquela pessoa que nos enviou a requisição pode
não estar esperando a resposta exatamente naquela hora. Mesmo que ela
tenha enviado logo a requisição, provavelmente poderia esperar. Porém, nós só
estamos dando uma opção, ou seja, se alguém quer nos mandar uma requisição
vamos responder imediatamente e, como não conseguimos fazer isso, preferimos
perder a requisição.
Exponential backoff
Isso nos mostra que todas as vezes que trabalhamos com políticas de retry é
extremamente válido inserirmos o jitter, ou seja, vamos adicionar algoritmos de
ruídos para que nossa requisição não seja exatamente igual a dos outros clientes.
Assim, o sistema não terá que lidar com requisições simultaneamente, tendo
Introdução à Arquitetura de Software 113
Então, caso não sejamos respondidos na primeira vez que solicitarmos, com essa
estratégia temos mais chances de alcançarmos o resultado esperado em nossa
segunda tentativa. O que queremos destacar, neste tópico, é a necessidade de
sermos espertos em nossas novas tentativas. Porém elas precisam ter lógica, os
números colocados não são meros palpites.
estamos enviando para o kafka, por exemplo, está chegando até ele e ter certeza
de que a requisição não será perdida no meio do caminho. Normalmente, gosto
de “brincar” que cada transação que envio tem o valor de 1 milhão de dólares,
então, não posso perdê-la de forma alguma. Porém, quando se trata de resiliência,
tudo tem um lado bom e um lado não tão bom. Veremos isso nos próximos
parágrafos.
Partindo do princípio de que não sabemos muito sobre o Apache Kafka, vamos
imaginar que mandaremos uma mensagem para que essa solução guarde até que
o outro sistema possa processá-la. Quando trabalhamos com alta disponibilidade
teremos um cluster, isto é, um conjunto de brokers. Nesse caso, falando especifi-
camente do Kafka, teremos o broker “A”, “B” e o “C”. A mensagem que enviamos
cairá em um broker “A”, que é chamado de líder. Sabendo disso, temos alguns
pontos para pensarmos em relação à garantia de entrega. Primeiro, podemos
optar por não ter uma confirmação de entrega, logo não temos certeza de que
nossa mensagem foi recebida. Podemos escolher essa opção dependendo do nível
de importância da mensagem.
temos essa opção quando enviamos uma mensagem ao nosso broker: ter ou não
a garantia de entrega.
Vamos imaginar, ainda, que estamos trabalhando com o Uber. A cada momento
o aplicativo nos mostra a posição do motorista e do passageiro. Mas o que
aconteceria se perdêssemos algumas dessas posições? Seria um problema gra-
víssimo para o negócio? Acreditamos que não. O aplicativo pode nos mostrar
o máximo de posições possíveis, ou seja, mesmo que algumas posições sejam
perdidas, o mais importante é que sejam mandadas o maior número possível.
Como não pedimos uma confirmação, esse uber fica mais rápido. Isso acontece
porque só está recebendo, sem necessitar pausar para confirmar que recebeu.
Logo, podemos perceber que todas as vezes que não pedimos a confirmação de
entrega ganhamos velocidade. Porém, existe a possibilidade de perdermos uma
mensagem ou outra. Podemos chamar isso de fire-and-forget, isto é, disparamos,
esquecemos e torcemos para que a mensagem seja entregue.
Existem alguns casos em que não podemos trabalhar desse modo, pois precisa-
mos, necessariamente, ter a certeza de que o broker recebeu a mensagem. Isso
pode acontecer porque a mensagem é muito importante, por exemplo 1 milhão
de dólares. Neste caso, devido à sua importância, precisamos ter a confirmação
de que o broker líder realmente recebeu a requisição. Mandamos a solicitação e
o líder nos retorna um Ack1, ou seja, a confirmação que precisamos ter.
Porém, vamos supor que temos 3 brokers e essas informações ficam replicadas,
assim, caso um caia o outro poderá assumir. Digamos que nós enviamos uma
Introdução à Arquitetura de Software 116
solicitação para o broker “A” e ele nos deu um retorno de recebimento, neste caso
ficamos tranquilos. Mas em seguida, por algum motivo, esse broker caiu. Sem
saber da queda, pensaremos que a mensagem está segura, porém, por alguns
momentos nossa mensagem não teve alta disponibilidade. Isso pode fazer com
que percamos alguma mensagem.
Essas observações não são apenas para o kafka pois existem outros sistemas que
trabalham de forma similar. Essa solução foi utilizada apenas para termos um
exemplo prático. Diante de tudo isso, o que queremos destacar é que precisamos
ter uma noção do nível que as coisas chegam em relação a garantias quando
trabalhamos de forma assíncrona. Isso porque não se trata apenas de mandar
Introdução à Arquitetura de Software 117
Digamos que nós temos um broker que apoia nossa aplicação. O que aconteceria
Introdução à Arquitetura de Software 118
Nos dias atuais muitas empresas estão trabalhando com multi-cloud. Muito
provavelmente isso não se dá apenas por uma questão de custos. Está relacionado
à segurança, resiliência e disponibilidade. Então, o que queremos destacar
é que sempre terá um limite para setar nossa resiliência. Ou seja, quanto
mais resiliência, mais esforço e mais caro. Todavia, podemos dizer que não é
responsabilidade do desenvolvedor definir qual é o nível de resiliência, muitas
vezes essa decisão precisa ser estratégica da empresa, pois em níveis elevados,
saberão quais riscos a organização está disposta a ter para o negócio. Diferente de
decisões que envolvam comunicação entre sistemas, perda de dados, tentativas
de retry. Estas são responsabilidades dos devs.
Para pensar em resiliência terá que ser definido os custos, a necessidade de mais
mão de obra, etc. Isso significa que essas decisões são mais alto nível por envolver
dinheiro, especialidades, etc. Assim, será avaliado, como já dissemos, o custo
benefício relacionado à necessidade de ter um sistema resiliente.
Sistemas Monolíticos
Sistemas “tradicionais”
Com isso em mente, já é possível termos ideia de outras áreas que poderiam
existir dentro desse sistema, como um catálogo para exibição dos produtos,
evoluindo assim para uma loja virtual.
Podemos utilizar plataformas para desenvolver tal loja como um Magento por
exemplo.
Restrições
Além disso, temos que concordar que ao colocarmos, cem, duzentas, ou mesmo
mil pessoas para trabalhar na mesma base de código pode ser em determinadas
situações algo caótico.
Sistemas Monolíticos 122
Apesar das restrições citadas acima, trabalhar com sistemas monolíticos não é
nenhum demérito ou atestado de obsolescência.
Sistemas monolíticos na maioria das vezes sem dúvidas é a melhor opção para
grande parte das empresas. Afinal de contas, nem toda empresa possui 6000 devs
como Mercado Livre.
Martin Fowler em seu artigo MonolithFirst¹ faz duas grandes observações logo
no início:
Deploy
Necessidade de escala
Débitos técnicos
Por outro lado, isso não significa que pequenos sistemas não possuem ou não
possuirão débitos técnicos. Todavia, como a base de código é limitada, tais
débitos não terão tanta influência na solução como um todo.
Domain Driven Design
Introdução
Isso tudo é muito estranho porque de fato o DDD parece complexo quando
nós consultamos as principais literaturas a respeito dele. E mesmo quando
pesquisamos, isso ainda nos deixa dúvidas sobre como nós podemos colocar isso
em prática no nosso dia a dia.
Nesse capítulo não vamos focar apenas nos aspectos práticos porque o DDD vai
Domain Driven Design 127
muito além disso. O seu foco é conhecer não apenas o ambiente, mas também
os contextos e as pessoas que trabalham em um projeto. E ainda, baseado nisso,
permitir uma separação que faça sentido para a organização em si.
Não basta começar diversos projetos e dizer que estamos aplicando o DDD em
tudo, sendo que no final das contas isso resulta em diversas pastas repetidas em
diversos projetos.
O intuito deste capítulo é fazer com que essa filosofia de trabalho mude a sua
forma de pensar em software, principalmente no seu trabalho com projetos de
grande porte. Normalmente não aplicamos isso em pequenos projetos porque
o DDD é fundamentalmente utilizado quando nós não temos clareza total do
projeto e suas áreas.
O DDD é sem dúvidas um recurso que pode nos ajudar com esse objetivo e, com
a sua devida aplicação, desenvolver software se tornará mais divertido e com
menos riscos.
Domain Driven Design 128
Agora iremos explorar a filosofia e os conceitos teóricos que estão por trás do
DDD, considerando que ao termos mais entendimento desses pontos, facilitará
o processo de aplicar DDD na prática.
Não pense apenas sobre os design patterns, pastas dentro do seu projeto, entre
outros, porque o DDD foca muito mais em como modelar o software do que
desenvolvê-lo em si.
É importante termos isso em mente porque o DDD é um assunto que oscila muito
durante os anos. Ora é muito falado, ora não. Atualmente, com a importância
dos microsserviços, o DDD também tem destaque porque o grande desafio de
trabalhar com microsserviços é modelar o software e os seus contextos.
Domain Driven Design 129
Quando observamos o livro de Eric Evans, é notável que existe um lado filosófico
em torno dele, que chega ser até mais importante do que os padrões de projeto
que utilizamos em nosso dia a dia.
Essa filosofia parte de uma visão madura para que o desenvolvedor trabalhe em
seu projeto com orientação de trazer soluções para problemas complexos. Não
podemos ser inocentes ao ponto de ver um projeto e pensar somente no banco
de dados, cadastros, CRUDs, entre outros.
Foram através desses conceitos iniciais de Evans que surgiram entusiastas que
tiveram mais clareza nas falhas que existiam em seus grandes projetos.
importantes de fora.
A vantagem de ler esse livro são os tópicos que vão direto ao ponto, que
desmistificam grande parte do DDD de uma forma menos densa.
As complexidades de um software
São softwares que podem ser adaptados a qualquer tipo de negócio sem custo-
mização.
O DDD nos deixa claro de que em grandes projetos há muitas áreas, regras de
negócio e pessoas com diferentes visões da organização que estão situadas em
diferentes contextos.
Vamos usar como exemplo uma empresa que seu “core business” é fazer
cobranças de contas em aberto em nome de diversas corporações. Essa operação
envolve atendentes de telemarketing que usam um software de discagem auto-
mática. Se pensarmos bem, com certeza existe um diferencial na automatização
desses processos de cobrança em relação ao de uma empresa “tradicional”, que
liga para cobrar boletos bancários em aberto de seus clientes.
Os bancários, por exemplo, podem usar o termo ‘francesinha’, que é o nome que
eles dão para um tipo de relatório de quem realizou pagamentos. Mas quando
esse termo é mencionado para funcionários de outro departamento, isso pode
não fazer sentido nenhum.
Quando percebemos isso, é possível ter mais clareza para entender que o
Domain Driven Design 132
software não é apenas uma simples unidade. Ele é feito de contextos, regras,
implementações que possuem objetivos diferentes.
Em torno de uma solução há política, pessoal e cultura. Tudo isso deve ser
levado em consideração. Se não levarmos isso em conta, sem dúvidas o projeto
já fracassou em seu primeiro dia de desenvolvimento.
Seja qual for a sua experiência trabalhando em grandes projetos, você já deve
ter visto um projeto falhar devido a alguns pontos que foram citados até aqui.
É notável que grande parte da complexidade desse tipo de software não vem
da tecnologia; mas da comunicação e separação de contextos que envolvem o
negócio por diversos ângulos.
Nós perguntamos como as coisas devem ser feitas, seguindo as instruções que
nos foram dadas. E de repente o responsável pelo produto diz que quer um
resultado diferente.
Domain Driven Design 133
De forma geral o Domain Driven Design vai te ajudar a ter uma visão ampla
do problema a ser resolvido e a quebra-lo em problemas menores. Também
ele te dará técnicas de como minimizar ruídos de comunicação entre todos
os envolvidos, bem como trabalharmos com patterns que visam deixar nossas
aplicações cada vez mais desacopladas preservando ao máximo suas regras de
negócio.
Resumindo
Nos tópicos anteriores, falamos sobre Domain Driven Design de maneira in-
trodutória, mas daqui para frente queremos explicar alguns aspectos essenciais
Domain Driven Design 134
Domínio
Subdomínios
Linguagem universal
O DDD também estabelece uma linguagem universal entre todos os que estão
envolvidos no projeto. A “Ubiquitous Language”, ou Linguagem Ubíqua, é um
termo recorrente em qualquer livro sobre DDD.
Todo esse problema ocorre porque nós não conseguimos ter uma única lin-
guagem universal dentro da empresa. Por mais estranho que pareça, você vai
perceber que a empresa é composta por uma cultura que é modificada e adaptada
aos poucos dentro de cada departamento.
E como cada área tem o seu próprio jargão, para um funcionário na área de
vendas pode existir um cadastro de clientes que fecharam contratos com ele.
Já no departamento de de compras também existe uma área para cadastro de
clientes, porém nesse caso o cliente é a própria empresa, pois ela possui diversas
filiais.
Por isso é importante entender, mapear e extrair essa linguagem universal para
esclarecer e minimizar os principais ruídos de comunicação.
Agora que tivemos uma idéia geral sobre DDD, a seguir entenderemos os
princípios básicos relacionados aos domínios e subdomínios como elementos
fundamentais do Domain Driven Design.
Domain Driven Design 138
Delimitação
E é assim que conseguimos ver, nós percebemos que existem partes que nós
podemos separar e é por isso que essas partes são chamadas de subdomínios.
Mas na separação dos subdomínios também percebemos que eles possuem graus
diferentes de importância para o negócio.
Domain Driven Design 139
Por outro ângulo, ainda observando através da nossa lanterna, nós também
temos alguns pontos importantes para definir.
Domínios de Suporte
Por outro lado, vamos imaginar que temos outro quadrado. Neste segundo,
poderemos entender o problema e organizá-lo para que possamos encontrar
possíveis soluções. Chamaremos este segundo ambiente de espaço da solução.
Assim, podemos separar o domínio e suas complexidades para fazermos a
modelagem desse domínio. Então, se no espaço do problema temos o domínio de
forma geral, no espaço da solução temos tudo para resolvê-lo. Lembrando que, ao
falarmos sobre DDD, um dos grandes pilares é conseguir fazer essa modelagem
de domínio. Pois o domínio é o problema do negócio e a solução é conseguir
modelar esse domínio para desenvolver a aplicação de maneira sustentável.
Contexto delimitado
Então, todas as vezes que começamos a falar sobre DDD, estamos fazendo
uma exploração do domínio para conseguirmos iniciar sua modelagem. Essa
modelagem, no final das contas, sempre será o entendimento do problema do
subdomínio. Normalmente esses subdomínios vão se tornar contextos delimita-
dos, isto é, será o local que verificaremos os problemas específicos para resolvê-
los.
linguagem que é utilizada dentro do modelo é um dos grandes indícios que nos
possibilita perceber em qual contexto estamos.
Contexto é rei
Elementos transversais
Por exemplo, vamos imaginar que temos um “cliente” que está em duas áreas:
na área de vendas de ingresso e na área de suporte ao cliente. Nesse caso,
teremos o mesmo cliente em contextos diferentes. Mas é possível percebermos
uma correlação entre essas áreas. Quando trabalhamos com cliente em venda
de ingressos, estamos preocupados com o evento, com o ticket local e com o
vendedor. Por outro lado, quando trabalhamos com cliente na área de suporte,
estamos preocupados com o departamento que vai ser atribuído para dar o
suporte, com o responsável pelo retorno ao cliente, com o ticket e com as
dúvidas dos clientes. Assim, podemos perceber o quanto a perspectiva muda
quando mudamos de contexto. Isso pode gerar uma confusão enorme na cabeça
das pessoas que vão desenvolver a solução. Pois nós, desenvolvedores, temos a
tendência de pensar que tudo é a mesma coisa, com a mesma perspectiva. Isto é,
Domain Driven Design 147
Ainda com o exemplo da palavra cliente, imagine que precisaremos modelar uma
entidade. Primeiro criamos uma classe de clientes, então vamos criar o ID e o
nome do cliente. Depois disso, vemos que o cliente vai poder comprar ingressos.
Logo, colocamos da seguinte forma no código, “ticket: locais que esse cliente
comprou, vendedores que já venderam para ele, os eventos que esse cliente já
fez” . Deste modo, modelamos a área de vendas. Porém, ao percebermos que
esse cliente pode ter ticket de suporte podemos colocar “ticket: a dúvida que o
cliente abriu, departamentos que colaboraram para ele e quais os responsáveis
pelo suporte dado ao cliente”. É possível percebermos que a classe de cliente
ficou enorme, pois ela está tentando atender diversos contextos onde o cliente
existe. Podemos concluir, então, que isso é uma “loucura” já que o cliente pode
participar de diversos contextos. Imagine a pessoa desenvolvedora ter que criar
apenas um arquivo para modelar tudo isso como uma coisa só.
Assim, quando temos contextos diferentes, mesmo que a entidade seja a mesma,
precisamos modelá-la de acordo com aquele contexto. Devemos fazer isso
mesmo em um sistema monolítico, pois se não delimitarmos a aplicação, nosso
arquivo irá virar um “monstro”. Então, mesmo que tenhamos um único cliente, é
extremamente necessário nos atentarmos a essas delimitações, pois caso precise
quebrar a solução em microsserviços teremos que reescrever tudo, por termos
uma única classe que está servindo para todos os lados do sistema. Ter essa ideia
de perspectivas diferentes faz muita diferença ao desenvolvermos um sistema.
Domain Driven Design 148
Podemos colocar essas ideias, até aqui teóricas, em prática através de um mape-
amento de contexto. Com esse recurso, fazemos uma modelagem estratégica das
parte do domínio de nossa aplicação. Dedicaremos um tópico para explicarmos
como isso funciona.
Visão estratégica
Por meio de um context map podemos conseguir ter essa visão. Isso acontece
pois, utilizando esse recurso, conseguimos mapear nossos contextos e, desse
Domain Driven Design 149
Para entendermos como isso funciona na prática, vamos imaginar que temos
um ambiente de um negócio de vendas onde colocaremos todos os nossos
contextos. Neste ambiente, teremos a modelagem do nosso domínio com seus
contextos delimitados. Podemos deduzir que o “core business” é a área de venda
de ingressos online. Mas, além dessa área, teremos também a de suporte ao
cliente, a de vendas de ingressos offline (através de parceiros) e a de pagamentos.
É importante dizermos, antes de prosseguirmos com o exemplo, que a área de
vendas online tem peso diferente da área de vendas offline para o negócio. A
primeira seria responsável por 80% do funcionamento do negócio. A segunda
seria algo extra, fruto da parceria com shoppings, lojas, casas noturnas, etc.
Porém, as duas são partes essenciais para o funcionamento do negócio.
Então, ao vendermos ingressos online, criamos uma API para que o sistema do
shopping possa consumir. Mas o sistema de vendas online também irá consumir
a API de quem está vendendo offline. Logo, podemos dizer que, nessa parceria,
um contexto consome do outro.
O suporte ao cliente também pode ter uma relação de cliente e fornecedor, onde
a área de vendas de ingressos (em que os clientes são gerados) pode fornecer
informações para que a área de suporte ao cliente funcione. Desse modo, a área
de vendas pode ser um upstream e a área de suporte um downstream. Isso vai
depender de como a solução está sendo modelada.
É importante dizermos que não há uma regra específica que diga o certo e o
errado em relação a esse tema, pois tudo vai depender muito de como a empresa
funciona. Por exemplo, se a área de vendas usar a de suporte, teríamos uma
situação invertida em relação à que apresentamos anteriormente. Então tudo
depende de como vai ser a dinâmica do negócio. O que precisamos compreender
é que pode existir relações de parceria, chamada de shared partnership, assim
como pode existir também relações cliente e fornecedor entre os contextos.
em seu sistema por nossa API trabalhar de modo diferente. O mais provável é
que tenhamos uma relação conformista com uma empresa desse porte, a não
ser que sejamos tão grandes quanto o Itaú. Neste caso, podemos conversar com
o banco para que eles criem algo personalizado que nos atenda. Assim, dessa
última maneira, a relação não será conformista.
É perceptível que, quanto mais conformista é essa relação, temos tendência a nos
amarrar em outro sistema. Por exemplo, se usamos um sistema CRM de terceiros,
quanto mais o utilizamos, mais informações nossas esse sistema possui. É uma
relação conformista e raramente o modo de trabalho poderá ser alterado com
facilidade.
como se fosse um adaptador que nos ajuda a minimizar esse problema que
vem dos relacionamentos conformistas, pois uma vez que temos uma camada
anticorrupção, ficará mais fácil conseguirmos realizar trocas de fornecedores.
Fora esses padrões, temos outros exemplos como, o Open host service, um
padrão em que um contexto vai fornecer um serviço que estará disponível
com determinado protocolo, como um GRPC; o Published language, onde a
linguagem faz total diferença na hora que vamos nos comunicar; o separate
ways, em que os contextos delimitados não vão mais se comunicar e cada um
mantém seu próprio padrão como também o Big Ball of mud, um sistema muito
comentado em livros por ter várias coisas misturadas e, por isso, torna-se comum
termos que lidar com ele no dia a dia.
Por vezes, é possível que esses nomes tragam algum tipo de complicação nas
relações, principalmente quando vamos fazer um contexto mapping. Então,
gostaríamos de indicar um projeto no Github chamado de DDD Crew. Você
pode acessá-lo aqui: https://github.com/ddd-crew/context-mapping
Dentro desse projeto, na parte de context map, conseguimos ver uma cheat sheet,
exemplos de diversos padrões com uma imagem representando cada tipo de
relação. Por exemplo, temos uma imagem que nos mostra o resumo do que é o
Open Host Service, isto é, um Bounded Context que oferece a definição de uma
série de serviços que serão expostos para outros sistemas. Além dessa imagem,
vemos uma que resume o customer/supplier, em que o primeiro é o downstream
e o segundo é o upstream. Assim, conseguimos ver a explicação de cada um dos
padrões.
Domain Driven Design 156
Podemos ver também, mais abaixo, quais são os tipos de relações, por exemplo
se os contextos são mutuamente dependentes, se são free ou upstream e
downstream.
Além disso, nesse site temos a possibilidade de acessar uma versão read only
para utilizarmos no Miro. Assim, podemos criar novos documentos e utilizar
todos os padrões para fazermos o mapeamento de nossos próprios projetos.
Arquitetura Hexagonal
forma geral. Fazemos tudo baseado na frase “nossa função, como dev, é resolver
problemas através do código”. De certo modo, podemos afirmar que essa frase
é muito “rasa” para descrever nossa função, porque existe muita complexidade
quando desenvolvemos um sistema. Inicialmente até teremos uma complexidade
de negócio, isto é, relacionada diretamente ao problema que estamos sendo
pagos para desenvolver, mas a criação de um sistema envolve também as
complexidades técnicas que nós mesmos adicionamos para resolver o negócio.
Entretanto, é possível afirmarmos que o problema de negócio é essa complexi-
dade que dizemos ser inevitável, pois estamos sendo pagos especificamente para
resolvê-la. Por outro lado, não devemos misturá-la com a complexidade técnica.
Dizemos que essa complexidade são os aparatos técnicos que iremos utilizar para
resolver o problema do negócio. Então, conseguimos perceber claramente que
existem duas complexidades quando vamos desenvolver uma solução. Temos a
complexidade de negócio e a técnica. Quando temos clareza sobre a existência
desses dois tipos de complexidades, nosso trabalho flui de maneira mais tran-
quila. Por exemplo, é comum misturarmos regras de negócio, banco de dados,
forma de comunicação etc. por não sabermos com qual complexidade estamos
trabalhando ao digitar determinado arquivo. Fazendo isso, nós esquecemos
que nossa principal função é proteger o negócio. Isso porque, colocar limites
muito claros entre as complexidades evita que a complexidade técnica invada o
negócio.
Ainda neste capítulo, nossos estudos serão direcionados para que possamos
compreender cada uma dessas complexidades. Pois quando trabalhamos com
arquitetura hexagonal, naturalmente separamos o negócio do técnico. Fora isso,
veremos também que essa breve introdução foi necessária para que nossa com-
preensão seja melhor quando falarmos, especificamente, sobre essa arquitetura.
Uma vez que arquitetamos nosso software, temos como resultado a criação de
um “lego”, isso mesmo quando fazemos uma arquitetura mais abrangente, como
microsserviços; ou quando trabalhamos pontos mais específicos de design do
software. Quando as partes do nosso sistemas são “legos”, conseguimos trocá-
las sem quebrar a estrutura desse software. Mas isso só é possível se tivermos
uma aplicação bem desenhada.
Um projeto de software tem diversas fases em seu ciclo de vida. Nos próximos
tópicos, iremos descrever dez etapas que envolvem um projeto sem sustenta-
bilidade. É importante dizermos que, com esse exemplo, não queremos julgar
o trabalho de nenhum desenvolvedor. Ou seja, mesmo que o sistema não seja
sustentável, não podemos afirmar que a pessoa desenvolvedora fez necessari-
amente uma programação ruim. Assim, reforçamos que é muito comum uma
aplicação não se sustentar ao longo do tempo, ainda que a pessoa desenvolvedora
escreva bem. Por outro lado, isso pode significar que ela tomou más decisões
de arquitetura. Então, mesmo que ela escreva bem, por dentro o desenho pode
estar mal feito. E conforme o tempo passa é necessário usar uma “borracha”
na aplicação, fazendo com que existam várias marcações que geram diversos
problemas na estrutura do sistema. Visualizar essas fases pode nos ajudar a
compreender como tudo isso acontece na prática. O objetivo é refletirmos
sobre nossas decisões, para que possamos desenvolver aplicações totalmente
sustentáveis.
Arquitetura Hexagonal 163
Fase 1
Fase 2
Fase 3
Nesta fase, o sistema começa a ter mais acessos, por isso precisamos fazer
Upgrade de hardware, ou seja, precisamos realizar uma escala vertical para
melhorar o hardware e segurar a quantidade maior de acessos ao software.
Depois, começaremos a trabalhar com cache, consumindo API de parceiros. Isso
faz com que o sistema fique sujeito a algumas regras externas. Por exemplo, ao
fazermos um checkout da empresa “X” de gateway de pagamento precisamos
estar prontos para que nossa aplicação esteja sujeita às regras dessa API externa.
Além disso, mais relatórios serão necessários para analisarmos os eventos, caso
aconteça algum erro na aplicação.
Fase 4
Na fase quatro, o sistema tem ainda mais acessos e precisa de mais Upgrade de
hardware. Consequentemente, precisamos de mais relatórios e consultas. Além
disso, surgem algumas dificuldades no banco de dados, devido ao aumento
dos acessos nele. Então, é preciso gerar alguns comandos, podendo ser via
linha de comando mesmo, para conseguir fazer alguns relatórios ou para
exportar algumas informações. Durante o processo, alguns problemas podem ter
acontecido, ou até mesmo algumas mudanças na empresa, por isso, é necessário
criar a versão 2 da API, mas a versão 1 precisa ser mantida.
Arquitetura Hexagonal 165
Fase 5
Fase 6
Depois disso, precisamos integrar o sistema com o novo CRM. Neste momento, já
conseguimos perceber que temos algumas regras de negócio misturadas. Assim,
precisamos mudar nossa SPA (frontend) para React e começar a refatoração.
Fase 7
Sabendo que o mundo está migrando para Docker container kubernetes, vemos
o quanto é bom para nossa aplicação colocá-la para rodar em container. Como
iremos migrar para container precisamos pensar na maneira que faremos o
processo de CI/CD, isto é, a integração contínua e o deploy contínuo. Depois
disso, precisamos de um container registry para trabalhar e fazer os processos.
Arquitetura Hexagonal 168
Fase 8
Na fase oito, mesmo percebendo que o software está virando um legado, não
queremos refazê-lo. Então, a melhor solução é criar microsserviços em volta
desse sistema. Agora, precisamos fazer a comunicação entre esses microsserviços.
Para isso, a opção mais simples é fazer com que todos acessem o mesmo
banco de dados. Mas com isso começamos a ter alguns problemas de tracing.
Acessamos um microsserviço que se comunicou com outro, depois disso, tivemos
um problema em nossa aplicação e de repente não conseguimos identificar em
qual microsserviço esse problema aconteceu. O que vai dificultar nosso trabalho
nesse sistema. Algumas aplicações começam a ficar mais lentas do que eram
Arquitetura Hexagonal 169
Fase 9
Na fase nove, não temos mais condições de trabalhar com containers, pois o custo
desses serviços está além do esperado. Assim, vamos para Kubernetes. Se ainda
não soubermos trabalhar com essa tecnologia, é necessário bastante esforço para
aprendermos a maneira adequada de implementá-la em nossa solução.
Finalmente chegamos à fase dez. Nesta fase, precisamos usar a nossa imaginação!
O que foi colocado neste tópico não pode ser considerado um exagero perto
do que acontece nos dias de hoje. Percebemos que, conforme essas evoluções
acontecem, o sistema vira um “rabisco de borracha”. O software aparenta ser
um legado, por isso ninguém mais quer “por a mão” nele. Lembrando que os de-
senvolvedores que estão escrevendo esse sistema não são necessariamente ruins.
Provavelmente, eles estão estudando e tentando evoluir, colocando tecnologias
novas. Porém, rastros vão sendo deixados para trás. Isto é, vários backlogs e
débitos técnicos ficam para trás nesse processo de evolução.