In che modo entità, DTO, DOA lavorano insieme e qual è il loro ruolo all'interno di un sistema?

3

Gran parte del mio lavoro è scritto in Java usando Spring Boot. In un progetto recente le mie entità non contenevano alcuna logica aziendale, Spring Data è stato utilizzato per gestire i repository e ho classi di servizio che i controllori chiamerebbero per recuperare determinati dati o eseguire azioni su un'entità. Ad esempio, potresti avere un UserService con un'azione restPassword che gestirà il ripristino della password di un utente.

Di recente ho letto molto sul design dei domini e mi sto confondendo perché gli esempi in un libro mostrano un comportamento su ciò che sembra un'entità. Ad esempio, un'azione changeStatus () su un ordine.

Se applichi il patter MVC, sto faticando a capire le relazioni tra controller, servizi, DTO, DAO ed entità.

Diciamo che stai creando un servizio web, il tuo controller restituirà JSON al client. Le tue entità corrisponderebbero alla struttura del tuo database, ma hai bisogno di un altro tipo di oggetto per restituire i dati al client in quanto la struttura di tali dati potrebbe essere diversa dal tuo database, questo è il DTO?

Il tuo controller avrebbe parlato con un servizio? E cosa restituirebbe il servizio? Entità o DTO? Se le entità dovresti mappare quelle a DTO usando un mapper nel controller?

E il comportamento? Va bene in una classe di servizio, o le entità dovrebbero essere mappate a DAO che contengono un comportamento?

Sono confuso, quando cerco questo, alcuni sembrano confondere entità, repository e daos. Alcuni dicono che le entità possono contenere comportamenti, altri no.

Un altro esempio, diciamo che vogliamo sapere quali autorizzazioni ha un utente nel sistema, possiamo trovarlo chiamando getUserPermissions (utente Utente) che potrebbe restituire List. Cosa chiameremmo da un controller? Lo metteremo in una classe PermissionsService e lo chiameremo? O avremmo un altro oggetto che rappresenta un'entità ma che ha anche un comportamento e potrebbe comunicare con i repository per raccogliere ulteriori dati? Certamente non potremmo metterlo sull'entità stessa, poiché ciò significherebbe iniettare un repository in un'entità, il che è sbagliato.

Qualsiasi consiglio è apprezzato.

    
posta SheppardDigital 21.09.2018 - 00:19
fonte

2 risposte

3

I tuoi punti di partenza primari dovrebbero essere

  • Eric Evans: Domain Driven Design
  • Martin Fowler: Patterns of Enterprise Application Architecture

Evans offre la chiara introduzione a cosa significa mettere per primo il modello di dominio. Fowler riassume il modello del modello di dominio, insieme a diverse alternative ad esso.

Ecco l'idea chiave: le imprese scrivono software personalizzato (anziché acquistare semplicemente qualcosa di nuovo), perché non esiste l'articolo off-the-shelf di cui hanno bisogno o perché è in grado di mettere a punto i dettagli sono importanti per la linea di fondo del business.

Se sei un'impresa con un team di sviluppo JVM, non carichi il tuo server web, né gli oggetti di accesso ai dati, né nessuno di quei camion che è lo stesso utilizzato da tutti gli altri. Scarica Spring, o qualcos'altro che piace al tuo team di sviluppo, e investi il tuo capitale di sviluppo in codice che fa la differenza.

Quindi quando Evans parla di "entità", non sta parlando di righe in un database - questo è solo plumbing. Sta parlando di concetti che sono fondamentali per il modello di business - cosa significa essere una spedizione cargo? Se riceviamo un messaggio che annuncia che un container è stato scaricato sulla porta sbagliata o ha perso una connessione, cosa facciamo al riguardo?

E sì, assolutamente, nell'inquadramento di Evans / Fowler, le classi che rappresentano le entità di dominio includono metodi che modificano il loro stato. Racconta, non chiedere .

A repository class would be responsible for getting the OrderEntity from the db and returning it, but what class would be responsible for mapping it to an Order object on the domain?

Per Evans, capitolo 6, quel lavoro sarebbe anche accaduto all'interno del repository - più precisamente, si leggerà il valore (dalla modalità di sospensione), si converte il valore in una radice aggregata usando una Factory, e quindi si ritornerà alla radice applicazione.

(Quindi il codice dell'applicazione ottiene per vedere il valore di ibernazione, è racchiuso all'interno della radice aggregata).

    
risposta data 21.09.2018 - 05:08
fonte
1

È importante capire la tua applicazione in termini di architettura. Penso che la tua confusione sia probabilmente causata da una mancanza di comprensione delle perché le applicazioni sono strutturate come sono. Partendo dalle basi, un'architettura tradizionale "a strati", assomiglierà a qualcosa del tipo:

View -> Application -> Domain -> Persistence 

L'idea è che ogni livello è completamente disaccoppiato dagli strati sopra di esso, in modo tale che il flusso delle dipendenze in "verso il basso". La gestione delle dipendenze e l'accoppiamento è la preoccupazione più critica di qualsiasi architettura! Ciò che sopra compie è la possibilità di "scambiare" qualsiasi layer in modo tale che sappiamo quali altri pezzi dell'applicazione possono essere modificati come risultato (i livelli direttamente sopra) . Con questa idea potente in mente, ha senso strutturare le tue dipendenze in un modo in cui:

Volatile Modules -> Stable Modules

Poiché il tuo Domain è probabilmente il modulo meno volatile (quasi certamente non verrà sostituito), possiamo usare il DIP (principal di inversione delle dipendenze) per "sistemare" l'architettura stratificata sopra a:

View -> Application -> Domain <- Persistence

Ora abbiamo raggiunto l'architettura che stai descrivendo nella tua applicazione. Ora esaminiamo ogni livello e individuiamo ciò che ciascuno dovrebbe essere responsabile.

Il tuo livello View è il luogo in cui vivono i tuoi controllori. Ogni Controller semplicemente media il flusso di dati (e controllo) verso il tuo livello Application . È importante sottolineare che i controller non contengono la logica aziendale . Contengono solo la logica necessaria per trasformare l'input dal mondo esterno in concetti che il tuo strato Application può comprendere e trasformare l'output dal tuo strato Application in concetti che il mondo esterno può comprendere. Se abbiamo "scambiato" il tuo layer View (o ne abbiamo aggiunto un altro) da un'interfaccia web all'interfaccia della riga di comando, avresti avuto bisogno di nuovi controller. Gli input / output per View sarebbero necessariamente diversi.

Il livello Application viene utilizzato per eseguire casi d'uso per i tuoi utenti. Lo fa coordinando il tuo Domain . Ancora una volta, è importante che questo livello non contenga logica aziendale. Ad esempio, per cambiare una password:

class ChangePasswordCommand
{
    private string newPassword;

    public string UserId { get; set; }

    public string CurrentPassword { get; set; }

    public string NewPassword
    { 
        get { return newPassword; } 
        set { 

            if( value.Length > 50 ) {
                throw new System.ArgumentException("New password must be less than 50 characters");
            }

            newPassword = value;
        }
    }
}


class ChangePasswordCommandHandler
{
    private IUserRepository userRepository;

    handle( ChangePasswordCommand cmd )
    {
        userId = cmd.UserId;
        newPassword = cmd.NewPassword;
        currentPassword = cmd.CurrentPassword;

        user = this.userRepository.FindById(userId);

        // this may raise a UserChangedPasswordDomainEvent
        // or throw a PasswordIsNotDifferentException
        user.changePassword(newPassword, currentPassword);

        this.userRepository.save(user)
    }
}

Si noti che non si verifica qui alcuna "logica" pertinente alla modifica effettiva di una password. L'invariante che ChangePasswordCommand protegge non ha nulla a che fare con la tua logica aziendale . Cioè, il tuo CEO probabilmente non è preoccupato della lunghezza di una password Users . La lunghezza della password invariante è un dettaglio tecnico del sistema (ad esempio il campo del database di backup è VARCHAR (50) o qualcosa del genere) e vogliamo "fallire velocemente".

Il gestore di comandi, di per sé, prende semplicemente un input che capisce ( ChangePasswordCommand ) e coordina il modello del dominio per eseguire il comando. Cose come la gestione delle transazioni dovrebbero verificarsi anche nel tuo livello Application . Pensa a ogni caso d'uso come a un problema in 3 fasi:

  1. Ottieni pezzi di modello di dominio nella memoria necessari per eseguire il caso d'uso.
  2. Coordinate modello di dominio (cambia stato) 2.5. Gestisci / bolla le eccezioni
  3. Modello dominio persistente

Vale anche la pena notare che la dipendenza da IUserRepository . Questa interfaccia è definita nel tuo livello Domain , ma implementata nel tuo livello Persistence (questo è il DI aggiunto).

Seguendo il flusso del controllo, raggiungiamo il tuo livello Persistence . Questo è dove esiste il tuo ORM (e solo qui!). Ad esempio il UserRepository.FindById potrebbe essere simile a:

public User FindById( string userId )
{
    // 1. get UserEntity
    // 2. map UserEntity to User
    // 3. return User
}

Non c'è davvero bisogno di qualche DTO extra o altro a meno che non si intenda utilizzare una libreria per la mappatura che richiede un determinato formato. Ancora una volta, l'idea è che questo livello può essere completamente sostituito e NESSUN ALTRO parte della tua applicazione potrebbe nemmeno conoscere la differenza. Gli unici vettori di cambiamento per questo livello sono quando modifichi / aggiorni il tuo ORM o il Domain .

Infine, ti mostriamo il Domain . Questo è dove sta accadendo tutto. È qui che ci assicuriamo che 2 password non possano essere uguali e che modifichino il modello di dominio di conseguenza. Non c'è bisogno di troppe spiegazioni qui.

Infine, vorrei fare una nota qui che TUTTO QUI SOPRA è veramente necessario solo nel contesto della SCRITTURA ad un sistema. Puoi leggerlo da quel che vuoi (non c'è pericolo di uno stato non valido durante una lettura). Se hai bisogno di permessi utente, prendili. Non complicarlo troppo. Ottieni i dati necessari per il tuo View come vuoi, MA assicurati di scrivere solo i dati attraverso i canali appropriati (il tuo Domain ). Questa è l'idea di CQRS.

Sei libero di creare un progetto di modello di lettura separato, ma in questo progetto non vi è alcun concetto o bisogno di una Domain , quindi finirà per essere ridondante. Meglio definire solo alcuni servizi di lettura per organizzarlo, se lo desideri. Riesco a capire la confusione di alcuni metodi Controller (letture) utilizzando un progetto e altri metodi (scritture) utilizzando un altro, ma un View ne ha bisogno entrambi.

    
risposta data 21.09.2018 - 20:34
fonte

Leggi altre domande sui tag