A Arquitetura Multidimensional define vários planos de camadas de código, dos quais o mais determinante – o Plano Principal – é baseado em princípios SOLID e na organização e regras presentes em Arquiteturas Planas como a Arquitetura Hexagonal, Clean ou de Cebola. Todas estas arquiteturas tentem a definir várias camadas concêntricas, contudo nem sempre é necessário utilizar-se de todas elas.
A Arquitetura Multidimensional não é, nem pretende ser, uma arquitetura universal. Não é uma arquitetura boa para jogos, ou IA, por exemplo. A Arquitetura Multidimensional é pensada para sistema corporativos baseados em bancos de dados, e comunicação entre sistemas, que necessitam aplicar lógicas adicionais à mera persistência ou passagem de dados de um lado para o outro. Contudo ela é pensada para ser fácil de evoluir. Portanto, dentro do nicho de aplicações corporativas a Arquitetura Multidimensional é desenhada para servir de base à grande maioria das necessidades e tipos de aplicação comuns num ambiente corporativo.
Neste artigo vamos ver como a mesma Arquitetura Multidimensional promove diferentes Configurações Arquiteturais que podem ser usadas desde as aplicações mais simples às mais complexas ao mesmo tempo que possibilita a evolução da mais simples para a mais complexa sem necessidade de refatoração.
Em traços largos e como resumo a figura seguinte ilustra as 3 principais camadas do Plano Principal da Arquitetura Multidimensional
Ao olhar para este diagrama pode parecer que sempre temos que ter os 3 círculos, mas na realidade apenas o círculos exterior está sempre presente. Os outros são opcionais. Inclusive a Arquitetura Multidimensional se baseia no conceito de que mais círculos podem ser adicionados, conforme a necessidade. O foco em explicar e aplicar apenas três advém do fato que com frequência estes 3 círculos são suficientes para a grande maioria das aplicações.
Vejamos como podemos, a partir do mesmo modelo base, obter configurações diferentes.
Quando a aplicação é muito simples e apenas queremos pegar algum dado de entrada e persisti-lo ou enviá-lo a outro sistema, no fundo queremos apenas estabelecer um contrato em mundo externo para termos acesso à persistência ou às funcionalidades do outro sistema.
Exemplos de aplicações usando este tipo de configuração seriam:
Para este tipo de aplicação os dados coletados na entrada são simplesmente convertidos para uma saída ou persistência e pouco mais há do que isso. Possíveis cálculos ou mecanismos são não existentes, ou suficientemente simples para não demandar uma camada extra. Não existem preocupações especiais com nível de acesso de usuários e provavelmente nem existe a distinção de comportamento por usuário.
Para esta configuração utilizamos apenas a camada mais externa da Arquitetura, com a região de entrada ( claro) e a de saída ( escuro) normalmente indicando interação com outros sistema como o banco de dados ou sistema de arquivos.
Para esta configuração não precisamos de muito, mas também não faz muito. Como não temos as outras camadas o controle de transações terá que ser inicializado por requisição e o controle de segurança de alguma forma feito no controlador. Lógicas de acesso a serviços externos podem ser feitas nesta configuração, mas acabará havendo uma mistura entre os objetos de dados dos controladores, da persistência e dos serviços externos. Para sistemas muito simples – com uma entidade ou duas- isto pode não ser uma questão, mas para sistemas maiores com dezenas de entidades esta configuração não é suficiente.
Prós | Contras |
Poucas camadas e poucos objetos envolvidos | Não captura modelos com muitas entidades ou relações entre elas |
Ideal para isolamento de contratos com outros sistemas, em que o viés é mais de adaptação da representações dos mesmos dados | Não isola regras de acesso e segurança |
Ideal para aplicações que apenas se resumem a I/O simples. | Não isola regras de transação |
Quando as operações da aplicação começam a ter alguma lógica particular à empresa que a está desenvolvendo – como considerações de acesso – ou o número de integrações com outros sistemas aumenta, precisamos de uma configuração mais organizada. Precisamos de orquestração
Quando a aplicação tem um papel voltado aos usuários ou é encarado como um produto, começa a ser necessário incluir regras de acesso e algoritmos que produzem serviços/informação para o usuário – como relatórios. A pura entrada e persistência “burra” de dados, não é mais suficiente. Isto também acontece quando o sistema passa a ser um hub de conexões com vários diferentes sistemas externos. A necessidade de organização da orquestração necessária passa a ser mais importante que ter poucas camadas e objetos envolvidos.
Exemplos de aplicações usando este tipo de configuração seriam:
Para este tipo de aplicações, precisamos contar com mais um anel no plano principal.
Esta configuração permite isolar o que são preocupações de I/O e comunicação com o mundo externo, das regras que a empresa deseja incutir na aplicação ( áreas em verde). Sejam elas relativas ao que aplicação faz, como faz ou para quem faz. O controle de transação se torna mais simples pois as operações se tornam mais perto das funcionalidades de alto nível e menos relativos a operações de I/O.
Este tipo de configuração consegue ser útil em bastantes tipos de sistema que são comuns no mundo corporativo desde que a interação entre as entidades da aplicação sejam muito simples, especialmente quanto não existem interação entre as entidades.
Prós | Contras |
Possibilidade de controlar regras ditadas pela empresa – regras de negócio | Não captura modelos com entidades que tenham relações ou dependências complexas |
Possibilita a orquestração entre vários sistemas externos de forma orientada ao negócio, que distingue entre o modelo externo e o modelo interno dos dados | Não separa o que são regras de negócio ditadas pela empresa, de regras de mercado seguidas por todos. |
Possibilita uma melhor divisão entre as responsabilidades das entidades | Necessita de um pouco mais de disciplina para manter as camadas separadas corretamente. |
Esta configuração cobre bastante terreno e se olharmos com atenção iremos constatar que não difere muito da tradicional arquitetura em 3-camadas. A grande diferença sendo que os objetos de dados são isolados por camada em vez de pertencerem numa camada única, no plano de sustentação.
Esta configuração é baseada em serviços de aplicação. Conforme as regras se tornam mais complexas o reflexo é começar a criar outros serviços que os serviços de aplicação pode orquestrar. Contudo, se não houver cuidado, estes serviços são rapidamente confundidos com verdadeiros serviços de aplicação e começa a ser criada uma intrincada rede de chamadas entre os serviços, levando eventualmente – quando não há cuidado – a dependências cíclicas entre os serviços. Estas dependências cíclicas, por sua vez, impedem um raciocínio linear simples e complicam – ou até, bloqueiam – muitas iniciativas mais globais como o uso de AOP, instrumentação, logging ou tratamento centralizado de exceções.
Este fenômeno pode ser indicativo que as regras de negócio são muito complexas, mas mais frequentemente indicam que regras de negócio estão sendo misturadas com regras mais básicas, das quais dependem. Por que estas regras mais básicas estão sempre subjacentes a tudo o que o sistema faz, elas são isoladas em serviços, mas porque são muito usadas a proliferação destes serviços não é controlada e muitas vezes acontece de ter dois ou mais serviços que fazem a mesma regra, porque simplesmente a documentação e a nomenclatura não eram boas, e novo programador não entendeu que a regras já existia implementada.
Esta “de-evolução natural” que acontece em sistema clássicos de 3-camadas também é possível nesta configuração. Contudo, as arquiteturas planas, e a Arquitetura Multidimensional, já vêm de fábrica com a solução para este problema: a possibilidade de adicional uma camada de domínio, sem necessidade de refatoração.
Quando o sistema se utiliza de muitas regras de mercado que são especificas ao(s) seu(s) domínio(s) de atuação a configuração de 2 anéis não é suficiente. As regras comuns precisam ser recolhidas num ponto comum, de forma que se garanta que a regras base é a mesma para todas as regras de negócio.
As regras de domínio tendem a ser mais ricas e gerais que as regras de negócio e poderíamos diz quer o negócio advém de prover as regras de domínio da forma mais intuitiva possível ao usuário.
Exemplos de aplicações usando este tipo de configuração seriam:
Quando é necessário orquestrar as regras de mercado, para um ou mais domínios, é quando usamos todos os anéis no plano principal.
É preciso notar que em sistema corporativos a necessidade de ter isolamento sobre o domínio não é uma questão de se, mas de quando. Pode ser que por um tempo as regras sejam simples e não haja muita necessidade de isolar as regras em uma camada própria, mas conforme o tempo passa se torna cada vez mais necessário.
Uma das razões que torna cada vez mais necessário é a necessidade de testar as regras. Para domínios complexos e/ou regras complexas um teste que nos obriga a utilizar todas as camadas para descobrir se uma regra de domínio está corretamente implementação pode ter um custo avassalador, tanto que os testes se tornam difíceis de montar e frágeis, pois o caminho que o código executa é relevante, já que estamos tentando atingir a partir de fora do primeiro anel uma funcionalidade do centro. É muito mais simples testar o centro de forma isolada. E porque a arquitetura garante que o centro não depende das camadas externas a lógica de testes é muito mais simples e direta ao ponto.
Prós | Contras |
Possibilidade de controlar e evoluir regras ditadas pela empresa – regras de negócio – e regras ditadas pelo mercado – domínio – de forma organizada e separada. | Exige disciplina para distinguir e manter isoladas as regras de negócio das regras de mercado |
Possibilita uma ainda melhor divisão entre as responsabilidades das entidades, separando as entidades de aplicação das de domínio. | Exige disciplina para distinguir a que domino a regra de mercado pertence |
Permite lidar com modelos simples e complexos simultaneamente. Os modelos são livres para seguirem o modelo natural que existe fora do software | A correta implementação e separação das regras de domínio pode requerer maior envolvimento de Domain Experts e desenvolvedores de grau sênior |
Permite lidar com diferentes modelos de uma forma organizada em que os modelos são isolados uns dos outros. | |
Permite testes mais simples e com maior abrangência | |
Permite aplicar conceitos e práticas de Domain Driven Design |
A Arquitetura Multidimensional, assim como qualquer arquitetura plana é flexível e permite várias configurações diferentes. Todas elas obedecendo aos critérios SOLID. Cada configuração possível é mais adequada a um certo tipo de aplicação e permite que uma mesma base de código evolua de uma configuração para a outra sem necessidade de refatoração.
Sabemos que as aplicações nascem muito mais focadas na funcionalidade que têm que prover do que na forma de organizar o código e muitas, em pleno século 21, ainda nascem como aplicações em 3-camadas com objetos de dados compartilhados entre as camadas. É sabido de há muito tempo que esse não é um bom design porque não respeita o SOLID e leva a práticas que no longo prazo causam o engessamento e obsolescência da aplicação – como a circularidade de dependências, o alto acoplamento entre serviços e objetos de dados e a extrema dificuldade e elevado custo de adicionar funcionalidades novas.
Esta prática é guiada pelo antiquado , mas ainda presente, conceito de que mais pessoas fazem o software me menos tempo. Então há uma aposta recorrente em nivelar por baixo de forma que o filtro permite que sejam contratadas muitas pessoas. Estas pessoas vão entender uma coisa básica como 3-camadas, mas terão dificuldade nos casos em que essa arquitetura não dá suporte, como webservices, jobs automáticos, multi-bancos de dados, etc. As pessoas começaram a inventar formas de lidar com estas incapacidades da arqutetura de 3-camadas e o resultado frequente e inevitável é a famosa Grande Bola de Lama (Big Ball of Mud) em que muitos dos sistemas atuais se encontram.
Aqui o uso da configuração mais simples em 2 camadas pode ser uma grande vantagem. Ainda é simples – poderíamos até argumentar que é mais simples – comparada com 3-camadas o que permite que pessoas menos experientes a usem, mas ainda promove uma organização que pode evoluir de forma simples e barata quando os desafios aparecerem.
Por outro lado, quando a empresa já tem que encarar a sua Grande Bola de Lama que foi construindo ao longo de vários anos de más escolhas arquitetônicas, a solução que é apregoada na praça no momento é Micro Serviços. O problema é que Micro Serviços implica na correta identificação de quais serviços são estes. E aqui, uma equipe sem experiência e menos antenada com as práticas da profissão, vai entender que esses serviços são aquelas classes que eles chamam de “Serviço” no código. O resultado é uma Grande e Distribuída Bola de Lama. Um caos ainda pior.
Para uma correta separação em Micro Serviços, a identificação dos serviços advém da correta identificação dos serviços de negócio. O quê a aplicação provê em termos de resultados e não o como ela provê esses resultados. Mas inequivocamente estes serviços de negócio estão intimamente ligados a serviços de domínio, então a real divisão de Micro Serviços passa por identificar os serviços de domínio e os serviços de aplicação e organizá-los em pacotes o mais autossuficientes possível. Isto não apenas requer disciplina, mas conhecimento de quais operações vão juntas e quais não, ao mesmo tempo de requer conhecer quais padrões arquitetônicos são mais aptos a cada pacote. Além de tudo isto, temos inevitavelmente a necessidade de algum tipo de aplicação que realmente comunique com o cliente em nome de todas estas aplicações, o famoso: BFF (Back-for-Front).
As várias configurações que vimos aqui podem ser usadas em diferentes pontos desta rede de aplicações distribuídas. Desde aplicações de I/O muito simples que comunicam e isolam sistemas externos, até à rede de Micro Serviços, passando por aplicações com camadas de segurança e transação como os BFF, ou clientes de front, até serviços com regras complexas que precisam tirar partido da camada de domínio.
A Arquitetura Multidimensional permite organizar o Plano Principal com as mesmas configurações que qualquer Arquitetura Plana – como a Arquitetura Clean, de Cebola e Hexagonal – de uma forma flexível e adaptável às necessidade de cada aplicação. Desde uma muito simples configuração em 2 camadas, até uma configuração completa com níveis de domínio e aplicação separados.
A flexibilidade faz parte da Arquitetura Multidimensional por design e desde o primeiro dia, o que permite uma evolução para uma configuração mais avançada sem necessidade de refatoração na medida em que isso for necessário. A evolução pode acontecer na medida da necessidade, dos recursos disponíveis e do nível técnico da equipe de desenvolvimento.