Você está na página 1de 16

Construindo uma arquitetura corporativa de

alto nível com a Onion Architecture


A Onion Architecture se apoia em um paradigma que 9 em cada 10 desenvolvedores afirmam ter
domínio,esse paradigma se chama orientação a objetos.

O uso de uma linguagem orientada a objetos não é suficiente para que o sistema desenvolvido
seja de fato orientado a objetos. Eu gosto de dizer que existem 2 tipos de sistemas desenvolvidos
nesse paradigma, que são: os sistemas orientados a objetos e os sistemas com objetos, e logo
vamos entender o que isso significa na prática.

Uma breve análise sobre falhas na aplicação da OOP

O código abaixo eu tendo a chamar de consequência vertical pois em minhas observações eu


percebi que os métodos crescem de forma vertical, além de perder expressividade pois os
contextos não são apresentados nas interações dos objetos:

//O quê?
public void registerAuditingOf(Negotiation negotiation, User user) {
//Como?
Optional<Auditing> lastVersionAudit = repository.findLastVersion(negotiation)
Audinting currentVersion = new Auditing(negotiation);

if (!lastVersionAudit.equals(currentVersion)) {
currentVersion.setUser(user);
repository.save(currentVersion)
}
}
Observem que esse método não é tão claro em relação ao objetivo dele, o método recebe 2
parâmetros que não responde à pergunta "O quê?" e no corpo não é muito claro o que significa
esse currentVersion.setUser(user), quem é user o que ele faz? Nem esse tal de
repository.save(currentVersion), a pergunta que fica é: qual a interação entre os objetos? Qual o
contexto dessa interação? Esse é um típico código "com objetos" e não orientado a eles. Isso é o
início daqueles códigos ininteligíveis que só o dono entende, e olhe lá!

E como ficaria o mesmo código aplicando o OOP?

Consequência horizontal é mais um termo que cunhei observando sistemas realmente orientados
a objetos, e baseado no exemplo abaixo, do mesmo método exemplificado acima, observem a
diferença na expressividade.

//O quê?
1 - public void register(NewAuditingRequest request) {
//Como?
2- User user = request.getUser();
3- Negotiation negotiation = request.getNegotiation();
4- Optional<Auditing> lastVersionAuditing = repository.findOne(LastVersion.of(negotiation))
5- Boolean itWasChanged=negotiation.wasChanged().whenComparedWith(lastVersionAuditing);
6- if (itWasChanged) {
7- auditing().addNewVersionOf(negotiation.changedBy(user))
}
}
Na linha 5 vemos a "consequência horizontal" funcionando e com isso vem a interação dos
objetos, o resultado é uma linguagem clara e objetiva.
E para finalizar na linha 6 é verificado se a negociação foi alterada em comparação com a última
versão, se sim, o objeto auditoria recebe a nova versão de negociação que foi alterada pelo
usuário… Olha como ficou mais claro, agora sei que o usuário alterou a negociação e que a
auditoria guarda versões de negociações alteradas :)

Como fazer isso funcionar?


Quando pensamos em algo fácil de se manusear e de se entender pensamos em organização e o
básico desse princípio une dois outros que conhecemos e são o SRP (single responsibility
principle) e o KISS (Keep It Simple, Stupid) eu poderia dizer que o SRP é a tão famosa Bounded
Context que é bastante apreciado por quem estuda DDD. Infelizmente o SRP, o Bounded Context
e o KISS são pouco aplicados ou tendem a ser aplicados de forma muito micro, e quando digo
micro, digo que é apenas em nível de classe ou de método, o que não é suficiente.

Hoje com o advento dos micro serviços os desenvolvedores acordaram para a necessidade de
modularizar seus sistemas de uma forma que cada módulo trate de um assunto específico,
entretanto a aplicação de tudo isso tende a desrespeitar o KISS, pois as soluções tendem a ser
complexas demais. Uma boa arquitetura sempre está desacoplada da técnica e tende a ser
modulável, assim como funciona no mundo real.

Imaginemos que compramos um carro e não gostamos do escapamento, então levamos a um


mecânico e pedimos para trocá-lo. No mundo dos sistemas quando pedimos para extrair somente
vendas do projeto esbarramos num: "não dá, o projeto terá que ser reescrito".

O problema de reescrever é que isso irá custar uma Ferrari e o que me garante que não farão da
mesma forma que fizeram o primeiro?

Com isso é muito comum você ver micro serviços que são monólitos pequenos.

Já questionou alguma vez a arquitetura tradicional?


A base de 99,99% dos projetos os quais passei e que já estavam em produção usavam o modelo
tradicional de arquitetura, como mostrado na imagem abaixo. Reparem que a infraestrutura passa
a ser a camada mais conhecida dentro dessa arquitetura com isso é comum você ver a parte
técnica espalhada por todo o sistema ao ponto de ofuscar o negócio.

E como eu disse acima, esse é outro gerador de anomalias, como métodos enormes, falta de
coesão, serviços chamando outros serviços, entidades do hibernate ou qualquer outro framework
ORM espalhados por todas as camadas, etc.

Figura 1: Arquitetura tradicional


Uma outra maneira
Já há algum tempo vinha-se discutindo modelos diferentes de arquiteturas.

Em 2008 um cara chamado Jeffrey Palermo propôs uma arquitetura chamada Onion Architecture
também conhecida como Hexagonal Architecture ou Ports and Adapters, acabou ganhando o
gosto dos desenvolvedores.

Não que isso se tornou ultra popular até porque existe uma certa dificuldade no entendimento da
técnica, porém é muito usado quando se fala de DDD.

O projeto DDD Sample que é um esforço da empresa do Erick Evans (autor do famoso livro da
capa azul que trata sobre o tema) tem por objetivo apresentar uma forma de modelar usando a
Onion Architecture. Alguns pontos dessa implementação deixam a desejar, mas ainda assim é
uma boa referência.

A Cebola pode salvar o seu projeto (Onion Architecture)


A imagem abaixo mostra as divisões dessa arquitetura e uma diferença que se nota em relação à
arquitetura tradicional está na orientação da dependência que as camadas têm.

A imagem mostra que a infraestrutura conhece a camada de aplicação e a camada de domínio. A


camada de aplicação não conhece a infraestrutura, porém conhece o domínio. Por último o
domínio não conhece nada além dele. Isso é desenvolver orientado ao negócio, pois o mesmo é
o núcleo da sua arquitetura.

Figura 2: Onion Architecture


No projeto, essa estrutura talvez não se mostre desconhecida por você. Na imagem abaixo as
camadas são representadas.

Figura 3: Camadas representadas em pacotes


Cada pacote tem sua relevância dentro da arquitetura e abaixo descrevo um pouco melhor:

 View - Camada de interação com o cliente (endpoint, controllers);


 Application (orquestrador de domínio) - Responsável por facilitar o acesso ao domínio;
 Domain (atores) - Camada onde reside a lógica de negócio;
 infraestrutura (adaptador) - Provê acesso aos sistemas externos, configurações etc.

Como aplicar?
Agora que entendemos um pouco, e para muitos isso não era novidade, iremos aplicar o que foi
dito. Nada melhor do que construir um projeto de exemplo para ver a aplicação de todas as
técnicas.

Mãos na massa
Todo projeto inicia com uma conversa com o domain expert ou com o product owner. Como estou
acostumado com Kanban e Scrum irei adotar o termo P.O. (product owner) para me referenciar à
pessoa de negócio que tem o entendimento do produto que será desenvolvido.

Bora conversar com o P.O. e descobrir o que ele quer que seja feito.

P.O. apresentando o projeto: "Iremos desenvolver um sistema para melhorar as vendas e


assim substituir o legado. Nele será possível ao vendedor criar
a negociação de produtos utilizando nossa base de clientes.
Quando a negociação for concluída iremos gerar uma venda que irá para o sistema ERP, sistema
responsável por fazer o faturamento"
Tendo o que foi falado, iremos fazer um mapeamento básico das fronteiras do projeto, pelo
menos no primeiro momento, claro que no decorrer do desenvolvimento isso pode mudar e o seu
projeto tem que ser flexível o suficiente para suportar essas mudanças (para quem trabalha com
métodos ágeis nunca se esqueçam que ser ágil é responder rápido às mudanças).
Módulos do projeto

Até o momento isso foi o que conseguimos reproduzir a partir do que o P.O. falou. Pode
acontecer que no decorrer do projeto algumas fronteiras sejam alteradas, entretanto isso irá
depender de como o negócio irá evoluir e o quão claro ele é nesse momento.

Visto que nosso desenho comporta a estrutura apresentada pelo P.O., vamos replicar essa
estrutura em nosso projeto.
Cada uma dessas bolinhas será um módulo em nosso sistema e os módulos nada mais são do
que as fronteiras que delimitam os contextos (Bounded Context, SRP, Micro Service etc.), então a
forma mais simples de se trabalhar com módulos em qualquer linguagem que tenha o conceito de
pacotes é nomear os pacotes de acordo, veja o exemplo:

A estrutura da Onion Architecture nos dá um modelo para os módulos do sistema, nessa estrutura
eu tenho uma infraestrutura geral, ela é responsável por conter configurações dos frameworks
que serão utilizados no projeto.

Dentro de cada tema iremos tratá-los de forma separada e única, e é aí que está a grande
jogada, tratar no mesmo projeto temas variados. Permitir que que os temas se relacionem, sem
gerar um acoplamento forte e que permita que um módulo seja extraído para outro projeto
quando necessário.

Vamos começar por negociação e o P.O. veio falar com a gente: "Eu gostaria que
um vendedorconseguisse criar uma negociação de produtos para um cliente de nossa base e
essa negociação gerasse uma venda".
O desenvolvimento do módulo de negociação será a base para os demais e nele iremos aplicar o
máximo possível de técnicas de modelagem para tornar nossos módulos independentes, apesar
de estarem no mesmo projeto, mesmo jar, mesmo repositório no git. O mais importante é o KISS
(Keep it Simple, Stupid) e a medida que o negócio for crescendo, o mesmo irá sinalizar outras
necessidades. Complexidade não faz sentido no primeiro momento porque comprometeria a
velocidade do desenvolvimento e não sabemos se terá 1 ou 1 milhão de clientes usando,
contudo, como disse acima, temos que manter a capacidade de reagir às mudanças.
Iniciando a construção do módulo de negociaçã

Figura 6: Modelos de domínio do projeto


Quando o P.O apresentou esse módulo, ele mostrou quais eram os domínios e foram esses que
colocamos no domain.model no caso o Customer, Negotiation, Product e Seller.

Figura 7: Modelos de domínio protagonistas e coadjuvantes


Observe que o domínio tem dois tipos de representações:

 Protagonistas: São os modelos os quais têm seu ciclo de vida controlado pelo módulo, ou seja, o módulo
de negociação irá criar, atualizar e deletar uma negociação ou um item da negociação.
 Coadjuvantes: São modelos que aparecem no domínio porém não têm seu ciclo de vida controlado pelo
módulo, ou seja, eu não posso criar, atualizar ou deletar um Customer, Seller ou Product no módulo de
negociação. Esses domínios só tem seus IDs conhecidos, nada mais (faz sentido, não?).
Criando os repositories (ports)
Agora que temos nossos modelos de negócio precisamos ter a capacidade de listar, salvar,
deletar, e para isso usamos o repository. O repository é somente a interface dentro do nosso
domínio e é essa interface que será a porta (lembram do Ports and Adapters?) de comunicação
com o mundo externo, com isso ele se torna uma das partes mais importantes dentro da nossa
arquitetura.

Figura 8: Repository
Nesse momento temos a modelagem do Core Domain do nosso módulo (bounded context).

É muito importante notar que o DI (dependency injection) é fundamental para que essa estratégia
funcione sem gerar um acoplamento forte entre o domínio e a infraestrutura que conterá os
adapters (implementação dos repositories), isso ficará mais claro adiante.

Camada de Aplicação (Application layer)


Essa camada é conhecida como serviço, porém ela tem um conceito diferente do serviço que
conhecemos da arquitetura tradicional. A principal diferença está em como usar, no serviço
tradicional você pode colocar uma regra de negócio nela, porém aqui não. Uma regra de negócio
estará confinada somente ao domínio, que foi a camada que já desenvolvemos e que vamos
revisitá-la em breve.

Em OOP a interface define o comportamento que uma implementação deve ter e é por esse
motivo que eu coloco o nome da interface com o sufixo façade, porque o desenvolvedor irá olhar
a interface e entender que a implementação deverá se comportar como um façade (pelo menos é
isso que se espera). Um façade não tem regra de negócio e sua finalidade é facilitar o acesso a
algo, que no nosso caso é o domínio.
Figura 9: Interfaces de serviço
Agora que criamos nossas interfaces e classes da nossa Application vamos implementar os
primeiros métodos.

Figura 10: Negotiation Service


Observe que o domínio fala exatamente o que a pessoa de negócio (P.O.) diz. Isso é orientar o
desenvolvimento ao negócio e a application layer é onde irá aparecer o fluxo de negócio, é nessa
camada que o fluxo é desenhado. Então pense no domínio como as regras e na application layer
como os fluxos.

Continuando:

Figura 11: Negotiation Service - Busca de Negociações


Finalizamos a negociação e iremos implementar o Item da negociação:

Figura 12: Item Service


Isso é muito diferente de um simples CRUD, estamos desenvolvendo o projeto orientado ao
negócio e não à técnica, dessa forma a comunicação do código tem menos ruídos e suposições.
Dessa forma o nível de comunicação entre o time de desenvolvimento e o P.O. se torna fluida.
View Layer (Endpoints/Controllers)
Essa camada irá possibilitar o acesso às regras de negócio ao cliente da aplicação. A view é uma
camada que está no mesmo nível da infraestrutura, portanto ela pode acessar todas as camadas
abaixo: domain model, application service.

Figura 13: View Layer


O Endpoint irá expor um JSON e iremos usar REST usando o media-type application/hal+json, e
ter um media- type faz todo o sentido semântico. Observe o payload abaixo, nós temos as
informações sobre negociação, porém nesse módulo nós não temos informações sobre o
customer, apenas o ID, assim como o seller. Para o consumidor da sua api poder carregar as
informações de modelos que não são gerenciados pelo módulo de negociação ele deverá pegar o
link e buscar as informações no módulo responsável, dessa forma meu módulo de negociação irá
falar somente sobre negociação (princípio de responsabilidade única) e utilizar RESTFul, com um
nível de maturidade elevado, irá te ajudar a alcançar isso.

Figura 14: Endpoint - .../negotiations


Os itens da negociação estão em ItemEndpoint e os mesmos estão representados no JSON
abaixo:
Figura 15: Itens da negociação
Nós finalizamos o desenvolvimento do sistema, conseguimos testar (mockando os repositories) e
a única pessoa que escutamos foram as pessoas de negócio. Não falamos qual o banco de
dados iremos usar. Lembram que o P.O falou que iríamos substituir o legado? Então teremos que
usar uma base de dados já existente e dentro desse modelo de desenvolvimento não importa se
vou usar um banco orientado a relação, ou a documento ou a grafo. Isso acontece porque o
sistema passou a não ser orientado ao dado. Isso muda muita coisa.

Agora iremos entrar numa parte crucial para o projeto e essa parte se chama Infrastructure. Essa
é a camada que dá vida ao negócio, que permite ao sistema acessar coisas no mundo externo,
como um banco de dados, um serviço HTTP, SOAP etc.

Seguindo nosso projeto, vamos à empresa falar com o pessoal responsável pelo banco de dados,
afinal tudo terá que funcionar usando a estrutura já existente.

Sr. DBA qual a estrutura das tabelas de negociação?

R: Não existe esse termo em nossa estrutura de dados, porém existem 3 tabelas que fazem parte
da estrutura de pré pedido, imagino que essa seria a negociação que você está falando.
Figura 16: Tabelas de pré-pedido
Temos 3 tabelas que representam um pedido em um banco de dados relacional e não uma. Por
quê? O DBA tem que refatorar o banco? Não! Não importa como o dado está, talvez um dia isso
tenha feito todo o sentido do mundo e hoje não mais, porém fazer um refactoring no banco tem
um custo extremamente elevado.

É comum que muitas equipes culpem a estrutura de dados pelos seus erros arquitetônicos,
porque os sistemas são orientados ao dado, por esse motivo sempre iniciam o desenvolvimento
modelando tabelas num banco de dados.

Para completar o DBA disse que o item do pré pedido, que seria o item da negociação, está no
MongoDB.
Figura 17: Itens de pré pedido em documentos
Entendido como a estrutura de dados funciona vamos fazer essa estrutura funcionar com o nosso
domínio.

Infrastructure Layer (Adaptadores)


Eu falei que a Onion Architecture tem outros nomes e eu particularmente gosto de chamá-la de
Ports and Adapters. Em nossa implementação adicionamos a interface do repository no Domain
Model e essa interface representa as portas, agora na infraestrutura colocaremos os adaptadores
àquelas interfaces.

Na infraestrutura eu costumo criar essa estrutura de pacote onde tenho persistence, dentro desse
pacote coloco entities que são entidades do hibernate (ORM), springdata que são interfaces do
Spring Data e translate que são os caras que irão converter o modelo do banco para o modelo de
domínio. Como estou falando de um adaptador observe que o NegotiationRepositoryOracle
implementa a interface que está no domínio e Oracle no sufixo mostra qual o drive dela. Em item
acontece a mesma coisa. Como o hibernate abstrai os bancos, esse sufixo poderia ser trocado
para Hibernate.