BT

Construindo uma arquitetura corporativa de alto nível com a Onion Architecture

| por Marcelo Maico Seguir 2 Seguidores em 25 set 2018. Tempo estimado de leitura: 23 minutos |

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).

Figura 4: 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:

Figura 5: Pacotes do projeto

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 vendedor conseguisse 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ção

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.

Figura 18: Infrastructure Layer

Observe que na imagem abaixo eu tenho as entidades do hibernate que irão representar o mundo do banco de dados e as interfaces do Spring Data que irão fazer o fetch das informações. Dentro de Spring Data eu tenho Hibernate e Mongo.

Figura 19: Interfaces em springdata

Vamos ver a implementação do adaptador NegotiationRepository e logo percebemos que ele recebe os repositories (técnicos) e no método findOne eu recupero as informações das 3 tabelas e converto para o meu domínio. Isso é chamado de Anti Corruption Layer, por proteger o meu domínio da estrutura do dado. Imagine que o banco mudou e agora será tudo MongoDB o que precisaríamos fazer? Trocar a implementação (adaptador) de NegotiationRepositoryOracle para uma implementacao que use o MongoDB, simples não?

Figura 20: Implementação de repository - camada de anti corrupção

Assim como trocamos a placa de rede do nosso computador, e não o computador inteiro, para se comunicar via wireless ao invés de cabo, em nosso sistema seguimos o mesmo princípio.

É comum encontrar resistências com relação a essa arquitetura porque parece que o CRUD tradicional é mais rápido de ser desenvolvido, contudo ser mais rápido no início não significa que você se manterá rápido ao longo do projeto, isso porque as coisas vão se misturando e se tornando uma grande bola de lama. E dependendo do tipo de negócio as coisas tendem a mudar com grande velocidade, hoje estamos usando um banco relacional amanhã poderemos precisar de uma engine de dados diferente, e baseada em uma estrutura que dê uma performance melhor. Olhando os projetos que você trabalha hoje seria fácil trocar de uma estrutura relacional para uma orientada a Documentos, e sem que o negócio seja afetado por isso?

Mais uma vez digo: "Ser ágil é responder rápido à mudanças". Tenha isso como estilo de vida!

Comunicação entre módulos

Aprendemos a nos comunicar com o mundo externo, agora precisamos aprender a nos comunicar com os outros módulos, porque os módulos têm regras que devem ser seguidas, caso essas regras sejam violadas, o projeto se tornará um monólito com cara de algo modulável.

Imagine que ao salvar uma negociação eu precise fazer algumas validações e uma dessas validações é verificar se o vendedor está ativo, mas lembre que no módulo de negociação eu não tenho o status do vendedor, somente o ID, como faríamos isso?

Figura 21: Validador de negociação

Esse validador será chamado antes da criação de uma negociação, observe o repository e a DSL sobre o Seller, isso é minha DSL de negócio que irá perguntar para o repository se o vendedor está ativo ou não, caso ele não esteja irá retornar um Option empty.

O repository é uma coleção e podemos perguntar qualquer coisa a essa coleção, quando estamos operando sobre atributos de modelos (coadjuvantes) que não seja o ID temos que manter isso dentro da infraestrutura e somente perguntar ao repository como no exemplo:

sellerRepository.findOne(when(seller).isActive()).isPresent()

Quando o resultado é um Optional com um Seller com o id preenchido, dizemos que esse vendedor está ativo.

Vamos implementar a interface de Seller (adaptador)

Figura 22: Seller Repository

O SellerRepositoryModule recebe a injeção do SellerService que está no módulo de Seller. O sufixo module é para indicar que esse adaptador (implementação) se comunica com outro módulo do mesmo sistema.

Um serviço sendo injetado dentro de um repository? Sim!

Pode parecer estranho mas a implementação do meu Repository chama um serviço externo, que poderia ser um HTTP, SOAP, File System etc. Como está no mesmo projeto a forma mais fácil para eu chamar o serviço de Seller, que irá retornar um Seller, com todas as informações, inclusive o status de ativo ou não, é injetar na implementação do meu repository. E amanhã, se eu tirar o módulo de seller do projeto o que precisaria ser feito no módulo de negociação? Apenas remover o SellerService e trocar por uma chamada HTTP, por exemplo. Simple não?

Reactive (pub-sub)

Agora que sabemos como se comunicar com o mundo externo temos que entender que as chamadas diretas tornam nosso módulo menos resiliente e isso se resume a fragilidade. Nesse ponto iremos usar um pouco do poder do reativo para minimizar a dependência do módulo de negociação com o módulo de vendas.

Temos um requisito que é gerar uma venda após uma negociação concluída e para resolver esse problema iremos usar outra técnica.

A imagem abaixo mostra que estamos alterando o status da negociação, que pode ser frio, morno, quente ou concluído. Quando a negociação alcançar o concluído uma venda deverá ser gerada. A Venda está em outro módulo e se eu chamá-la eu tenho que representar a venda no domínio de negociação para justificar a criação de um repository. Outro ponto é, que se eu quiser, além da venda, gerar uma notificação, enviar uma e-mail etc, como ficaria?

É muito comum ver sistemas com a injeção de vários serviços, isso quebra a coesão, desrespeita o princípio de responsabilidade única e faz com que seu módulo seja lembrado, não só pelo tema dele, mas pelas diversas coisas que ele se tornou responsável.

Figura 23: Atualização de status de negociação

É nesse momento que ao invés de eu chamar o módulo de vendas para gerar uma venda ou o módulo de comunicação para enviar um alerta no admin, ou um e-mail, eu somente digo o que fiz. No código abaixo o sistema envia uma mensagem para o BUS dizendo que aquela negociação foi fechada com sucesso.

Figura 24: Disparando evento

A interface EventHandler está no dominio e a implementação, como sempre, está na infraestrutura.

Observe que estou usando o EventBus, porque o KISS tem que ser respeitado, como os módulos estão no mesmo projeto não preciso de um RabbitMQ, ActiveMQ ou qualquer outro sistema de mensageria. O EventBus está no pacote do Guava e o mesmo cria um BUS onde a mensagem passa, e o mesmo envia para os assinantes do tópico, para entender um pouco mais clique aqui

Figura 25: Implementação de disparo de eventos

O tópico é o objeto que está sendo disparado, nesse caso NegotiationClosedWon. O módulo de vendas tem um Subscriber para esse tópico e o framework é responsável por invocar o método passando o objeto que foi enviado.

Figura 26: Inscrição em eventos de negociação

Lembre-se que assim como o módulo de negociação não pode conhecer vendas, vendas não pode conhecer mais que o ID de negociação, então é o repository que irá fazer essa ponte e converterá a negociação em venda. Para mais detalhes baixe o projeto ou poste suas dúvidas nos comentários.

Observações finais

Desenvolver aplicações orientadas ao negócio, delimitando o contexto, minimizando o acoplamento e tudo isso no mesmo projeto não tende a ser uma tarefa fácil, pois demanda um bom conhecimento de todas as técnicas envolvidas. A vantagem é que você terá menos complexidade no início do projeto e tenderá a conhecer melhor os domínios e suas relações para quando chegar a necessidade de separar, por exemplo: poderíamos remover negociações e vendas e entregar para um time continuar o desenvolvimento. Essas possibilidades são essenciais e evita que iniciemos o projeto partindo de uma estrutura complexa de micro serviços em um momento que ainda temos dúvidas com relação ao negócio e suas subdivisões gerando assim os "micro monólitos" que é um termo pejorativo para aqueles micro serviços sem propósito.

Em resumo eu não preciso quebrar meu projeto de forma desnecessária para dizer que não estou produzindo um monólito. Apesar de estar no mesmo classpath eu posso dizer que o projeto é modular, mesmo não usando os recursos de módulo do Java 9.

E é modular porque eu consigo extrair partes e colocar em outros projetos, então vou dizer que isso é uma cultura de micro serviços.

Mantenha em mente que a base do projeto é determinante para o todo. Projetos bem estruturados tendem a dar ao desenvolvedor mais ferramentas para a resolução de problemas e tudo isso funciona como a teoria da casa limpa, se a casa está limpa você toma cuidados ao entrar e se está suja e bagunçada ninguém irá se preocupar com alguma coisa, ao entrar.

E por aqui termino e até a próxima.

Avalie esse artigo

Relevância
Estilo/Redação

Olá visitante

Você precisa cadastrar-se no InfoQ Brasil ou para enviar comentários. Há muitas vantagens em se cadastrar.

Obtenha o máximo da experiência do InfoQ Brasil.

Dê sua opinião

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

Correção by marcelo maico

No segundo trecho que código a linha(5) está com quebra de linha, o correto seria em uma única linha:
Boolean itWasChanged=negotiation.wasChanged().whenComparedWith(lastVersionAuditing);

Re: Correção by Júlia Fernandes Gonçalves

Arrumado Marcelo! Obrigada!

Boa cobertura by Alex Almeida

Não apresenta nenhum conceito desconhecido para uma galera de desenvolvedores, mas eu gostei da maneira que cobriu, desde o requisito até a implementação, agregando vários conhecimentos que as vezes vemos de forma isolada. Parabéns!

Muito interessante by Giovanni Candido

Gostei muito. Não conhecia essa arquitetura, ela vai de encontro ao que pratico e defendo que é não organizar por tecnica apenas mas por negócio. Uso o ECB (Entity Control Boundary) com várias idéias minhas pois o ECB é um pouco simplista.

Esse padrão + java 9 modules com uma boa visibilidade (além do private, protected e default) é uma abordagem muito interessante. Obrigado por compartilhar.

O que acha de a view ficar fora do pacote de negócio? Costumo chamar isso de controllers. Como faço aplicações mais orientadas para REST, entendo a comunicação com a view como uma camada mesmo. E chamo de view a interface como usuário que pode ser mobile, desktop, ou web e defendo que seja em tecnolgia distinta web como angular, mas tanto faz se é nativa o importante é ser serviço. Tem uma ligeira desvantagem de quando for quebrar a aplicação em microserviços, fica mais manual o processo de mover os controladores (denovo onion chama de view). No entanto tem a vantagens os modulos de negócio podem ser movidos (com uma certa facilidade) independentemente do framework.

Em uma experiencia real, movi um projeto de play framework para spring, que estava organizado nessa linha e foi relativamente muito fácil, trocando apenas os controladores e reaproveitando o negócio. Embora isso seja menos comum.

Re: Muito interessante by marcelo maico

Olá Giovanni,

Sim, com Java 9 você tem mais ferramentas para fechar as fronteiras e evitar que um módulo vaze para o outro.

Eu penso no módulo como uma unidade completa que é composta por input, processamento e output, dessa forma fica mais fácil levar o módulo para outro projeto beirando a um plug and play.

A view como você disse pode ter clientes com características distintas, e nesse caso eu não mexo no módulo e sim na arquitetura. Por exemplo, imagine que eu tenha que fechar uma venda e mandar um e-mail, eu tenho o módulo de venda e um módulo de comunicação, então criaria um módulo fechar-venda e nele teria todo o processo para fechar uma venda, mas, esse módulo seria orquestrador, ele chamaria os módulos de venda e comunicação e o que mais for necessário.

No caso da leitura atualmente eu uso o GraphQL para permitir que diferentes dispositivos tenham consumos personalizados, assim a URA, mobile, web compartilham da mesma fonte.

Mas, em uma arquitetura mais simples eu não vejo problemas em criar seus endpoints e/ou controllers dentro da view, específico para o tipo de cliente que irá consumir, pois a camada view está no mesmo nível da infraestrutura, em uma eventual locomoção do módulo a view assim como a infra são suscetíveis a ajustes.

Obrigado pelo apoio. Abraço!

Listas com múltiplas informações by WiLL Stenico

Muito bacana o artigo e muito bem explicado. Parabéns.

Uma dúvida, como eu implementaria a solicitação abaixo:

-"Crie uma lista exibindo o nome e o código dos vendedores e a quantidade de cada produto que cada um vendeu."

Seria muito tentador criar um método com uma query dando join entre as tabelas misturando os domínios.

Re: Listas com múltiplas informações by marcelo maico

Olá, Will.

O problema que você expôs tem uma característica cross domain.

Os domínios concentram as regras de negócio e essas regras são verificadas no momento que o dado é criado ou alterado. A leitura tem uma característica distinta, porque o dado nesse momento já passou pelas regras de negócio e ele precisa ser consultado. Você precisa de flexibilidade dos dados para resolver questões como: todas as vendas por período, todas as vendas por região, todas as vendas de um vendedor por produto etc.

No seu problema o sistema teria os módulos de vendas, vendedor e produtos, pelo menos, os três poderiam indexar suas informações usando uma estratégia de database report ou data lake e assim a obtenção dos dados seria por uma simples query(em um módulo de leitura), como você bem disse.

Para sistemas onde tudo está numa única base dados você só precisa separar essa leitura em um módulo distinto, destinado a retornar dados sumarizados e esse deve ser o mais simples possível, visto que é somente leitura e tem uma quantidade enorme de possibilidades de variações.

Obrigado pela pergunta. Abraço!

Re: Listas com múltiplas informações by WiLL Stenico

Muito obrigado pela Resposta Marcelo! Abração

Re: Muito interessante by Giovanni Candido

Me referi a tirar a view do módulo e deixar apenas negócio, mas entendi sua resposta.

Obrigado.

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

9 Dê sua opinião

Faça seu login para melhorar sua experiência com o InfoQ e ter acesso a funcionalidades exclusivas


Esqueci minha senha

Follow

Siga seus tópicos e editores favoritos

Acompanhe e seja notificados sobre as mais importantes novidades do mundo do desenvolvimento de software.

Like

Mais interação, mais personalização

Crie seu próprio feed de novidades escolhendo os tópicos e pessoas que você gostaria de acompanhar.

Notifications

Fique por dentro das novidades!

Configure as notificações e acompanhe as novidades relacionada a tópicos, conteúdos e pessoas de seu interesse

BT