Como representar relações entre múltiplos objetos sem raiz agregada óbvia

5

Digamos que temos três tipos de objetos:

  • Agentes
  • Chamadas
  • CallsQueues

Todos eles podem ser vinculados entre si (1 a 1, 1 a M, MtoM) ou não têm links, e esses links mudam com o tempo. As chamadas aguardam em CallsQueues por um Agente livre, cada Agente atende uma Chamada por vez e os Agentes aguardam por chamadas em zero, um ou vários CallQueues ao mesmo tempo.

De diferentes perspectivas na aplicação, cada tipo pode ser o tipo de raiz. Por exemplo, um AgentMonitoringController exibe uma lista de agentes com suas chamadas atuais e os CallQueues em que o agente está, e o QueuesMonitoringController faz o mesmo com filas que os objetos raiz.

Quais opções existem para representar links entre os objetos? As opções que tenho em mente:

  1. Faça com que cada objeto digite uma classe. Além disso, crie as classes AgentToCallLink, CallToCallQueueLink e AgentToCallQueueLink, que manteriam referências a instâncias concretas das classes, como em relacionamentos muitos-para-muitos em um banco de dados relacional. Armazene listas de referências às classes de link em ambas as classes de entidade.
  2. O mesmo que em 1, mas em vez de criar classes de link, armazene apenas listas de referências a instâncias concretas (o que elimina a possibilidade de inserir informações adicionais em links, como datetime de criação).

ORM e qualquer outro tipo de persistência não são considerados.

Além disso, a partir de qual "coleção" esses objetos devem ser buscados para manipulação? O repositório por tipo é uma boa ideia (por exemplo, AgentsRepository , CallsRepository , CallsQueuesRepository )?

    
por G. Kashtanov 31.05.2018 / 20:27
fonte

2 respostas

5

IMHO você deve começar por acertar o modelo primeiro. Eu não esperaria que um Call ocorresse em mais de um CallQueue de cada vez, portanto, essa é apenas uma relação 1:M . Além disso, como um Agent pode apenas chamar um servidor de cada vez (como você escreveu), esse é um relacionamento 1:1 . A única relação M:M que vejo é aquela entre agentes e filas.

Sua questão principal, no entanto, permanece a mesma (exceto em sua opção 1, há apenas uma classe de link necessária, não três). A resposta para isso se resume a qual nível de ignorância de persistência você quer ou precisa alcançar.

Para um modelo totalmente ignorante de persistência, você pode tentar modelar um Call sem nenhuma dependência para uma entidade CallQueue e sem nenhuma dependência para uma entidade Agent . Esta é principalmente sua opção 2. Por exemplo, as filas farão referência a suas chamadas por algumas listas internas, os Agentes terão uma referência à chamada que eles atendem no momento, e os agentes e filas podem referenciar-se uns aos outros por listas também. Obviamente, se você precisar de atributos adicionais para o link entre agentes e filas, será necessária uma classe como AgentToCallQueueLink .

Uma vantagem disso é evitar dependências cíclicas (por exemplo, entre Chamadas e CallQueues), o que torna as coisas como testes de unidade um pouco mais fáceis.

No entanto, a ignorância da persistência não vem de graça:

  • alcançar a persistência é um pouco mais difícil

  • trabalhar com subconjuntos isolados dos dados (unidades de trabalho) é mais difícil, pois isso pressupõe implicitamente que há um armazenamento persistente no plano de fundo mantendo os dados fora da unidade de trabalho atual

  • obter as transações corretas em um ambiente concorrente é um pouco mais difícil (especialmente se você não quiser adicionar o CQRS ao seu sistema)

Por exemplo, modelando um objeto Call com uma chave primária CallID e um atributo de chave estrangeira CallQueueID , sabendo que ele será armazenado dessa maneira em um banco de dados, será um pouco mais fácil extrair um alguns Chamar objetos de uma fila do banco de dados, manipular a fila isoladamente e atualizar o conteúdo original de CallQueue sem a necessidade de excluir e reescrever todas as chamadas da fila. No entanto, agora você introduziu uma dependência cíclica entre CallQueues e Call s em seu modelo de domínio (o que pode ser perfeitamente aceitável, já que na verdade é apenas uma dependência fraca por IDs, não mais).

No passado, eu costumava usar os dois: classes com todos os ids de chave estrangeira como atributos em uma correspondência de 1 para 1 para as colunas no banco de dados e, adicionalmente, listas de referências, que permite escrever código mais conciso. É uma informação redundante, é claro, mas essa redundância é boa desde que as chaves sejam imutáveis e a alteração de uma referência a outra instância seja proibida ou encapsulada corretamente.

Então, no final, é um trade-off. Se você precisar de persistência e não precisar de ignorância de persistência, escolha a opção 1. Um repositório por tipo será ok. Seu mapeamento do modelo de domínio para um banco de dados será mais simples. E se você considerar usar um ORM, este será um ajuste natural (se você modelar suas classes diretamente de uma maneira que elas se ajustem às restrições que o ORM impõe a elas).

No entanto, se você acha que realmente precisa de ignorância persistente, escolha a opção 2, mas não fique surpreso se persistência, lidar com unidades de trabalho sobrepostas e manuseio transacional se tornarem mais difíceis. A opção 2 geralmente é mais adequada para modelos em que não é necessária persistência ou a única maneira de persistência necessária é carregar e salvar todos os dados de um agregado de uma só vez, sem gravadores simultâneos. Para esse uso, um repo para carregar e salvar tudo pode estar ok.

    
por 01.06.2018 / 10:54
fonte
1

Quase sempre optaria pelo modelo ignorante de persistência (Opção 2), devido ao princípio da separação de interesses , mas depende da implementação muitas vezes . Por exemplo, se você estiver usando um determinado Framework de persistência que, por padrão, vem com seu próprio padrão MVC e em que Models também são construtores de consulta (entidades típicas que reconhecem a persistência), faz sentido 1 .Mas não exatamente como você está apresentando, com uma classe para todos os links, isso não faria sentido.

Além disso, a opção 2 não é uma receita sempre assim, , você não pode fazer isso se precisar armazenar alguns atributos no relacionamento. MAS isso não implica que você não poderia misturar os dois em um modelo ignorante de persistência, criando classes intermediárias apenas quando for necessário fazê-lo.

Finalmente, eu faria o seguinte (mas é apenas uma escolha pessoal):

  • Ligue conhece o Agente com uma referência à sua instância e também conhece o seu CallQueue atual.

  • CallQueue não conhece nenhuma chamada nem contém qualquer lista de referências de qualquer tipo. Eu acho que isso poderia levar a um código bagunçado. Se você quiser saber, por exemplo, a lista de todas as chamadas pertencentes a uma determinada fila, você conseguiria isso em seu PersistenceFacade, consultando seu banco de dados para todas as chamadas apontando para esse CallQueue .

  • Estritamente falando, não há necessidade de o Agente conhecer qualquer instância CallQueue . A instância Call conhece o Agent e o CallQueue, portanto, tecnicamente, você seria capaz de navegar nesse relacionamento. Isso complicará as coisas quando você quiser saber, por exemplo, em quais filas um determinado agente está. Mais consultas para fazer, preenchendo uma lista, etc. Tenha em mente que é um relacionamento N para N , então você não pode ter uma única referência CallQueue no Agent, você pode usar uma lista de referências ou, melhor ainda, usar uma classe intermediária CallQueueAgent apenas para economizar tempo e simplificar a consulta. MAS SOMENTE neste caso, porque é um N para N , não nos outros.

  • A preocupação de transformar um registro do BD no Modelo apropriado pertence à sua classe PersistenceFacade , que também é responsável por consulte seu banco de dados. Os frameworks modernos vêm todos com um DB Facade que pode perfeitamente fazer o trabalho. Como é uma Fachada, você tem todo o espaço para modificar sua lógica de perseverança como quiser, e isso não afetará sua lógica ou modelagem comercial.

Essa é toda a força do padrão PersistenceFacade e é por isso que recomendo que você o use, aplicando uma modelagem sem persistência consciente para sua modelagem de negócios e o princípio da separação de interesses .

    
por 01.06.2018 / 22:12
fonte