Cellular Architecture
Criando sistemas independentes com ajuda da biologia celular
“Se conseguirmos uma arquitetura fracamente acoplada e bem encapsulada…, podemos obter um melhor desempenho de entrega…, crescer substancialmente o tamanho da engenharia e aumentar a produtividade linearmente” [1]
Esta arquitetura é embasada nos seguintes princípios:
- Cada célula possui um único núcleo que é a aplicação principal, podendo variar entre ser um microsserviço ou um BFF, a depender do contexto.
- Maior isolamento entre as células, mediante integração externa, na medida do possível, a fim de que as equipes possam alcançar uma maior autonomia de atuação.
- As integrações devem ser as mais assíncronas possíveis, se utilizando do lançamento de eventos para notificar alterações no estado interno das células para outras células que estejam interessadas.
Que por sua vez, são apoiados nos alicerces trazidos por Eric Evans em DDD (Domain-Driven Design) [10], assim como arquiteturas conhecidas no mercado, como Event-Driven.
Essa arquitetura não é de toda nova, a questão é que nunca foi compilada e traduzida da forma que iremos fazer aqui.
Um pouco de contextualização histórica
(1973) Actor Model
É um modelo matemático de computação concorrente criado por Carl Hewitt que trata o ator como a primitiva universal da computação concorrente. As informações são trocadas entre os atores em forma de mensagens [2].
Peculiaridades:
- Para garantir a resiliência, são usados supervisores
- É stateful, ou seja, 1 cliente 1 ator
(2003) O que é “Programação Orientada a Objeto”?
“Eu pensei em objetos como sendo células biológicas e/ou computadores individuais em uma rede, apenas capazes de se comunicar com mensagens…” [3]
(2011) Microsserviços
(2014) O manifesto reativo
Este manifesto fala de alguns princípios importantes de aplicações com viés reativo como serem: mais fácil de manter, serem extensivas, responsivas, elásticas, resilientes e dirigidas a mensagem (Message-Driven).
Quanto ao princípio Message-Driven o manifesto diz o seguinte: “os sistemas reativos contam com a transmissão assíncrona de mensagens para estabelecer um limite entre os componentes que garante acoplamento fraco, isolamento e transparência de localização … Empregar a passagem de mensagens … permite o gerenciamento de carga e elasticidade … A comunicação não bloqueante permite que os destinatários consumam recursos apenas enquanto estiverem ativos, levando a menos sobrecarga do sistema.”
(2015) A vida além das transações distribuídas
Escrito por Pat Helland tinha como objetivo falar sobre os problemas e possíveis soluções da distribuição de transações que vinha ocorrendo até então com a alavancagem de sistemas distribuídos.
Segue algumas especulações nessa postagem que estão relacionadas as possíveis abordagens para se conectar entidades independentes usando mensagens:
- Escalabilidade quase infinita e aplicativos agnósticos de escala.
- A comunicação deve ser necessariamente assíncrona, o que impossibilita a garantia da atomicidade.
- O não conhecimento do destino da mensagem, sendo que essa amarração, deve ser feita de forma externa ao aplicativo que está enviando.
“Estamos em um momento em que os padrões para construir esses aplicativos podem ser vistos, mas ninguém ainda está aplicando esses padrões de forma consistente. Este artigo argumenta que esses padrões nascentes podem ser aplicados de forma mais consistente no desenvolvimento de aplicativos projetados para dimensionamento quase infinito.” [5]
(2015) BFF (Backends For Frontends)
“… você deve pensar no aplicativo voltado para o usuário como sendo dois componentes — um aplicativo do lado do cliente … e um componente do lado do servidor (o BFF) … O BFF está fortemente acoplado a uma experiência de usuário específica e normalmente será mantido pela mesma equipe que a interface do usuário, facilitando assim a definição e adaptação da API conforme a interface do usuário exige, além de simplificar o processo de alinhamento de liberação de ambos os componentes cliente e servidor.” [21]
“Essa mudança significou que as pessoas, que até agora trabalhavam principalmente no código de frontend … precisavam raciocinar sobre backend: pilha de tecnologia diferente e desafios diferentes.” [22]
(2017) Actor Model + Domain-Driven Design (DDD) + Reactive Architecture
Nos deparamos com um mundo online e móvel que continua a crescer exponencialmente, e uma terceira onda de IoT visa adicionar centenas de bilhões de dispositivos conectados às nossas vidas.
Para gerenciar as necessidades exigentes de hoje e se preparar para a próxima onda, grandes empresas estão adotando sistemas reativos. A base para esses sistemas é o Actor Model.
Os atores são excepcionalmente adequados para uso com o Domain-Driven Design, falando a Linguagem Ubíqua do seu domínio principal de negócios. Os atores ainda são projetados para lidar com falhas, pois mantem a resiliência do sistema e a capacidade de resposta aos usuários, não importando o que esteja acontecendo. Além de ajudarem a dimensionar de forma reativa os sistemas para atenderem às demandas de simultaneidade, crescendo de forma elástica. [12]
O modelo de célula arquitetural
Uma célula arquitetura é equivalente ao conceito de Bounded Context do Domain-Driven Design (DDD).
A célula arquitetural é uma unidade lógica que é formada por canais de entrada e saída, um banco de dados e uma aplicação.
Para mais detalhes sobre as cores utilizadas nas mensagens, consultar o artigo SocioTechnical Architecture Method (STAM).
As mensagens podem ser do tipo comando que irá acionar um processamento interno ou do tipo evento que representa o resultado da mudança de estado da célula, a ser eventualmente capturado por outras células interessadas.
O canal de entrada pode ser representado por uma fila/tópico ou até mesmo endpoints, já o canal de saída, deve ser prioritariamente representado por uma fila/tópico, pois esta arquitetura é orientada por eventos [9], salvo as células de interface que podem optar por chamadas a endpoints externos, por terem comportamento caracteristicamente ativo.
O banco de dados local está aderente ao padrão da arquitetura de microsserviços denominado Database per Service [28], ou seja, cada microsserviço deve possuir um banco de dados próprio não compartilhado. Sendo que o banco de dados possui a função de manter o estado do Domain Model (Modelo de Domínio), apresentado por Eric Evans em DDD [10] e também representado como Command Model no padrão CQRS [13] de Martin Fowler.
Também atende a proposta de se enriquecer um modelo de dados mediante a captura de eventos, encontrada em Read Model em EventStorming [20] por Alberto Brandolini e se olharmos mais atentamente, também está presente no conceito de segregação de responsabilidades, mais especificamente, através do Query Model em CQRS [13] por Martin Fowler. As duas propostas, tem como objetivo, representar a concepção de um modelo para consultas, servindo única e exclusivamente uma UI (interface com o usuário), assim como iremos propor aqui através das células de interface, por exemplo.
Aqui cabe uma observação. Mesmo que estejamos criando uma referência a proposta de Martin Fowler de CQRS, uma célula não pode possuir dois nucléolos (dois Modelos), como motrado na figura acima, ou seja, é natural que esta segregação de responsabilidades, seja refletida na segregação de contextos, portanto, uma célula só pode possuir ou um Modelo de Domínio ou um Modelo de Consulta.
O núcleo da célula, representado por uma aplicação que deve seguir os princípios de DDD quando microsserviços, ou seja, deve dar acesso de fora para dentro, mas não o contrário, com a intenção de proteger o nucléolo, ou seja o Modelo de Domínio, comportamento encontrado em arquiteturas como Hexagonal [29], Onion [30] e Clean [31]. Quando o núcleo da célula for um BFF, não temos uma arquitetura específica sendo proposta, mas pensando na questão de boas práticas de projeto, a sugestão é que seja baseada nos padrões Ports And Adapters [29], também objetivando isolar o nucléolo, ou seja, o Modelo de Consulta.
A integração entre células deve ser feita, preferencialmente, de forma externa as células, objetivando o isolamento das mesmas. Para se conseguir essa integração indireta / de forma externa, podem ser utilizadas ferramentas como ksqlDB [8] e dapr [23].
Quanto ao uso de Workers (padrão de processamento não oficial que se desvia dos princípios de uma arquitetura orientada a microsserviços, por se enquadrar mais como um nanoserviço) teria que ser revisto, pois seria como vírus dentro da célula, concorrendo com o núcleo em questão de processamento.
Porém, componentes que capturem eventos e agreguem os dados contidos no mesmo dentro do modelo de dados local da célula podem surgir, utilizando-se por exemplo o padrão Message Store [33]. Uma ferramenta que possui recursos para se fazer este tipo de operação, é o ksldb com o conector Sink [34].
Caso ocorra uma integração direta entre células via endpoints, nos casos de comunicação entre células de interface ou de fachada com células de domínio e células de domínio com células de integração, poderá se optar por utilizar uma integração via Command Gateway [24] ou API Gateway [25] ou até mesmo via Sidecar Proxy [27], para se obter um maior isolamento entre as mesmas.
Get Giuliano Firmino’s stories in your inbox
Join Medium for free to get updates from this writer.
Como já citado, existem basicamente quatro tipos de células arquiteturais:
- Célula de domínio: Cuja aplicação é um microsserviço de processamento assíncrono. O canal de entrada recebe comandos e o canal de saída emite eventos.
- Célula de integração: Possui o mesmo modelo das células de domínio, a diferença é que são criadas para servirem como camadas de abstração (ou anticorrupção) para chamadas de sistemas e até mesmo de invocação por aplicações que estão de fora das fronteira da corporação. Portanto, a mecânica de comunicação mais adequada para este tipo de célula é a transferência de estado representacional, mais conhecida como REST [32]. A comunicação assíncrona quando disponível pela aplicação externa, pode ser via chamada REST webhooks (também conhecido como Http Callback) [26]. O banco de dados local, neste caso, pode ser utilizado para armazenamento de dados em cache, mantendo o “estado” do pseudo-domínio.
- Célula de interface: é feita especificamente para entregar valor ao usuário, portanto assim que haja necessidades de mudanças mais estruturais, ela poderá ser descartada e outra célula pode ser construída para atender as novas necessidades. A grande diferença aqui, é que todos os dados necessários a serem exibidos na interface, são mantidos na base local, portanto não há chamadas síncronas para consulta de dados, o que resolve questões de indisponibilidade externa e aumenta consideravelmente a performance das consultas. A aplicação é um BBF e ainda é constituída de uma interface com o usuário. Mas ao contrário das células de domínio, o canal de entrada recebe eventos que podem compor dados armazenados localmente e ainda, podem ser enviados via notificações a UI via protocolo WebSocket, por exemplo quando aplicações Web ou via MQTT quando aplicações Mobile, ou até mesmo usando WebRTC. O canal de saída, é responsável por enviar comandos que necessariamente acionam células de domínio.
- Célula de fachada: é praticamente inverso a célula de integração e similar a célula de interface, as diferenças são que ao invés de prover um backend para aplicações web ou mobile, provê uma fachada de endpoints REST, para serem utilizados por sistemas externos (por parceiros, por exemplos). E novamente, como a aplicação cliente se encontra fora da fronteira corporativa, a comunicação assíncrona ocorre mediante chamadas REST webhooks.
OK e como as células poderiam se comunicam?
Integration Patterns (Padrões de Integração)
“Este catálogo de padrões descreve 65 padrões de integração, coletados de muitos projetos de integração desde 2002. Os padrões fornecem orientação de design independente de tecnologia para desenvolvedores e arquitetos descreverem e desenvolverem soluções de integração robustas.” [11]
Abaixo, o que seria a representação de uma célula arquitetural com a notação de integração.
Ferramentas utilizadas como sugestão, para aplicação dos padrões de integração
Para materializarmos os padrões de integração apresentados, iremos utilizar uma solução via stream e para isso vamos precisar:
- Apache Kafka: Foi construído para ser um sistema de mensagens altamente escalável e desempenhar um papel crítico como pipeline central de dados. Desde então tem sido usado por diversas empresas, principalmente para troca e tratamento de mensagens em tempo real. Idealizado pelo LinkedIn, após a migração de uma arquitetura monolítica para uma de microsserviços [4].
- ksqlDB: A API do Kafka Streams foi criada com base nas APIs de Produtor e Consumidor do Kafka. Embora simplificada, a API do Streams ainda exigia a escrita do código do aplicativo em Java/Scala. Surge então, o KSQL que é o mecanismo SQL criado com base na API Kafka Streams. Isso permitiu a escrita de consultas SQL nos dados de fluxo sem a necessidade de codificação. Por fim, o KSQL foi renomeado para ksqlDB [8].
- Event Streaming Patterns [19]: catálogo de padrões de transmissão de eventos a serem implementados via ksqlDB.
Cenários de aplicação prática
1) Integração 1:1 entre células distintas
Abaixo um desenho representado pela notação, utilizando os padrões Filter [14] e Translator [15].
E abaixo, um desenho de como seria a aplicação destes padrões, utilizando células arquiteturais sendo integradas através de um Event Stream [16] criado dentro do ksqlsb, provindo da célula A, sendo transformados em comandos de entrada para a célula B.
2) Integração N:1 entre células distintas
Abaixo um desenho representado pela notação, utilizando o padrão Aggregator [17].
E abaixo, um desenho de como seria a aplicação destes padrões, utilizando células arquiteturais sendo integradas através de um Event Aggregator [18] criado dentro do ksqlsb, provindos das células A e N, sendo transformados em comandos de entrada para a célula B.
Fontes de Referência
[1] Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations (Nicole Forsgren, Jez Humble e Gene Kim)
[2] Actor model (Wikipédia) — https://en.wikipedia.org/wiki/Actor_model
[3] Publicação Dr. Alan Kay on the Meaning of “Object-Oriented Programming” — http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay_oop_en,
[4] A Brief History of Kafka, LinkedIn’s Messaging Platform — https://insidebigdata.com/2016/04/28/a-brief-history-of-kafka-linkedins-messaging-platform/
[5] Life beyond Distributed Transactions: an Apostate’s Opinion (Pat Helland) — https://web.archive.org/web/20151219204628/http://adrianmarriott.net/logosroot/papers/LifeBeyondTxns.pdf
[6] Kubernetes (Wikipédia) — https://en.wikipedia.org/wiki/Kubernetes
[7] Microservices in Practice, Part 2: Service Integration and Sustainability (Cesare Pautasso) — https://www.si.usi.ch/assets/publications/journals/software/software34/PautassoZALJ17a.pdf
[8] ksqlDB History — https://dbdb.io/db/ksqldb
[9] O que é uma arquitetura orientada por eventos? — https://aws.amazon.com/pt/event-driven-architecture/
[10] Domain-Driven Design — Tackling Complexity in the Heart of Software (Eric Evans)
[11] Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions — https://www.enterpriseintegrationpatterns.com/
[12] Using the Actor Model with Domain-Driven Design (DDD) in Reactive Systems (Vaughn Vernon) — https://www.youtube.com/watch?v=rMv4sul0RwU
[13] CQRS — Command Query Responsibility Segregation (Martin Fowler) — https://martinfowler.com/bliki/CQRS.html
[14] Enterprise Integration Patterns: Filter — https://www.enterpriseintegrationpatterns.com/patterns/messaging/Filter.html
[15] Enterprise Integration Patterns: Message Translator — https://www.enterpriseintegrationpatterns.com/patterns/messaging/MessageTranslator.html
[16] Event Streaming Patterns: Event Stream — https://developer.confluent.io/patterns/event-stream/event-stream/ e Event
[17] Enterprise Integration Patterns: Aggregator — https://www.enterpriseintegrationpatterns.com/patterns/messaging/Aggregator.html
[18] Event Streaming Patterns: Event Aggregator — https://developer.confluent.io/patterns/stream-processing/event-aggregator/
[19] Event Streaming Patterns — https://developer.confluent.io/patterns/
[20] EventStorming (Alberto Brandolini) — https://www.eventstorming.com/
[21] Pattern: Backends For Frontends (Sam Newman) — https://samnewman.io/patterns/architectural/bff/
[22] BFF @ SoundCloud (Lukasz Plotnicki) — https://www.thoughtworks.com/insights/blog/bff-soundcloud
[23] Dapr — Distributed Application Runtime — https://dapr.io/
[24] Command Gateway — https://docs.axoniq.io/reference-guide/axon-framework/axon-framework-commands/command-dispatchers#the-command-gateway
[25] API Gateway — https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-integrating-microservices/api-gateway-pattern.html
[26] RESTful WebHooks — https://web.archive.org/web/20120302000920/http://wiki.webhooks.org/w/page/13385128/RESTful%20WebHooks
[27] Sidecar Proxy Pattern — https://learn.microsoft.com/en-us/azure/architecture/patterns/sidecar
[28] Pattern: Database per service — https://microservices.io/patterns/data/database-per-service.html
[29] Hexagonal Architecture or Ports And Adapters (Alistair Cockburn) — https://alistair.cockburn.us/hexagonal-architecture/
[30] The Onion Architecture — https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/
[31] The Clean Architecture (Robert C. Martin) — https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
[32] Architectural Styles and
the Design of Network-based Software Architectures — Representational State Transfer (REST)
[33] Enterprise Integration Patterns — Messaging Patterns — Message Store — https://www.enterpriseintegrationpatterns.com/patterns/messaging/MessageStore.html
[34] ksqlDB Documentation — CREATE CONNECTOR — https://docs.ksqldb.io/en/latest/developer-guide/ksqldb-reference/create-connector/










