A maioria dos padrões de design tático DDD pertence ao paradigma orientado a objetos, e o modelo anêmico descreve a situação em que toda a lógica de negócios é colocada em serviços, em vez de objetos, tornando-os uma espécie de DTO. Em outras palavras, modelo anêmico é sinônimo de estilo procedural, o que não é recomendado para modelos complexos.
Não tenho muita experiência em programação funcional pura, mas gostaria de saber como o DDD se encaixa no paradigma do FP e se o termo 'modelo anêmico' ainda existe nesse caso.
Atualização : Recentlry publicada livro e vídeo sobre o assunto.
functional-programming
domain-driven-design
Pavel Voronin
fonte
fonte
Respostas:
A maneira como o problema do "modelo anêmico" é descrito não se traduz bem no FP como está. Primeiro, ele precisa ser adequadamente generalizado. No fundo, um modelo anêmico é um modelo que contém conhecimento sobre como usá-lo adequadamente, que não é encapsulado pelo próprio modelo. Em vez disso, esse conhecimento está espalhado em torno de uma pilha de serviços relacionados. Esses serviços devem ser apenas clientes do modelo, mas devido à sua anemia, eles são responsabilizados por ele. Por exemplo, considere uma
Account
classe que não pode ser usada para ativar ou desativar contas ou mesmo procurar informações sobre uma conta, a menos que seja tratada por meio de umaAccountManager
classe. A conta deve ser responsável por operações básicas, não por alguma classe de gerente externo.Na programação funcional, existe um problema semelhante quando os tipos de dados não representam com precisão o que devem modelar. Suponha que precisamos definir um tipo representando IDs do usuário. Uma definição "anêmica" indicaria que os IDs do usuário são cadeias. Isso é tecnicamente viável, mas enfrenta grandes problemas porque os IDs de usuário não são usados como sequências arbitrárias. Não faz sentido concatená-los ou cortar subcadeias deles, o Unicode não deve realmente importar e deve ser facilmente incorporado em URLs e outros contextos com limitações estritas de caracteres e formatos.
A solução desse problema geralmente ocorre em algumas etapas. Um primeiro corte simples é dizer "Bem, a
UserID
é representado equivalentemente a uma string, mas são tipos diferentes e você não pode usar um onde espera o outro". Haskell (e algumas outras linguagens funcionais digitadas) fornece esse recurso vianewtype
:Isso define uma
UserID
função que, quando fornecida,String
constrói um valor que é tratado como umUserID
pelo sistema de tipos, mas que ainda é apenas umString
em tempo de execução. Agora, as funções podem declarar que exigemUserID
uma sequência em vez de uma sequência; usandoUserID
s em que você estava usando cordas guardas contra o código que concatena duasUserID
s juntos. O sistema de tipos garante que isso não pode acontecer, sem a necessidade de testes.A fraqueza aqui é que o código ainda pode pegar qualquer
String
tipo arbitrário"hello"
e construir um aUserID
partir dele. Outras etapas incluem a criação de uma função "construtor inteligente" que, quando recebe uma string, verifica alguns invariantes e retorna apenas umUserID
se estiver satisfeito. Em seguida, oUserID
construtor "burro" é tornado privado, portanto, se um cliente quiser umUserID
ele deve usar o construtor inteligente, impedindo que IDs de usuário malformados passem a existir.Outras etapas definem o
UserID
tipo de dados de tal maneira que é impossível construir um que esteja malformado ou "impróprio", simplesmente por definição. Por exemplo, definindo aUserID
como uma lista de dígitos:Para construir uma
UserID
lista de dígitos deve ser fornecido. Dada essa definição, é trivial mostrar que é impossívelUserID
existir um que não possa ser representado em um URL. A definição de modelos de dados como este em Haskell geralmente é auxiliada por recursos avançados do sistema de tipos, como tipos de dados e tipos de dados algébricos generalizados (GADTs) , que permitem ao sistema de tipos definir e provar mais invariantes sobre seu código. Quando os dados são dissociados do comportamento, sua definição de dados é o único meio de você impor o comportamento.fonte
Em grande medida, a imutabilidade torna desnecessário associar firmemente suas funções aos seus dados, como advogam o OOP. Você pode fazer quantas cópias quiser, até criando estruturas de dados derivadas, em códigos muito distantes do código original, sem medo de que a estrutura de dados original seja inesperadamente alterada sob você.
No entanto, uma maneira melhor de fazer essa comparação é provavelmente examinar quais funções você está alocando para a camada de modelo versus a camada de serviços . Mesmo que não pareça o mesmo que no OOP, é um erro bastante comum no FP tentar agrupar o que deveria ser vários níveis de abstração em uma função.
Até onde eu sei, ninguém o chama de modelo anêmico, pois esse é um termo OOP, mas o efeito é o mesmo. Você pode e deve reutilizar funções genéricas sempre que aplicável, mas para operações mais complexas ou específicas de aplicativos, você também deve fornecer um rico conjunto de funções apenas para trabalhar com seu modelo. Criar camadas de abstração apropriadas é um bom design em qualquer paradigma.
fonte
Ao usar o DDD no OOP, um dos principais motivos para colocar a lógica de negócios nos próprios objetos de domínio é que a lógica de negócios geralmente é aplicada pela mutação do estado do objeto. Isso está relacionado ao encapsulamento:
Employee.RaiseSalary
provavelmente modifica osalary
campo daEmployee
instância, que não deve ser publicamente configurável.No FP, a mutação é evitada; portanto, você implementaria esse comportamento criando uma
RaiseSalary
função que pega umaEmployee
instância existente e retorna uma novaEmployee
instância com o novo salário. Portanto, nenhuma mutação está envolvida: apenas lendo o objeto original e criando o novo objeto. Por esse motivo, essaRaiseSalary
função não precisa ser definida como um método naEmployee
classe, mas pode estar em qualquer lugar.Nesse caso, torna-se natural separar os dados do comportamento: uma estrutura representa os
Employee
dados (completamente anêmicos), enquanto um (ou vários) módulos contêm funções que operam nesses dados (preservando a imutabilidade).Observe que, quando você associa dados e comportamento como no DDD, geralmente viola o Princípio de Responsabilidade Única (SRP):
Employee
pode ser necessário mudar se as regras para alterações salariais mudarem; mas também pode ser necessário alterar se as regras para o cálculo do bônus EOY mudarem. Com a abordagem dissociada, esse não é o caso, pois você pode ter vários módulos, cada um com uma responsabilidade.Portanto, como sempre, a abordagem FP fornece maior modularidade / composibilidade.
fonte
Eu acho que a essência da questão é que um modelo anêmico com toda a lógica de domínio nos serviços que operam no modelo é basicamente programação procedural - em oposição à programação OO "real", na qual você tem objetos "inteligentes" e não apenas dados mas também a lógica mais intimamente ligada aos dados.
E o mesmo contraste existe com a programação funcional: FP "real" significa usar funções como entidades de primeira classe, que são passadas como parâmetros, assim como construídas em tempo real e retornadas como valor de retorno. Mas quando você falha em usar todo esse poder e possui apenas funções que operam nas estruturas de dados que são passadas entre eles, você acaba no mesmo local: basicamente está fazendo programação procedural.
fonte
Eu acho que sim, mas em grande parte como uma abordagem tática para fazer a transição entre objetos de valor imutáveis ou como uma maneira de acionar métodos em entidades. (Onde a maior parte da lógica ainda vive na entidade.)
Bem, se você quer dizer "de maneira análoga à OOP tradicional", ajuda a ignorar os detalhes de implementação usuais e voltar ao básico: que idioma seus especialistas em domínio usam? Que intenção você está capturando de seus usuários?
Supondo que eles falem sobre encadear processos e funções juntos, parece que funções (ou pelo menos objetos "executores") são basicamente seus objetos de domínio!
Portanto, nesse cenário, um "modelo anêmico" provavelmente ocorreria quando suas "funções" não forem realmente executáveis e, em vez disso, são apenas constelações de metadados que são interpretados por um Serviço que faz o trabalho real.
fonte