È ancora valido parlare di modello anemico nel contesto della programmazione funzionale?

28

La maggior parte dei modelli di progettazione tattica DDD appartiene al paradigma orientato agli oggetti, e il modello anemico descrive la situazione quando tutta la logica aziendale è messa in servizi piuttosto che oggetti, rendendoli quindi una sorta di DTO. In altre parole, il modello anemico è un sinonimo di stile procedurale, che non è consigliato per il modello complesso.

Non ho molta esperienza nella pura programmazione funzionale, eppure mi piacerebbe sapere in che modo DDD si inserisce nel paradigma del FP e se il termine "modello anemico" esiste ancora in quel caso.

Aggiornamento : Recentlry pubblicato libro e video sull'argomento.

    
posta Pavel Voronin 05.05.2016 - 00:04
fonte

5 risposte

19

Il modo in cui il problema del "modello anemico" è descritto non si traduce bene con FP come è. Per prima cosa deve essere adeguatamente generalizzato. Nel suo cuore, un modello anemico è un modello che contiene conoscenze su come usarlo correttamente e non è incapsulato dal modello stesso. Invece, quella conoscenza si sviluppa attorno a una pila di servizi correlati. Questi servizi dovrebbero essere solo client del modello, ma a causa della sua anemia sono ritenuti responsabili per questo. Ad esempio, considera una classe Account che non può essere utilizzata per attivare o disattivare account o anche informazioni di ricerca su un account a meno che non venga gestita tramite una classe AccountManager . L'account dovrebbe essere responsabile delle operazioni di base su di esso, non di alcune classi di manager esterni.

Nella programmazione funzionale, esiste un problema simile quando i tipi di dati non rappresentano esattamente ciò che dovrebbero modellare. Supponiamo di dover definire un tipo che rappresenti gli ID utente. Una definizione "anemica" indica che gli ID utente sono stringhe. Questo è tecnicamente fattibile, ma presenta enormi problemi perché gli ID utente non sono utilizzati come stringhe arbitrarie. Non ha senso concatenarli o sviscerarli, Unicode non dovrebbe avere molta importanza e dovrebbero essere facilmente incorporabili in URL e in altri contesti con limiti rigorosi di carattere e formato.

La risoluzione di questo problema di solito avviene in alcune fasi. Un semplice primo taglio è dire "Beh, un UserID è rappresentato in modo equivalente a una stringa, ma sono tipi diversi e non puoi usarne uno dove ti aspetti l'altro." Haskell (e alcune altre lingue funzionali digitate) fornisce questa funzione tramite newtype :

newtype UserID = UserID String

Definisce una funzione UserID che quando viene data una String costruisce un valore che è trattato come a UserID dal sistema di tipi, ma che è ancora solo un String a runtime. Ora le funzioni possono dichiarare che richiedono un UserID invece di una stringa; usando UserID s in cui in precedenza utilizzavi le guardie delle stringhe contro il codice che concatena due UserID s insieme. Il sistema di tipi garantisce che non può accadere, nessun test richiesto.

Il punto debole è che il codice può ancora prendere qualsiasi String arbitrario come "hello" e costruire un UserID da esso. Ulteriori passaggi includono la creazione di una funzione "costruttore intelligente" che, quando viene fornita una stringa, verifica alcuni invarianti e restituisce solo UserID se sono soddisfatti. Quindi il costruttore "stupido" UserID viene reso privato, quindi se un cliente desidera un UserID , deve utilizzare il costruttore intelligente, impedendo così l'esistenza di UserID non validi.

Anche ulteriori passaggi definiscono il tipo di dati UserID in modo che sia impossibile per costruirne uno che è malformato o "improprio", semplicemente per definizione. Ad esempio, definendo un UserID come un elenco di cifre:

data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]

Per costruire un UserID deve essere fornito un elenco di cifre. Data questa definizione, è banale mostrare che è impossibile che esista un UserID che non può essere rappresentato in un URL. La definizione di modelli di dati come questo in Haskell è spesso aiutata da funzionalità di sistema di tipo avanzato come Dati Tipi e Tipi di dati algebrici generalizzati (GADT) , che consentono al sistema di tipi di definire e dimostrare più invarianti sul tuo codice . Quando i dati vengono disaccoppiati dal comportamento, la definizione dei dati è l'unico mezzo per forzare il comportamento.

    
risposta data 05.05.2016 - 05:34
fonte
9

In gran parte, l'immutabilità non rende necessario accoppiare strettamente le tue funzioni con i tuoi dati come sostiene OOP. Puoi creare tutte le copie che desideri, anche creando strutture di dati derivate, in codice molto lontano dal codice originale, senza timore che la struttura dei dati originale si stacchi inaspettatamente da sotto di te.

Tuttavia, un modo migliore per fare questo confronto è probabilmente quello di vedere quali funzioni si stanno assegnando al modello layer rispetto ai servizi layer . Anche se non sembra lo stesso di OOP, è un errore abbastanza comune in FP cercare di stipare ciò che dovrebbe essere più livelli di astrazione in un'unica funzione.

Per quanto ne so, nessuno lo definisce un modello anemico, poiché si tratta di un termine OOP, ma l'effetto è lo stesso. È possibile e dovrebbe riutilizzare le funzioni generiche laddove applicabile, ma per operazioni più complesse o specifiche dell'applicazione, è necessario fornire un ricco insieme di funzioni solo per lavorare con il proprio modello. Creare appropriati livelli di astrazione è un buon design in ogni paradigma.

    
risposta data 05.05.2016 - 05:05
fonte
5

Quando si utilizza DDD in OOP, uno dei motivi principali per mettere la logica aziendale negli oggetti del dominio è che la logica aziendale viene solitamente applicata mediante la modifica dello stato dell'oggetto. Questo è correlato all'incapsulamento: Employee.RaiseSalary probabilmente muta il campo salary dell'istanza Employee , che non dovrebbe essere impostabile pubblicamente.

In FP, la mutazione viene evitata, quindi devi implementare questo comportamento creando una funzione RaiseSalary che accetta un'istanza Employee esistente e restituisce una nuova istanza nuova Employee con la nuova stipendio. Quindi nessuna mutazione è coinvolta: solo la lettura dall'oggetto originale e la creazione del nuovo oggetto. Per questo motivo, tale funzione RaiseSalary non ha bisogno di essere definita come un metodo sulla classe Employee , ma potrebbe vivere ovunque.

In questo caso, diventa naturale separare i dati dal comportamento: una struttura rappresenta il Employee come dati (completamente anemico), mentre uno (o più) moduli contengono funzioni che operano su quei dati (preservando l'immutabilità) .

Si noti che quando si uniscono dati e comportamenti come nel DDD si viola in genere il Principio di Responsabilità Unica (SRP): potrebbe essere necessario modificare Employee se cambiano le regole per le modifiche salariali; ma potrebbe anche essere necessario cambiare se le regole per il calcolo del cambio bonus EOY. Con l'approccio disaccoppiato questo non è il caso, dal momento che puoi avere diversi moduli, ognuno con una sola responsabilità.

Quindi, come al solito l'approccio FP fornisce una maggiore modularità / componibilità.

    
risposta data 31.05.2016 - 10:14
fonte
-2

Penso che l'essenza della questione sia che un modello anemico con tutta la logica di dominio nei servizi che operano sul modello è fondamentalmente una programmazione procedurale - al contrario della programmazione OO "reale" in cui si hanno oggetti "intelligenti" e che contengono non solo dati ma anche la logica più strettamente legata ai dati.

E lo stesso contrasto esiste con la programmazione funzionale: FP "reale" significa usare le funzioni come entità di prima classe, che vengono passate come parametri, così come costruite al volo e restituite come valore di ritorno. Ma quando non riesci a usare tutto quel potere e hai solo funzioni che funzionano su strutture dati che vengono passate tra di loro, allora finisci nello stesso posto: stai praticamente facendo programmazione procedurale.

    
risposta data 05.05.2016 - 00:32
fonte
-3

I'd like to know how DDD fits into FP paradigm

Penso che lo faccia, ma in gran parte come approccio tattico alla transizione tra oggetti Value immutabili o come metodo per trigger su entità. (Dove la maggior parte della logica vive ancora nell'entità.)

and whether term 'anemic model' still exists in that case.

Bene, se intendi "in un modo analogo al tradizionale OOP", allora aiuta a ignorare i soliti dettagli di implementazione e torna alle origini: quale linguaggio usano i tuoi esperti di dominio? Quale intento sta catturando dai tuoi utenti?

Supponendo che parlino di concatenare processi e funzioni insieme, allora sembra che funzioni (o almeno oggetti "do-er") in pratica siano i tuoi oggetti di dominio!

Quindi in questo scenario, probabilmente un "modello anemico" si verificherebbe quando le tue "funzioni" non sono effettivamente eseguibili, e sono invece solo costellazioni di metadati che sono interpretati da un Servizio che fa il vero lavoro.

    
risposta data 05.05.2016 - 00:47
fonte

Leggi altre domande sui tag