DEV Community

Cover image for Arquitetura orientada ao Ator
Leonardo Bonetti
Leonardo Bonetti

Posted on

Arquitetura orientada ao Ator

Propor um modelo de arquitetura de software que orienta o sistema a seus utilizadores.

Apesar de parecer redundante dizer que um sistema deve ser desenvolvido a partir da orientação de seus utilizadores, um dos maiores problemas do processo de desenvolvimento é que os programadores tendem a criar mecânicas que façam sentido no ponto de vista de quem desenvolve, mas não necessariamente no ponto de vista de quem o utiliza, essa prática leva ao processo comum de utilizadores não se adaptarem a mecânicas que parecem ser simples e intuitivas.

O modelo descrito a seguir extende os conceitos definidos por Robert C. Martin em Clean Architecture: A Craftsman’s Guide to Software Structure

Atores

Atores são basicamente quem stabelece quais regras o sistema deve cumprir e qual comportamento esperado, antes de começarmos a pensar em desenvolver nossa aplicação, temos de estabelecer quem são seus atores, eles podem ser usuários tradicionais ou outros sistemas.

Atores são todos que interagem com a aplicação.

Para desenvolvermos esse raciocinio, vamos propor um sistema que iremos construir ao decorrer deste artigo, a aplicação escolhida é um simples sistema de loja.

Normalmente quando iniciamos a produção de um sistema, nos questionamos sobre as funcionalidades que ele terá, no modelo A.O.A, vamos começar com uma pergunta diferente:
Quem vai utilizar esse sistema?

  • Consumidor comum
  • Gerente
  • Operador

Aqui já estabelecemos a quem o sistema interessa, agora Por que o sistema interessa a esses atores?
Consumidor Comum (Costumer)

Ele quer acessar o sistema para olhar os catálogos e comprar itens, podemos dizer que ele é nosso ator mais importante, é o motivo do sistema existir.

Operador (Operator)

Ele preenche nosso sistema com as informações que serão disponibilizadas ao Costumer, adiciona produtos e atualiza preços.

Gerente (Manager)

Ele é a entidade que existe para controlar as propriedades do sistema, ele adiciona novos operadores e pode alterar as taxas impostas por compras.


O processo de se estabelecer os atores do sistema é com certeza o mais demorado e de certa forma caótico deste modelo de arquitetura, pois todo o resto do sistema será orientado a isso, então tenha certeza de gastar um bom tempo com o P.O do projeto ou seu cliente diretamente para entender todas essas peculiaridades da aplicação, atores definem as funções assim como normalmente contradizem elas.

Imaginem que o recebemos as seguintes definições:

  • O gerente pode alterar a taxa de compra de todos os consumidores
  • O operador pode alterar a taxa de compra de consumidores específicos

Temos aqui duas solicitações conflitantes de atores diferentes, pois se o gerente pode alterar todas as taxas ele tem o poder completo sobre o conjunto, mas o operador tem o poder sobre um elemento do conjunto, dessa forma que descrevi podemos interpretar que o maior poder é o gerente então em caso de conflito, ele teria a prioridade, mas se pensarmos que o poder não vem de quem tem o controle sobre o todo e sim a maior precisão, então em caso de conflito o operador que venceria.

Existem diversas formar de se resolver o problema acima, mas todas elas exigem que o arquiteto crie regras de negócios e a não ser que você seja o arquiteto do sistema e o dono do negocio, não se deve fazer isso para resolver um problema técnico.

Sendo assim, esse problema deve ser identificado pelo arquiteto, mas devolvido ao P.O para que se crie uma regra que resolva a disputa, geralmente o melhor jeito de se resolver esse tipo de disputa é não deixar com que dois atores interagem da mesma forma com uma mesma entidade

Entidades

Entidades são os elementos internos de seus sistemas, seus modelos de dados.

Agora que já sabemos quem são nossos atores, podemos definir quem são nossas entidades a partir das funções que os atores precisam realizar em nosso sistema.

Vamos fazer uma análise de nosso primeiro ator e o quais entidades ele precisa:

Costumer

  • Tem acesso ao catálogo de produtos
  • Adiciona itens a seu carrinho de compras
  • Compra um produto

A primeira funcionalidade é uma visualização, e a entidade necessária para realizar a mesma está em sua citação:

  • Produto

Já a segunda funcionalidade nos descreve uma entidade também em sua citação:

  • Carrinho de compras

Porem um carrinho de compras naturalmente precisa estar atrelado a algo, normalmente a um usuário, e assim chegamos a conclusão de uma entidade ainda mais primitiva, a representação do Ator dentro do sistema, o Costumer se torna um usuário.

Atenção: O exemplo acima não estabelece que todos os atores tem uma entidade apenas para sí no sistema, apenas que esse ator em especifico precisa de uma representação, mas que também pode ser a representação de outros atores.

A terceira funcionalidade pode ser resolvida com as entidades já presentes?

Vamos analisar o caso, temos então três entidades:

  • Produto
  • Carrinho de compras
  • Usuário

Uma compra pode ser descrita como produtos em um carrinho de compras pertencentes a um usuário que realiza um pagamento mediante ao valor somado dos produtos.

Analisando a citação, temos uma ação e exatamente três objetos que são nossas entidades.

A ação ainda não era conhecida, mas agora sabemos que pagar é uma funcionalidade do Ator Costumer, então podemos adicionar a nossa lista de funcionalidades desse ator.

Vamos analisar o caso do Operador

Ele pode:

  • Cadastrar novos produtos
  • Alterar o valor dos produtos

Ja temos a entidade de produto, mas é necessário saber quem é o operador para permitirmos as operações, e também já temos uma entidade primitiva que pode servir a esse propósito, a entidade Usuário.

Ou seja, não é necessário criar mais nenhuma entidade para o operador.

O Gerente pode:

  • Adicionar novos operadores
  • Mudar taxas de compras

Para a primeira funcionalidade, já sabemos que o operador é representado pela entidade usuário, sendo assim o gerente vai interagir com ela para adicionar novos operadores.

Para alterar as taxas de compras vamos precisar de um entidade que defina esses valores, podemos por enquanto defini-la como propriedades do sistema.

No final estabelecemos as seguintes entidades:

  • Usuário
  • Carrinho de compras
  • Produtos
  • Propriedades do sistema

Domain

Já entendemos quem são nossos atores e entidades, vamos agora colocar isso em nosso sistema e essa definição é feito na primeira camada chamada Domain.

A proposta dessa camada é criar as definições em níveis de código que será estendidas a outras camadas que atendam as mesmas, para este artigo não vou colocar trechos de códigos, pois a proposta é que uma arquitetura de software não dependa do ambiente ou tecnologia para ser aplicada, sendo assim, sinta-se livre para adaptar os exemplos em sua linguagem de preferência.

A estrutura de arquivos de domain ficaria da seguinte forma:

  • Domain
    • Entities
      • User
      • Shop Cart
      • Product
      • SystemProperties
    • Actor
      • Costumer
        • Get catalog
        • Add product to cart
        • Remove product from cart
        • Buy
      • Operator
        • Add product
        • Change product
      • Manager
        • Add Operator
        • Change fees

É importante ressaltar que dentro de domain não temos nenhum processo lógico, apenas interfaces que terão de ser respeitadas por outras funções que as implementem.

Dessa forma, qualquer desenvolvedor pode pode compreender as responsabilidades de cada usuário do sistema e como as mesmas se comportam, mas onde estariam os processos lógicos para fazer as funções do domain funcionarem?

Composer

A camada de composição é onde implementaremos as interfaces descritas acima e iremos compor a lógica de nossas funções.

Essas funções tem diversas sub-responsabilidades, como em Costumer → Add product to cart, temos de:

  • Buscar o produto
  • Validar as quantidades disponíveis dos mesmos
  • Adicionar ao carrinho
  • Atualizar valor total do carrinho

Por isso essa camada se chama Composer, ela faz a composição dos módulos do sistema e ao mesmo tempo também declara como esses módulos devem se comportar, pensando nisso, alem de criarmos os usecases das funções relativas aos atores, temos de criar os protocolos (interfaces) das funções relativas aos módulos nesta camada, a estrutura de composer seria:

Composer

  • Protocols
    • Get product
    • Check product amount
    • Add to cart
    • Update cart price
    • ... (Todas as interfaces dos módulos necessários pelos compositores)
  • Costumer

    • Get catalog
    • Add product to cart
    • Remove product from cart
    • Buy

    ... (Continuar replicando os casos de uso de cada ator)

Agora devemos criar os módulos que atendam a necessidade de nossos compositores, já temos suas interfaces e sua lógica deve ser descrita na próxima camada

Modules

Um módulo é a menor parte executável de um sistema, geralmente caracterizado por um processo que pode ser utilizado por mais de um compositor, pegando o mesmo exemplo do Get product, isso é uma função que pode ser reutilizada em casos de uso pertencentes ao Usuário comum ou ao operador.

Diferentes módulos não devem implementar a mesma interface, digamos que tanto o Usuário comum quanto o Operador dependam de um modulo de buscar produto, porem o operador deve buscar esse produto da base de dados A e o Usuário comum deve buscar o produto da base de dados B, nesse caso o ideal é criarmos dois protocolos a mais em Composer, lá já temos estabelecido o protocolo de buscar produto e devemos estende-lo a interface Buscar produto de A e Buscar produto de B.

Tecnicamente não é problema dois módulos diferentes utilizarem a mesma interface, porem a diferenciação teria de ser feito nas Factories dos compositores, e uma boa arquitetura de software visa minimizar as possibilidades de erros, clarear o entendimento do sistema por seus desenvolvedores e facilitar a adaptação do sistema a mudanças. Não diferenciar a interface de dois módulos prejudica tanto a compreensão do sistema quando a possibilidade de ocasionar erros, pois digamos que um desenvolvedor precise fazer a alteração do compositor que utiliza o modulo de Buscar produtos de A, mas como a interface é a mesma ele se distrai e coloca o modulo de Buscar produtos de B, o sistema não vai reclamar, mas o P.O vai, por seu código não estar funcionando.

Infra

A camada de infraestrutura é onde vamos conectar nosso sistema as dependências necessárias para fazermos o mesmo funcionar, é a camada mais externa da nossa arquitetura.

Nosso módulo Get product from A precisa de um banco de dados para realizar essa busca, e o driver que irá conectar os dois está presente nessa mesma camada, neste caso o módulo em questão se torna também um adaptador ao banco de dados, pois qualquer alteração necessária no banco de dados vai gerar uma alteração no módulo que importa as propriedades de infraestrutura, mas não vai impactar de forma alguma nossa camada de composição que detém a regra de negócio do sistema.

Digamos que nosso sistema seja uma API Rest e precisamos de um framework para expor endpoints para os acessos do sistema pelos atores, o framework em si estará presente em infra.

Main

Esse parte é opcional e depende de como cada tecnologia se comporta, mas precisamos fazer essas camadas conversarem entre si e adicionarmos algum ponto de partida para nosso sistema ser iniciado.

Caso você esteja utilizando uma linguagem O.O e trabalhando com classes, precisa injetar as dependências necessárias nas classes dos compositores e isso normalmente é feito por factories, idealmente essas instruções estariam na camada Main que inicia o sistema.

Este é um trabalho ainda em desenvolvimento, então se chegou até aqui e quiser contribuir, me envie uma mensagem por aqui mesmo ou deixe um comentário, ficarei muito grato por qualquer esforço, de qualquer forma, muito obrigado pela atenção

Top comments (0)