Livro de receitas do desenvolvedor: DDD Recipes (Parte 3, Arquitetura de aplicativos)

1. Introdução


Em artigos anteriores, identificamos o escopo da abordagem e examinamos os princípios metodológicos básicos do Design Orientado a Domínios .


Neste artigo, gostaria de descrever as principais abordagens modernas para a construção da arquitetura de sistemas corporativos: flexível, gritante, limpa e dar a eles minha interpretação clara na forma de uma solução completa e pronta para uso.


WM


No futuro, consideraremos cada padrão de design em detalhes: indicamos o escopo, fornecemos exemplos de código, destacamos as práticas recomendadas. Como resultado, escreveremos um microsserviço pronto.


Arquitetura flexível


No último artigo, focamos no fato de que o DDD inclui a prática de implementação por meio do modelo. A área de assunto deve ser descrita através do seu código. Vamos tentar descobrir como fazer isso.


Em seu livro, Eric Evans fornece uma série de padrões de design recomendados e designa essa abordagem como flexível:


Em nome da flexibilidade da arquitetura, muitas construções desnecessárias foram empilhadas em programas. Níveis excessivos de abstração e vinculação indireta são mais propensos a interferir do que a ajudar nesse assunto. Observe a arquitetura que realmente inspira os programadores a refiná-la e você geralmente verá algo muito simples. Mas simples não significa fácil de executar. Para criar esses elementos que podem ser montados em sistemas complexos e, ao mesmo tempo, é fácil de entender, é necessário combinar "dedicação" ao design de acordo com o modelo com um estilo de arquitetura bastante rigoroso. Uma certa habilidade de design é necessária não apenas para criar algo, mas também para usar o acabado.

Eric Evans, Design Orientado a Domínios: Lidando com a Complexidade no Coração do Software

O conjunto apresentado de padrões de design não é uma arquitetura rigorosa ou uma solução pronta, mas um alimento para reflexão.


Arquitetura chamativa


Pensamentos semelhantes surgiram na mente de muitos desenvolvedores e designers de sistemas complexos.


Em 2011, foi publicado um artigo de Robert Martin - Screaming Architecture , que diz que seu código não deve apenas descrever a área de assunto, mas sim gritar, de preferência obscenamente.


Então, o que a arquitetura do seu aplicativo grita? Quando você olha para a estrutura de diretórios de nível superior e os arquivos de origem no pacote de nível superior; eles gritam: Sistema de Saúde, Sistema de Contabilidade ou Sistema de Gerenciamento de Estoque? Ou eles gritam: Rails, Spring / Hibernate ou ASP?

Robert C. Martin, 30 de setembro de 2011

Robert diz que o código do aplicativo deve refletir a atividade do aplicativo, em vez de se adaptar às regras da estrutura. A estrutura da estrutura não deve limitar sua arquitetura. O aplicativo, por sua vez, não deve ser anexado ao banco de dados ou ao protocolo http, são apenas mecanismos de armazenamento e entrega. A caixa delimitadora é uma ferramenta. Você não deve se tornar um aderente à estrutura. Os testes do seu aplicativo são testes da lógica de sua operação e não estão testando o protocolo http.


Arquitetura limpa


Um ano depois, o próximo artigo de Robert Martin - The Clean Architecture . Nele, o autor conta como fazer o código gritar. Tendo estudado várias arquiteturas, ele identifica os princípios básicos:


  1. Independência de quadros. A arquitetura não depende de nenhuma biblioteca existente. Isso permite que você use estruturas como ferramentas, em vez das restrições que prendem suas mãos.
  2. Testabilidade. As regras de negócios podem ser testadas sem uma interface de usuário, banco de dados, servidor da Web ou qualquer outro meio técnico.
  3. Independência da interface do usuário. A interface do usuário pode ser facilmente alterada sem alterar o restante do sistema. Por exemplo, a interface da web pode ser substituída pela interface do console sem alterar a lógica de negócios.
  4. Independência do banco de dados. Você pode trocar o Oracle ou o SQL Server pelo Mongo, BigTable, CouchDB ou qualquer outra coisa. Sua lógica do aplicativo não deve ser vinculada ao banco de dados.
  5. Independência de influências ambientais. De fato, suas regras de negócios simplesmente não sabem nada sobre o mundo exterior.

Em um habr já publicou um artigo muito bom Misconceptions Clean Architecture . Seu autor, Jeevuz , muito bem mastigou os meandros da compreensão dessa abordagem. Eu recomendo fortemente que você se familiarize com ele e com os materiais originais.


Arquitetura Variável


A descrição da abordagem acima não parece tão direta. Como parte do desenvolvimento da arquitetura de vários sistemas corporativos complexos, meus colegas e eu desenvolvemos uma interpretação bastante clara das abordagens descritas, que pretendo apresentar abaixo.


Antes do advento dos computadores e linguagens de programação, o fluxo de trabalho em papel era usado para criar e gerenciar sistemas com lógica de negócios complexa. O resultado de qualquer processo foi um documento que finalmente descreveu um objeto de negócios específico. Como resultado, a papelada se resumiu a três ações simples:


  1. Criação de documento
  2. Processamento de documentos
  3. Trabalhar com o arquivo de documentos
  4. Envio de documentos

Documento - registrando informações sobre atividades econômicas de um objeto de negócios real específico.

Observe que o documento em si não é um objeto de negócios real, mas apenas seu modelo . Atualmente, os documentos em papel estão sendo substituídos pelos eletrônicos. Um documento pode ser um registro em uma tabela, uma figura, um arquivo, uma carta enviada ou qualquer outra informação.
Eu não gostaria de usar a palavra documento no futuro, uma vez que será mais confuso, usaremos o conceito de Entidade da terminologia DDD. Mas você pode imaginar que agora todo o seu sistema é um sistema de gerenciamento eletrônico de documentos que executa quatro ações simples.


  1. Coletando
  2. Processamento
  3. Armazenamento
  4. Representatividade

Ação - uma unidade estrutural de atividade de um modelo de negócios; um ato separado relativamente concluído de uma meta consciente, a arbitrariedade e a intencionalidade da atividade individual de um objeto de negócios, distinguida pelo usuário final.

Um bom exemplo de ação é um ato teatral. O teatro modela eventos da vida real. O ato é uma parte significativa da peça. Mas, para completar a história, você precisa fazer vários atos em uma ordem estritamente designada. Tal ordem em nossa arquitetura, chamaremos de Mode .


Modo (Condução) - um conjunto de ações em uma determinada ordem, com um significado completo, benéfico para o usuário final.

condução


Para tais modos de operação, um condutor ou seletor seletivo foi inventado. Mais precisamente, "Mecanismo de tempo para conduzir um selecionado de uma pluralidade de sequências de operação", para o qual a patente US2870278A foi obtida. Conhecemos esse dispositivo como o "toque" da máquina de lavar. A torção arquitetônica é dada no início do artigo.


A variabilidade da abordagem se manifesta no fato de que, com essa arquitetura, você pode escolher qualquer um dos quatro modos , passando os quais você não executará ações desnecessárias.


Ao iniciar a máquina de lavar, você pode selecionar o modo: lavar, enxaguar ou fiar. Se você optar por lavar, sua máquina ainda enxaguará a roupa e a apertará. Com o enxágue no kit, você tem certeza de dar uma volta. Spin - a ação final no processo de lavagem, é a mais "simples". Em nossa arquitetura, a Ação mais simples é a Representação , e começaremos com ela.


Representação


Se falamos de uma apresentação pura, sem recorrer a um banco de dados ou a uma fonte externa, fornecemos algumas informações estáticas: uma página html, um arquivo, um diretório na forma de json. Podemos até fornecer apenas a resposta do código - 200:


Vamos escrever o "verificador de saúde" mais simples


module Health class Endpoints < Sinatra::Base get '/check' do; end end end 

Em sua forma mais primitiva, nosso esquema terá a seguinte aparência:


Representatividade


Digressão lírica

Peço que você observe que, na estrutura do Sinatra, a classe Endpoints combina roteador e controlador em uma classe. Isso viola o princípio da responsabilidade exclusiva? De fato, os Endpoints não são uma classe, mas uma camada expressa por meio de uma classe e sua área de responsabilidade em um nível superior.


Ok, e o roteador e o controlador ? Eles são representados não por um conjunto de classes, mas pelo nome e implementação de uma função. Um arquivo estático geralmente é um arquivo. Uma classe é responsável por uma responsabilidade, mas não tente expressar cada responsabilidade por meio de uma classe. Use praticidade, não dogmatismo.


Trabalhar com sistema de armazenamento


Os negócios exigem a disponibilidade do seu aplicativo. Por que alguém precisaria do seu serviço se não podemos usá-lo no momento certo? Para garantir a integridade dos dados, registramos uma alteração no estado de um objeto de negócios após cada processamento.


Recuperar um objeto do armazenamento não requer acesso à lógica de negócios. Imagine que automatizamos as atividades de uma cadeia de hotéis e temos uma revista para convidados na recepção. Decidimos ver informações sobre o visitante.


  module Reception class Endpoints < Sinatra::Base # Show item get '/residents/:id', provides: :json do resident = Repository::Residents.find params[:id] status 200 serialize(resident) end end end 

Trabalhe com o sistema de armazenamento na forma de um diagrama gráfico:


Armazenamento


Como podemos ver, a comunicação entre a camada responsável pelo armazenamento e a camada responsável pela apresentação de dados é implementada por meio do modelo de resposta. Este modelo não pertence a nenhuma dessas camadas. De fato, este é um objeto de negócios e está localizado na camada responsável pela lógica de negócios.


Processamento


Se o modelo de objeto for alterado com base em suas propriedades sem a introdução de novos dados, passamos à camada Interator diretamente. A camada Interator é a chave em nosso aplicativo, é nela que toda a lógica de negócios é descrita na forma de Casos de Uso separados, e é nela que as Entidades mudam.


Considere este caso de uso. O visitante já está registrado em nosso hotel, mas comemoramos todas as suas chegadas ou partidas.


  module Reception class Endpoints < Sinatra::Base # Register resident arrival post '/residents/:uid/arrival', provides: :json do result = Interactors::Arrival.call(resident_id: params[:id]) check!(result) do status 201 serialize result.data end end # Register resident departure post '/residents/:uid/departure', provides: :json do result = Interactors::Departure.call(resident_id: params[:id]) check!(result) do status 201 serialize result.data end end end end 

Vamos parar um pouco. Por que não tornar a implementação um método único com o parâmetro status ? Os interatores de Arrival e Departure são fundamentalmente diferentes. Se um hóspede vier até nós, devemos verificar se a limpeza terminou, se houve novas mensagens para ele etc. Com a sua partida, nós, pelo contrário, devemos iniciar a limpeza, se necessário. Por sua vez, nem nos lembramos das mensagens, porque se ele estivesse em um hotel, teríamos telefonado imediatamente. É toda essa lógica de negócios que prescrevemos na camada Interator .


Processamento


Mas o que devemos fazer se tivermos dados externos? Aqui a ação da coleta de dados está conectada.


Coletando dados


Durante o registro inicial de um hóspede no hotel, ele preenche o formulário de registro. Este formulário está sendo verificado. Se os dados estiverem corretos, o processo de negócios Registro ocorrerá. O processo retorna dados - o modelo de negócios "inquilino" criado. Apresentamos esse modelo para o convidado de forma legível:


 module Reception class Endpoints < Sinatra::Base # Register new resident post '/residents', provides: [:json] do form = Forms::Registration.new(params) complete! form do check! form.result do status 201 serialize form.result.data end end end end end 

Esquematicamente, fica assim:


Coletando


Regras do jogo (Regras)


  • O sistema variador do ponto de vista dos processos é dividido em ações .
  • A sequência de ações é determinada pelo modo .
  • Os modos são incrementais.
  • Um modo mais "complexo" complementa um modo mais "simples" por uma ação estritamente.
  • Cada ação ocorre dentro da estrutura de uma camada .
  • Cada camada é representada por uma classe .
  • Dentro de uma camada, pode haver classes de camada e classes de responsabilidade .
  • A comunicação ocorre apenas entre a camada e a classe entre camadas .
  • Modelos de representação são exceções.
  • A manipulação de erros deve ocorrer no nível da camada de classe .

Árvore


Esquema geral


Essa abordagem possui um alto limite de entrada. Sua aplicação requer muita experiência do designer para uma compreensão clara das tarefas a serem resolvidas. A complexidade também representa a variedade de opções da ferramenta necessária. Mas, apesar da complexidade da estrutura, a implementação no nível do código é incrivelmente simples e expressiva. Embora contenha várias convenções e procurações. No futuro, analisaremos cada modelo de design individualmente, descreveremos como criá-lo, testá-lo e designar o escopo. E, para não se confundir em sua diversidade, é oferecido um mapa completo:


Mapa de alta resolução




Fontes de inspiração

Source: https://habr.com/ru/post/pt429750/


All Articles