As entidades internas de um agregado devem responder diretamente aos eventos do domínio?

5

Eu sou relativamente novo na implementação de CQRS e Event Sourcing, enquanto aplico regras de design dirigido por domínio. Digamos que eu tenha um Order agregado e dentro dele está uma lista de OrderLine entidades. Eu estou usando este modelo no contexto de um sistema de origem de eventos (com CQRS) e assim as mudanças de estado são principalmente impulsionadas por eventos. As alterações no agregado devem ser executadas no Order , que serve como raiz agregada. Quando, por exemplo, um evento como ItemAdded for gerado, o respectivo agregado Order será chamado para aplicá-lo a si mesmo, algo como order.apply(itemAdded) . Isso acionará a criação de um novo objeto OrderLine interno para representar o item recém-adicionado no pedido. Em termos de implementação, faz sentido que o novo objeto OrderLine aplique o evento a si mesmo diretamente? Ou, devo apenas deixá-lo para o agregado Order para criar o próprio objeto OrderLine através de um construtor?

Qual deles é mais apropriado?

void apply(ItemAdded itemAdded) {
    OrderLine orderLine = new OrderLine();
    oderLine.apply(itemAdded);
    orderLines.add(orderLine);
}

ou

void apply(ItemAdded itemAdded) {
    OrderLine orderLine = new OrderLine(
        itemAdded.getId(), 
        itemAdded.getProductId(), 
        itemAdded.getQuantity(), 
        itemAdded.getPrice()
    );
    orderLines.add(orderLine);
}

Por enquanto, acho que a primeira abordagem é mais concisa e mais fácil de entender. Há eventos cujos campos podem ficar muito longos e ter a própria entidade interna lidando com a construção torna o código mais limpo. O problema, no entanto, é que, ao fornecer um método apply no OrderLine , estou abrindo para uma possível mutação fora do contexto do Order . Se eu fornecer um acessador, por exemplo, getOrderLines() , em Order , mesmo que eu torne a coleção / lista em si inalterável, os objetos contidos ainda serão mutáveis. Uma maneira de evitar isso é retornar clones, mas isso pode ser um pouco difícil de implementar para entidades aninhadas bastante complexas.

Para resolver isso, o que estou pensando é manter o método apply na classe OrderLine , mas limitar sua visibilidade. A abordagem que me dá maior flexibilidade nesse sentido é tornar OrderLine uma classe aninhada em Order class. Isso significa que posso manter o método apply privado, mas ainda acessível, dentro do contexto da classe Order . No entanto, isso pode significar uma classe Order muito longa, especialmente se eu precisar adicionar mais entidades aninhadas no futuro.

Como alternativa, posso limitar o acesso ao método apply no nível do pacote, o que significa que preciso reestruturar para limitar todas as classes de domínio em um pacote.

Algum conselho sobre isso?

    
por Psycho Punch 29.06.2017 / 14:40
fonte

4 respostas

2

Se considerarmos a sua questão apenas pelo valor de face, a resposta é simplesmente passar o objeto itemAdded para o construtor de OrderLine em vez de fornecer um método apply() . Dessa forma, a única diferença entre seus dois exemplos é se você está ou não passando o objeto inteiro em um único parâmetro em relação a suas propriedades como vários parâmetros.

Mas, se itemAdded for realmente um evento e não um item, você deverá renomeá-lo para algo como itemAddedEvent . Você também pode considerar ter uma classe Item que represente o Item e construí-lo a partir do Evento. Então você seria capaz de passar um item para o OrderLine. Isso pode deixar seu domínio mais alinhado com o modo como um especialista em negócios ou um cliente falaria sobre o processo.

    
por 29.06.2017 / 19:15
fonte
1

Which one is more appropriate?

Estou quase certo de que a primeira abordagem é a que será mais saudável a longo prazo.

Enigma: se a compreensão dos negócios de OrderLine fosse mudar durante a vida útil do modelo de domínio, com qual projeto seria mais fácil trabalhar?

No primeiro caso, você estende a mensagem ItemAdded para incluir os novos campos necessários e atualiza o item OrderLine para ler esses novos campos e fazer algo interessante, e é isso.

No segundo caso, você ainda precisa estender a mensagem ItemAdded, e ainda precisa modificar OrderLine , e você também precisa modificar Order . Isso não parece tão bom.

Parnas escreveu em ... Decompondo sistemas em módulos

We propose instead that one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others.

Neste exemplo, o OrderLine é o módulo que está ocultando a decisão "como consumimos uma mensagem ItemAdded?"

I'm opening it up for possible mutation outside the context of the Order. If I provide an accessor, say, getOrderLines(), in Order, even if I make the collection/list itself unmodifiable, the objects contained will still be mutable. One way to prevent this is to return clones, but that can get a little cumbersome to implement for fairly complex nested entities.

Separação de consultas de comando e Interfaces de funções podem ajudar aqui. A idéia básica é pensar no OrderItem como tendo diferentes papéis que ele pode executar. Assim, a interface que o OrderItem implementa seria dividida em diferentes fatias, uma das quais seria um IApplyEvents ou mesmo IHandle<ItemAdded> , e você cria sua lógica para que as funções que permitem que o item seja modificado sejam expostas apenas no uso casos em que você os deseja.

Portanto, se você tiver um acessor como getOrderLines , ele retornará uma coleção de RoleInterfaces que são compostos inteiramente de consultas.

interface OrderLineDetails {
    ProductDetails getProductDetails();
}

interface IHandle<Event> {
    void apply(Event event);
}

class OrderLine implements OrderLineDetails, IHandle<ItemAdded> {
    // ...
}

Os nomes podem ficar confusos; todo mundo quer "OrderLine" para significar o que significa nesse contexto específico. O DependencyInversion pode ajudar aqui.

One way to prevent this is to return clones, but that can get a little cumbersome to implement for fairly complex nested entities.

Às vezes, suas entidades aninhadas bastante complexas são realmente valores aninhados complexos, o que pode simplificar as coisas.

State next = current.apply(change)

é bastante razoável. No resumo, de qualquer maneira. Tenha cuidado para reconhecer que complexidade vem da ideia e quais são as ferramentas que você está usando; pode ser uma sugestão de que você precisa de algo que se adapte melhor ao problema .

    
por 30.06.2017 / 14:11
fonte
0

Você tem uma coisa errada em seu design. Parece que você está modelando de acordo com um banco de dados relacional em vez de seu domínio. Por que eu penso assim? Porque você tem uma raiz agregada Order , que contém internamente uma OrderLine holding Item s. Você modelou um relacionamento M: N usando a classe OrderLine .

O problema é que, ao usar DDD, você não modela para corresponder aos seus dados, portanto, sua estrutura também não deve refletir o banco de dados relacional.

Pergunte-se o seguinte:

  1. Com o que a empresa se importa?
  2. A empresa se preocupa em ter uma entidade adicional (provavelmente fraca) chamada OrderLine para mapear itens para um pedido?
  3. Os negócios precisam de adicionar / remover registros do mapeamento OrderLine table?

As respostas às perguntas 2 e 3 são não. Seus gerentes de negócios não poderiam se importar menos com a modelagem do relacionamento, mas o que eles vão se importar quando se trata de estatísticas é o seguinte:

  • Em média, quantos itens têm um Pedido que é processado completamente?
  • Quantas vezes as pessoas adicionam um Item a um Pedido e o remove o Item do Pedido ?
  • Qual é o custo médio de um Pedido que é processado completamente?
  • Quantas Encomendas s um Utilizador tem em média?
  • Quantas vezes um Item foi removido de um Pedido apenas para ser adicionado novamente?

Estas são todas as perguntas válidas que podem vir do departamento comercial no futuro. Eu destaquei os agregados afetados em cada uma dessas questões. Como você pode ver, não há OrderLine , porque essa entidade realmente apenas modela o relacionamento. Sua empresa se preocupa com seus itens se com s e você também.

Com isso em mente, o OrderLine deve ser apenas um simples List / Vector / Set contendo objetos e entidades de valor muito simples, como este:

class OrderLineItem extends Entity {
    private ItemId itemId;
    private int quantity = 1;

    public bool Equals(Entity other) {
        return
          other instanceof OrderLineItem &&
          itemId.Equals(other.itemId);
    }
}

Um Item é referenciado apenas por um id, porque você NÃO DEVE manter outros agregados diretamente.

Como você pode ver com essa abordagem, não precisa mais se preocupar se o OrderLine deve aceitar um evento diretamente, porque a entidade simplesmente desapareceu.

Em um sistema de evento de origem, a tabela de relacionamentos M: N não existirá, porque o seu armazenamento de dados são eventos, não relações.

Ao criar modelos de leitura de seus eventos para o lado da consulta, os modelos de leitura devem ser o mais simples possível. Idealmente você deve ser capaz de fazer SELECT * FROM read_model WHERE ... e nada mais. Nenhuma junção Portanto, é bem provável que você também não tenha o relacionamento M: N no lado de leitura. Para ser honesto, como os modelos de leitura são documentos de qualquer maneira, faz muito mais sentido usar um banco de dados diferente, mais adequado, como o MongoDB.

    
por 30.06.2017 / 08:14
fonte
0

sem regras lá. Depende de tanto: - Seu modelo de persistência - Se você está aplicando CQRS - a complexidade & os invariantes de suas entidades.

Eu geralmente confio em uma abstração para o meu AR & ES que cascateiam naturalmente eventos para qualquer interface ES no mesmo AR.

Mas a maioria das minhas raízes agregadas de ES é pequena e bastante “simples”, eu tento projetá-las apenas com VO. Normalmente, seu AR não deve alterar outro estado de entidade.

No seu exemplo, eu não diria um OrderLine ES. A menos que você tenha alguns complexos, regras que importam & aplicar quando OrderLine mudar seu estado. Considerando seu comportamento comercial atual: adicione um item. Eu projetaria OrderLine como um VO.

Como você está enviando seu pedido, não consigo encontrar nenhum uso para ES nas linhas. Você sempre poderá acompanhar o ciclo de vida clássico do comércio eletrônico.

Em cascata ES significa que seu agregado é muito complexo, questione seu design antes de ir para lá.

    
por 14.10.2018 / 19:33
fonte