Seja muito bem-vindo ou bem-vinda ao curso de Monolitos Modulares. Meu nome é Thiago Martins e serei o instrutor deste curso. Sou uma pessoa desenvolvedora back-end especialista em Node.js e Nest.js.
Audiodescrição: Thiago é um homem branco com cabelo castanho curto e barba curta. Ele está usando óculos e uma camisa verde clara. Atrás dele, há uma estante de livros e duas poltronas.
Este curso é avançado, destinado àquelas pessoas que desejam aprender a desenhar e arquitetar sistemas modulares. Ou seja, sistemas que possuem baixo acoplamento e resistem ao teste do tempo. Em outras palavras, a manutenção desses sistemas ao longo do tempo permanece mais ou menos constante.
O que vamos aprender no curso? Primeiramente, discutiremos o que são monolitos e quais são os desafios da manutenção de aplicações construídas dessa forma. Também aprenderemos a identificar o antepadrão chamado Big Ball of Mud (Grande Bola de Lama), compreendendo do que se trata. Simularemos o trabalho em sistemas legados para refletir um ambiente de trabalho real, como em empresas de verdade, onde normalmente lidamos com sistemas já em funcionamento e legados.
Além disso, aprenderemos a identificar os domínios existentes nesses sistemas e como podemos extrair subdomínios.
Vamos utilizar o Strangler Fig Pattern (Padrão da Figueira Estranguladora) para aprender a migrar partes do sistema de maneira segura. Em seguida, aplicaremos essas extrações e implementaremos módulos utilizando o NestJS, realizando o que chamamos de Vertical Slices (Fatias Verticais).
Por fim, discutiremos a comunicação síncrona e assíncrona, ou seja, em memória e em fila entre os módulos. Aplicaremos alguns conceitos de Domain Driven Design (Design Orientado a Domínio) na prática, como entidades e objetos de valor. Finalmente, prepararemos e discutiremos a evolução de um monólito para microserviços.
Quais são os pré-requisitos para este curso? É extremamente importante ter algum conhecimento em NodeJS e NestJS, que são as ferramentas que utilizaremos, além de familiaridade com Docker e Docker Compose, que serão usados para levantar nossa infraestrutura local. Também utilizaremos o Postgres como banco de dados, então é altamente recomendado ter conhecimento sobre ele, assim como os conceitos de Domain Driven Design, tanto os estratégicos quanto os táticos.
Esperamos que todos estejam tão animados quanto nós para começar este curso. Será um curso prático, avançado e desafiador, mas valerá a pena. Vamos realizar isso juntos? Nos vemos no próximo vídeo.
Se somos pessoas desenvolvedoras que já atuam na área há algum tempo, ou estamos prestes a começar, provavelmente já tivemos que lidar, ou iremos lidar, com sistemas legados. Muitas pessoas acreditam que um sistema legado é necessariamente ruim, mas isso não é verdade. Um sistema legado é, basicamente, um sistema que herdamos, construído por outras pessoas desenvolvedoras. Seja porque mudamos de empresa ou porque, dentro da mesma empresa, mudamos de equipe, precisamos lidar com um sistema que já existia.
A principal característica de um sistema legado é que ele já estava em produção, ou seja, já entregava valor para alguém e possuía usuários, estando, portanto, consolidado. Assim como uma casa antiga, um sistema legado foi construído em uma época diferente, atendendo a necessidades distintas. Com o tempo, ele pode apresentar problemas, assim como uma casa que começa a ter problemas no telhado, vazamentos, pisos quebrados ou problemas na fiação. O sistema legado, geralmente, apresenta problemas em sua arquitetura. Às vezes, devido à necessidade de atingir metas rapidamente, o desenvolvimento foi feito de maneira não muito controlada, resultando em problemas.
Esse é um fenômeno que observamos ao longo de nossas carreiras de maneira pragmática, mas que já é estudado teoricamente desde 1981. Barry Bowen publicou um artigo na revista Software Engineering Economics, discutindo o custo de mudança de software ao longo do tempo. Ou seja, quanto mais o tempo passa, mais caro se torna fazer modificações no sistema, caso encontremos problemas ou os requisitos mudem, ou ainda, caso descubramos uma nova necessidade do usuário final.
Uma solução que frequentemente consideramos é reescrever o sistema legado em uma linguagem nova, como o Rust, que está em alta atualmente. Acreditamos que isso resolverá os problemas e eliminará decisões ruins feitas no sistema antigo. No entanto, na maioria das vezes, um sistema legado não se torna difícil de gerir por um problema técnico ou pela linguagem em que foi desenvolvido. Mesmo que comecemos a construir um sistema em Rust, inicialmente ele será simples, com poucas rotas e serviços. Podemos até achar que está funcionando bem, mas, com o tempo, pelos mesmos motivos que exploraremos nas próximas aulas, a complexidade aumenta exponencialmente e o custo de mudança para esse novo sistema se torna praticamente o mesmo que o do sistema antigo.
Quando tínhamos poucos serviços, entidades e rotas, se fosse um sistema web, ele começa a crescer, proliferando o número de relações e comportamentos. Isso pode levar a situações em que um serviço ou entidade específica do sistema é utilizado por diferentes departamentos da empresa, ou seja, por diferentes usuários. Esses usuários são atores distintos que representam departamentos da empresa utilizando, talvez, um serviço específico.
Um problema comum que surge é quando fazemos uma mudança em uma entidade ou algo no sistema para atender a demanda de um ator, como um cliente final, mas acabamos afetando outro usuário do sistema, pois eles compartilham uma lógica emaranhada. Um exemplo prático é quando temos, no banco de dados, uma parte do sistema para fazer o onboarding de um usuário, ou seja, o cadastro. No banco, o campo "telefone" de uma pessoa que está se cadastrando é obrigatório. No entanto, mais adiante, descobrimos que usuários administradores gostariam de adicionar outros administradores sem precisar do telefone, apenas do e-mail. Como desenvolvemos o sistema ao redor de uma tabela e modelagem de usuário, acabamos com uma modelagem contraditória: para clientes, o telefone é obrigatório, mas, para administradores, é opcional.
Quando começamos a entrar em conflito e fazemos mudanças para atender a uma demanda, podemos acabar afetando outras partes e atores do sistema sem perceber. Isso é frequentemente chamado de acoplamento. Esse fenômeno é muito comum. À medida que o sistema cresce, se não dermos a devida manutenção e aplicarmos algumas técnicas que discutiremos aqui, começamos a ter um aumento significativo de acoplamento invisível. Isso ocorre quando modificamos uma parte do sistema e ela afeta outras partes que nem sabíamos que estavam relacionadas, por estarem conectadas de maneira indireta, mas compartilhadas.
Chamamos essa desordem de software ou sistema de Big Ball of Mud (grande bola de lama), um termo muito utilizado em livros sobre Domain Driven Design (Design Orientado a Domínio). O próprio Von Vernon, autor do livro Implementing Domain Driven Design (Implementando Design Orientado a Domínio), utiliza esse termo. Se já temos experiência no mercado, provavelmente já lidamos com isso.
Como reconhecemos uma grande bola de lama? É um sistema onde há falta de arquitetura, é difícil entender a finalidade de cada componente, e não conseguimos ver uma estrutura bem definida. Para adicionar, por exemplo, uma simples rota em um sistema web, ficamos perdidos, sem saber por onde começar. Muitas vezes, pessoas desenvolvedoras, principalmente as mais juniores, e até mesmo algumas mais experientes, acreditam que para resolver esse tipo de problema precisamos de soluções explicitamente técnicas, como Design Patterns (Padrões de Projeto).
Design Patterns são padrões de modelagem de partes do sistema, frequentemente utilizados com classes, mas também existem padrões funcionais que utilizam funções. As pessoas desenvolvedoras acreditam que com uma modelagem técnica, utilizando padrões de projeto, resolverão o problema do sistema estar emaranhado e acoplado. Inicialmente, isso pode até funcionar, criando interfaces e abstraindo partes do sistema, mas, ao longo do tempo, surge outro problema chamado de indireção.
Para criar apenas uma nova rota, por exemplo, em um sistema web, que pode ser uma rota simples para retornar dados de um usuário, é necessário criar vários arquivos. Talvez já tenhamos passado por isso em nossa carreira. Um exemplo disso é quando temos um controller que recebe a requisição HTTP e depende de uma interface, como iService, que é implementada por iUserService. Esse serviço depende de outras duas interfaces, iRepository e AbstractFactory, cada uma com sua implementação, e essas implementações dependem de outras interfaces. Isso leva à proliferação de interfaces no sistema que não abstraem informações ou comportamentos úteis, apenas repassam chamadas sem prover valor.
É interessante considerar, como recomendado por Vladimir Korikov, autor do livro Unit Testing Principles, Practices and Patterns (Princípios, Práticas e Padrões de Testes Unitários), que para criar uma interface, o ideal é ter pelo menos duas implementações para justificá-la. Uma interface é uma abstração de algo que pode ser feito de mais de uma maneira ou que queremos esconder parte dos detalhes de implementação.
Voltando ao ponto, muitas vezes as pessoas desenvolvedoras acreditam que para resolver o problema do Big Ball of Mud, basta aplicar padrões de projeto ou arquiteturas específicas. No entanto, isso nos leva ao mesmo problema: um sistema acoplado, com comportamentos ou entidades compartilhadas por diferentes departamentos, e uma série de interfaces e abstrações que tornam até tarefas simples mais difíceis.
A pergunta que deixamos para responder no próximo vídeo é: será que é possível, de alguma maneira, reduzir essa curva de custo de mudança ao longo do tempo, para que ela não se torne exponencial, mas que possamos achatá-la para que se mantenha quase uniforme, como a curva azul que vemos aqui? Parece que todo software está fadado a se tornar impossível de manter ao longo do tempo, mas não é esse o caso. Veremos isso no próximo vídeo.
No último vídeo, discutimos quatro pontos principais relacionados aos sistemas legados. Abordamos o que são esses sistemas, que são basicamente aqueles que já estão em produção há algum tempo e servem a alguns usuários. Não necessariamente são difíceis de modificar, mas, ao longo do tempo, se não forem cuidados adequadamente, podem se tornar de difícil manutenção. Analisamos a teoria por trás disso, que é a curva de custo versus tempo, a qual indica que, com o passar do tempo, o custo para modificar um software tende a crescer exponencialmente. Discutimos algumas das possíveis causas para isso.
Uma solução que muitos desenvolvedores acreditam ser eficaz é a reescrita do sistema. No entanto, vimos que isso é um mito. Quando tentamos reescrever o sistema do zero, acreditando que uma nova linguagem, framework ou biblioteca resolverá o problema, acabamos com dois sistemas problemáticos. O sistema novo, apesar de inicialmente parecer pequeno e de fácil manutenção, se não forem tomados os mesmos cuidados e se a raiz do problema não for resolvida, acabará se tornando novamente um "Big Ball of Mud" (grande bola de lama), um sistema totalmente acoplado e de difícil manutenção, onde mudanças em um local afetam outros de maneira indiscriminada.
Neste vídeo, responderemos à pergunta feita anteriormente: é possível achatar essa curva para que ela não seja exponencial e se mantenha mais constante ao longo do tempo? Para responder a essa pergunta, precisamos entender que o problema se divide em duas partes principais: problemas de comunicação e a modelagem do próprio sistema.
No que diz respeito à comunicação, enfrentamos um dilema. A comunicação ocorre quando uma pessoa, como Maria, que possui conhecimento sobre um assunto, tenta transferir esse conhecimento para outra pessoa, como João. O conhecimento de Maria é influenciado por seu contexto, educação e experiência. Para transferir esse conhecimento, Maria precisa codificá-lo, transformando algo abstrato em concreto, como imagem, texto ou fala, para externalizá-lo. João, ao receber essa informação, precisa decodificá-la e interpretá-la. O problema é que, muitas vezes, o que Maria quer dizer não chega exatamente como estava em sua mente para João, devido a diferenças de interpretação. Isso ocorre porque não temos uma maneira de transferir conhecimento de forma totalmente fidedigna entre cérebros, já que o conhecimento de João também é influenciado por seu contexto, experiência e educação.
Para resolver esse problema de comunicação, é necessário um ciclo de feedback. João precisa comunicar a Maria o que entendeu, para que ela confirme se o entendimento está correto. Esse ciclo de feedback é essencial para corrigir informações deturpadas.
Agora, consideremos uma organização com comunicação hierárquica. Nesse caso, a transferência de informação passa por diversos níveis até chegar ao ponto final. Por exemplo, o usuário do sistema, que é um dos stakeholders, pode ter uma necessidade ou sugestão. Em organizações hierárquicas, essa informação passa por várias cadeias, como gerente, product owner e, finalmente, o desenvolvedor. A cada passagem, o dilema da comunicação se exacerba, resultando em uma "comunicação de telefone sem fio", onde parte da informação original se perde. Diferentes departamentos interpretam funcionalidades de acordo com suas próprias necessidades, o que agrava o problema.
Para resolver essa questão, é necessário implementar a lateralização, que consiste em diminuir o número de hierarquias sempre que possível. Em vez de passar por várias etapas, o ideal é que o desenvolvedor esteja em contato direto com os usuários finais. Isso pode ser aplicado na prática em empresas, onde, ao invés de se comunicar com gerentes que repassam informações, o desenvolvedor interage diretamente com o cliente final. Essa abordagem pode ser benéfica para equipes técnicas e líderes de times.
Já trabalhamos em funcionalidades ou na correção de problemas em sistemas nos quais, após a implementação e entrega, não soubemos, por muito tempo ou nunca, qual foi o resultado final daquela implementação. Não sabemos como o usuário final foi impactado, se o problema foi realmente resolvido. Em muitas empresas, as pessoas desenvolvedoras apenas resolvem o problema, implementam algo, mas não têm conhecimento de como o usuário final reagiu àquela modificação. Quanto mais conseguimos deixar os dois em contato direto, menos camadas de tradução e menos perda de informação teremos. Isso é crucial.
Essa é a primeira parte do problema que, normalmente, leva a um big ball of mud (grande bola de lama). A segunda parte, que discutiremos agora, trata da modelagem técnica ou transacional. O que é isso? A modelagem técnica ou estritamente técnica ocorre quando a equipe de software, a equipe de desenvolvimento, modela o sistema e se comunica entre si, tanto por escrito quanto verbalmente, de maneira muito técnica, inclusive com o usuário final ou com quem está trazendo os requisitos do sistema. Um sintoma desse tipo de problema é quando a modelagem do sistema e sua arquitetura estão muito focadas em funções técnicas. Por exemplo, o usuário tem um gateway, um repositório, uma tabela X, e permissões. Às vezes, há algumas informações que remetem à linguagem do domínio, como endereço ou permissão, mas, na maior parte do tempo, a equipe se comunica de maneira muito técnica, não só entre si, mas também com outras equipes.
Isso é problemático porque começa a divergir quando falamos de termos muito técnicos. Surge mais uma camada de tradução para a linguagem que o usuário final do sistema utiliza, que geralmente não é técnica. Isso se reflete até na estrutura do sistema, como na estrutura de pastas. Se o sistema está dividido por funções, como Controllers, Services, Database, Gateways, DTOs, etc., e há uma série de arquivos dentro de cada uma dessas pastas, não há distinção das capacidades de negócio, dos subdomínios do problema. O sistema é arquitetado puramente de uma perspectiva técnica e funcional, focando na função de cada artefato.
Para começar a corrigir esse problema, é necessário modelar o sistema e se comunicar utilizando a linguagem do domínio. Em vez de falar sobre termos técnicos, especialmente com outros times, devemos usar os termos que os usuários finais utilizam, os que fazem sentido no domínio em que estamos trabalhando. Por exemplo, o usuário possui uma conta, realiza uma compra, possui um risco associado, reside em um endereço. A mudança necessária é na comunicação, uma mudança mais psicológica. Uma solução estritamente técnica muitas vezes não resolve o problema, pois ele não é técnico. O problema está na maneira de pensar e modelar o sistema, que se afasta da realidade do negócio e de como o usuário final entende e utiliza o sistema.
A primeira parte é falar a linguagem do domínio. A segunda parte é entender as capacidades distintas do negócio, ou seja, os departamentos existentes na empresa, organização ou sistema. Devemos alocar as entidades, comportamentos e artefatos de software nesses diferentes subdomínios. Por exemplo, quando falamos de conta, estamos no contexto de pagamentos; endereço, no contexto de logística; vendas, no contexto de compra; compliance, no contexto de risco. Ao segregar e entender essas modalidades do negócio, não separamos mais o sistema por função, mas por modalidades do negócio em que cada artefato está alocado.
Antes, com uma modelagem muito técnica, as entidades e comportamentos eram representados por uma única coisa. Por exemplo, um usuário que possuía todos os comportamentos do sistema. Ao modelar tudo através de um usuário, percebemos que, dependendo do contexto, ele pode ter nomes, comportamentos e atributos diferentes. No contexto de vendas, chamamos de cliente; em logística, de destinatário; em identidade, de usuário.
Quando isso se reflete na arquitetura do sistema, a mudança é significativa. Antes, com uma divisão funcional, agora temos uma divisão que reflete os domínios. A divisão de pastas e módulos está de acordo com as capacidades ou departamentos do sistema. Não falamos mais de controller, service, banco de dados, mas de departamentos como compliance, pagamentos, vendas, inventário, logística. A mudança é sutil, mas tem um grande impacto.
O Big Ball of Mud, um sistema todo acoplado com informações compartilhadas em diversas partes, começa a se dividir e focar na modelagem do negócio. Cada parte do sistema fica em seu próprio contexto, em seu próprio casulo. O contexto de vendas, administração, suporte, por exemplo, cresce de maneira vertical, não mais horizontal ou desorganizada. Isso é chamado de arquitetura vertical, de camadas verticais. Cada contexto é responsável por definir suas entidades, necessidades e comunicação com serviços ou sistemas externos, tornando-se mais autocontido e menos dependente dos outros.
Na prática, esses contextos se traduzem em módulos, formando o que chamamos de monolitos modulares. Essa divisão em arquitetura vertical, onde cada parte do sistema é autocontida e a complexidade cresce verticalmente, compreendendo uma capacidade de negócio distinta, é o que caracteriza um monolito modular. No próximo vídeo, veremos como fazer isso na prática, transformando um sistema Big Ball of Mud, cheio de artefatos, serviços e classes compartilhadas, em um monolito com módulos bem definidos e independentes, limitando o impacto de alterações em um módulo sobre os outros.
O curso Node.js: arquitetura de monolito modular possui 404 minutos de vídeos, em um total de 56 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
2 anos de Alura
Matricule-se no plano PLUS 24 e garanta:
Jornada de estudos progressiva que te guia desde os fundamentos até a atuação prática. Você acompanha sua evolução, entende os próximos passos e se aprofunda nos conteúdos com quem é referência no mercado.
Mobile, Programação, Front-end, DevOps, UX & Design, Marketing Digital, Data Science, Inovação & Gestão, Inteligência Artificial
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você participa de eventos exclusivos, pode tirar dúvidas em estudos colaborativos e ainda conta com mentorias em grupo com especialistas de diversas áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
2 anos de Alura
Todos os benefícios do PLUS 24 e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Para estudantes ultra comprometidos atingirem seu objetivo mais rápido.
2 anos de Alura
Todos os benefícios do PRO 24 e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.
Conecte-se ao mercado com mentoria individual personalizada, vagas exclusivas e networking estratégico que impulsionam sua carreira tech para o próximo nível.