Práticas recomendadas para serialização de agregados DDD

23

De acordo com o domínio DDD, a lógica não deve ser poluída com preocupações técnicas, como serialização, mapeamento objeto-relacional, etc.

Então, como serializar ou mapear o estado dos agregados sem expô-lo publicamente por meio de getters e setters? Eu já vi muitos exemplos para, por exemplo, implementações de repositórios, mas praticamente todos eles contavam com acessadores públicos nas entidades e objetos de valor para o mapeamento.

Poderíamos usar a reflexão para evitar acessadores públicos, mas na IMO esses objetos de domínio ainda dependiam implicitamente da preocupação de serialização. Por exemplo, você não pode renomear ou remover um campo privado sem alterar sua configuração de serialização / mapeamento. Portanto, você deve considerar a serialização em que deveria se concentrar na lógica do domínio.

Então, qual é o melhor compromisso a seguir aqui? Vive com acessadores públicos, mas evita usá-los para qualquer outra coisa que não seja o código de mapeamento? Ou acabei de perder algo óbvio?

Estou explicitamente interessado em serializar o estado dos objetos de domínio DDD (agregados constituídos por entidades e objetos de valor). NÃO se trata de serialização em cenários gerais ou de scripts de transciação em que os serviços sem estado operam em objetos simples de contêiner de dados.

EagleBeak
fonte

Respostas:

12

Tipos de objetos

Para os propósitos de nossa discussão, vamos separar nossos objetos em três tipos diferentes:

Lógica de domínio comercial

Esses são os objetos que realizam o trabalho. Eles transferem dinheiro de uma conta corrente para outra, cumprem pedidos e todas as outras ações que esperamos que o software comercial execute.

Objetos lógicos de domínio normalmente não precisam de acessadores (getters e setters). Em vez disso, você cria o objeto entregando dependências a ele por meio de um construtor e, em seguida, manipula o objeto por meio de métodos (diga, não pergunte).

Objetos de transferência de dados

Objetos de transferência de dados são estado puro; eles não contêm nenhuma lógica comercial. Eles sempre terão acessadores. Eles podem ter ou não setters, dependendo de você estar ou não gravá-los de maneira imutável . Você definirá seus campos no construtor e seus valores não serão alterados durante a vida útil do objeto, ou seus acessadores serão de leitura / gravação. Na prática, esses objetos geralmente são mutáveis, para que um usuário possa editá-los.

Exibir objetos de modelo

Os objetos View Model contêm uma representação de dados exibível / editável. Eles podem conter lógica de negócios, geralmente confinada à validação de dados. Um exemplo de um objeto View Model pode ser um InvoiceViewModel, contendo um objeto Customer, um objeto Header Invoice e itens de linha da fatura. Os objetos View Model sempre contêm acessadores.

Portanto, o único tipo de objeto que será "puro" no sentido de que não contém acessadores de campo será o objeto de lógica de domínio. A serialização de um objeto salva seu "estado computacional" atual, para que possa ser recuperado posteriormente para concluir o processamento. Os modelos de exibição e DTOs podem ser serializados livremente, mas, na prática, seus dados são normalmente salvos em um banco de dados.

Serialização, dependências e acoplamento

Embora seja verdade que a serialização crie dependências, no sentido de que você precisa desserializar para um objeto compatível, isso não significa necessariamente que você precise alterar sua configuração de serialização. Bons mecanismos de serialização são de uso geral; eles não se importam se você altera o nome de uma propriedade ou membro, desde que ele ainda possa mapear valores para os membros. Na prática, isso significa apenas que você deve serializar novamente a instância do objeto para tornar a representação de serialização (xml, json, qualquer que seja) compatível com seu novo objeto; nenhuma alteração na configuração do serializador deve ser necessária.

É verdade que os objetos não devem se preocupar com a maneira como são serializados. Você já descreveu uma maneira de dissociar essas preocupações das classes de domínio: reflexão. Mas o serializador deve se preocupar em como serializar e desserializar objetos; afinal, essa é a sua função. A maneira como você mantém seus objetos separados do processo de serialização é tornar a serialização uma função de uso geral , capaz de funcionar em todos os tipos de objetos.

Uma das coisas sobre as quais as pessoas ficam confusas é que a dissociação deve ocorrer nas duas direções. Isso não; só tem que trabalhar em uma direção. Na prática, você nunca pode se separar completamente; sempre há algum acoplamento. O objetivo do acoplamento flexível é facilitar a manutenção do código, não remover todas as dependências.

Robert Harvey
fonte
Concordo com a sua opinião sobre a dissociação. O serializador depende do objeto do domínio e tudo bem. Mas não o contrário. No entanto, discordo da sua opinião sobre acessadores públicos em objetos de domínio. Na prática, eles costumam tê-los, sim. Mas, na IMO, seria preferível implementar a lógica do domínio em um design limpo e orientado a objetos: diga, não pergunte . Mas você ainda precisa de acessadores para fins de mapeamento (ORM, serialização, GUI ...). E é isso que eu gostaria de evitar, se possível.
Bico-Fino
Como você planeja acessar seus campos, se você não tem acessadores?
Robert Harvey
Na verdade, estou me referindo a nenhum dos três tipos de objetos que você descreve, mas a "agregados" na terminologia DDD e seus subobjetos (enidades, objetos de valor). Percebo agora que minha pergunta não era explícita o suficiente sobre isso. Desculpe! Por favor, veja minha edição acima.
Bico-Fino
1
Esse é basicamente um problema não resolvido - você não pode ter encapsulamento, desacoplamento e serialização \ codificação ao mesmo tempo em que expõe o DTO, é uma maneira de obter um compromisso. No entanto, existem maneiras muito menos invasivas: yegor256.com/2016/07/06/data-transfer-object.html
Basilevs
1
Com o descarte do encapsulamento, qualquer pessoa pode implementar ou usar a classe friend para ler as partes internas do objeto.
Basilevs 28/11
-1

O objetivo subjacente da serialização é garantir que os dados produzidos por um sistema possam ser consumidos por um ou mais sistemas compatíveis.

A abordagem mais fácil e robusta à serialização é converter os dados em um formato independente de tipo que mantém a estrutura em um formato simples e fácil de consumir. Por exemplo, os formatos de serialização mais onipresentes (ie JSON, XML) usam um formato baseado em texto bem definido. O texto é simples de produzir, transmitir e consumir.

Existem 2 razões pelas quais o uso de um desses formatos pode não ser o ideal.

  1. Eficiência

    Há um custo inerente envolvido na conversão de todos os dados em seus equivalentes baseados em texto. Os tipos de dados não existiriam se o texto fosse a maneira mais eficiente de expressar todas as diferentes formas de dados. Além disso, a estrutura desses formatos não é ideal para recuperar subconjuntos de dados de forma assíncrona ou em partes.

    Por exemplo, XML e JSON assumem que os dados usados ​​serão gravados e lidos do início ao fim. Para processar conjuntos de dados muito grandes em que a memória é escassa, o sistema que consome os dados pode exigir a capacidade de processar os dados em partes. Nesse caso, uma implementação de serialização / desserialização de finalidade especial pode ser necessária para manipular os dados.

  2. Precisão

    A conversão necessária para serializar / desserializar os dados do tipo pretendido para o tipo agnóstico dos dados resulta em perda de precisão.

Alguém poderia argumentar que produzir uma representação binária dos objetos e dados é claramente a solução mais eficiente e precisa. A principal desvantagem é que a implementação de todos os sistemas que consomem e produzem dados precisa permanecer compatível. É uma restrição simples na teoria, mas é um pesadelo para manter na prática, pois os sistemas de produção tendem a mudar / evoluir ao longo do tempo.

Com isso dito. A dissociação da serialização / desserialização dos detalhes específicos do domínio faz sentido como regra geral, porque os formatos de uso geral são mais robustos, têm melhor suporte em diversos sistemas e exigem pouca sobrecarga de manutenção para usar.

Evan Plaice
fonte
Desculpe, mas isso não responde à minha pergunta. Trata-se de dissociar objetos de domínio da serialização, não dos motivos da serialização ou dos prós e contras de vários formatos. Como serializar objetos de domínio sem expor publicamente seu estado privado?
EagleBeak
@EagleBeak Oh, eu não sabia que sua preocupação era especificamente sobre lidar com membros privados. No seu caso, você pode serializar em binário (assumindo que o sistema receptor siga as mesmas regras / estrutura em que os objetos de domínio foram criados) ou escrever alguma lógica que extraia apenas os dados públicos antes da serialização.
precisa
Eu acho que a suposição 'usual' é que os dados que estão sendo serializados em um formato de uso geral (ex xml, json) serão públicos e esse privilégio é controlado pela API por meio de ACLs ou algum outro equivalente. A serialização / desserialização de uso geral cai mais ao longo da linha de dissociação de dados da lógica de negócios, passando de um sistema para outro.
precisa
Concordo que os acessadores públicos sejam geralmente assumidos em objetos a serem serializados. Mas ainda gostaria de saber mais sobre como isso se relaciona com o DDD e seu forte foco no encapsulamento da lógica do domínio. Todos os profissionais de DDD apenas expõem o estado do modelo de domínio por meio de acessadores públicos para serialização (e nunca o mencionam em seus exemplos)? Eu duvido. Por favor, não me interpretem mal. Eu aprecio muito a sua opinião. Só que estou interessado em um aspecto diferente. (Até agora, não acho que minha pergunta seja muito vaga, mas a resposta de Robert Harvey e a sua me fizeram pensar nisso ..) #
EagleBeak