Design del modello di dominio: best practice per la massima fluidità, incapsulamento ed estensibilità

6

Mi sto adoperando per avere l'architettura più gestibile possibile nella mia applicazione, ma non riesco a decidere un modello di dominio adeguato.

Ecco un esempio di modello di dominio per un utente:

public class User // Referred to as the domain model
{
    // A model whose properties map exactly to a database table schema 
    private UserDataModel Model { get; set; } // Posts data back to the database

    public string FirstName
    {
        get
        {
            return Model.FirstName;
        }
        set
        {
            // Validation & Observer logic here
            Model.FirstName = value;
        }
    }
    public string LastName
    {
        get
        {
            return Model.LastName;
        }
        set
        {
            // Validation & Observer logic here
            Model.LastName = value;
        }
    }
    public string FullName
    {
        return $”{FirstName} {LastName}”;
    }
    public Company Company // Other domain model with a similar design
    {
        get
        {
            return Model.Company;
        }
        set
        {
            // Validation & Observer logic here
            Model.Company = value;
        }
    }

    // List of other domain models with similar designs
    public IList<Order> Orders { get; set; } 
    // …

    internal User(UserDataModel user)
    {
        Model = user;
    }

    // Domain specific methods here 
    //(e.g. IsAdmin, CalculateBill, SendPasswordResetEmail, CanPlaceOrder, etc.)
}

In sostanza, le mie preoccupazioni riguardano le migliori pratiche per disporre di una progettazione di un dominio efficace, efficiente e gestibile che consenta un consumo robusto, una strong incapsulabilità e un'elevata estensibilità / manutenibilità o scalabilità. Le domande specifiche sono:

  1. Proprietà

    a) Se il mio modello di dominio contiene proprietà simili al modello di dati sottostante o proprietà che sono rilevanti solo per il consumo del modello di dominio dell'applicazione (cioè proprietà come FullName, flag booleani come Dirty (aggiornamenti si è verificato), Prototipo (cioè, un nuovo modello fittizio per la creazione da parte del cliente / consumatore)?

    b) il modello di dati sottostante deve essere esposto pubblicamente per l'accesso o dovrebbe essere accessibile solo tramite metodi? Dovrebbero essere restituite solo parti del modello dati o l'intero oggetto?

    c) il modello di dominio dovrebbe includere oggetti collegati (come l'oggetto Company nell'esempio) o solo il valore pertinente a quell'oggetto collegato (ovvero la proprietà Name dell'oggetto Company invece del intero oggetto)?

    d) I setter dovrebbero essere simili a quelli sopra o solo attraverso metodi chiamati sul modello di dominio o metodi privati chiamati nel setter della proprietà (cioè set {SetFirstName (valore);})? (Nota: i setter implementeranno le chiamate del motore di validazione e le chiamate dell'osservatore e le chiamate di servizio, quindi saranno relativamente pesanti)

  2. Costruzione

    a) Devo affrontare la costruzione di un nuovo modello di dominio dal cliente / consumatore tramite diversi metodi set o semplicemente mappando le proprietà a livello di client / consumatore o tramite uno schema factory / builder?

    b) Il mio modello di dominio dovrebbe nascondere completamente la struttura di dati sottostante che rappresenta (ad esempio, appare come se fosse un oggetto che non faceva mai parte di un database)?

    c) La convalida deve essere eseguita a livello di costruzione (ovvero quando le proprietà sono impostate) o lasciata al cliente / consumatore chiamando un metodo "IsValid" sul modello di dominio quando appropriato?

  3. Validation

    a) Gli errori di convalida devono essere memorizzati in una raccolta nel modello di dominio o in un modello di errore separato / classe singleton?

    b) La convalida deve essere eseguita a livello di modello di dominio o a livello di modello di dati?

  4. Recupero

    a) per gli elenchi di oggetti (cioè gli ordini), se l'elenco deve essere privato e accedere tramite un metodo "GetOrder (string orderNo)"? Non sarebbe una violazione del principio di Responsabilità Unica (I.e il metodo è uno pseudo repository / fabbrica) o no?

    b) Come si può recuperare un intero elenco senza influire sul modello di dominio contenente o sui modelli di dominio nell'elenco? Dovrebbe essere usato un clone nonostante sia potenzialmente impegnativo in termini di memoria (un utente potrebbe avere migliaia di ordini con i propri modelli di dominio collegati)?

Sto idealmente cercando di evitare lo sviluppo di un design di dominio non gestibile che è un dolore da consumare o testare. Voglio che il modello di dominio sia fluente e amp; il più robusto possibile, permettendogli così di essere letto come prosa al cliente / consumatore e rappresentando il dominio effettivo e non le strutture di dati sottostanti (cioè dovrebbe essere in grado di agire come se stesse da solo senza un back-end).

    
posta B1313 22.10.2017 - 03:53
fonte

2 risposte

7

Francamente, le classi di colla che passano semplicemente attraverso i campi dal modello di dati non aggiungono davvero un valore significativo alla tua applicazione, né sono particolarmente interessanti dal punto di vista funzionale.

Quindi, prima di approfondire le tue singole domande, propongo una semplice architettura fondamentale:

DB <---> ORM <---> SL/BLL <---> VM <---> V

Il DB è il tuo database. L'ORM è il tuo Object-Relational Mapper; comunica con il database tramite SQL ed espone i metodi CRUD. SL / BLL è il livello di servizio / livello di business logic; converte operazioni commerciali come CreateInvoice in metodi CRUD per il consumo da parte di ORM. La VM è il ViewModel; coordina l'interazione dell'interfaccia utente con SL / BLL. La V è la vista; contiene un'interfaccia utente di livello superficiale e codice di convalida code-behind.

Il tuo codice di esempio è indicativo di C #. In C #, il tuo ORM (molto probabilmente Entity Framework) può produrre classi di entità generate dal codice che possono essere incollate alla logica del dominio usando la parola chiave partial , che ti permette di scrivere dominio personalizzato e logica di validazione per ogni classe di entità senza il dolore della creazione tutto ciò che serve per DTO.

Un'architettura snella come questa dovrebbe permetterti di sviluppare un'applicazione facilmente manutenibile con il codice minimo necessario per il boilerplate. A meno che tu non stia costruendo un'architettura elaborata e cristallina in Java per un grande team di sviluppo, questa architettura rappresentativa dovrebbe essere tutta l'astrazione di cui avrai mai bisogno.

    
risposta data 22.10.2017 - 08:18
fonte
4
  1. a)

Should my domain model contain properties similar to the underlying data model

No, non è quasi mai il caso. Non deve essere il caso. I tuoi oggetti non devono memorizzare i loro dati. Alan Kay , un uomo dietro Smalltalk e ciò che ora realizziamo come OOP, voleva i suoi oggetti per sbarazzarsi dei dati . Ha proposto agli oggetti di avere solo un riferimento alla memoria in cui si trovano i suoi dati. È una conseguenza di una particolare mentalità riguardo alla ricerca di oggetti: dovrebbero essere identificati in base al comportamento desiderato , non dati. Quindi seguendo questo approccio sono giunto alla conclusione che non hai bisogno di un ORM (oltre al concetto semplicemente orribile). Ecco come puoi vivere senza di esso.

b) Idealmente, i dati non dovrebbero mai essere esposti a nessun oggetto tranne il suo proprietario. Solo il comportamento dovrebbe essere esposto. Questo è un principio OOP fondamentale chiamato incapsulamento . Da quando ho dimostrato come possiamo evitare che gli accessor di dati per ORM eseguano il proprio lavoro in un articolo precedente, qui è come evitare i Getter per l'interfaccia utente.

c) Rispondere a questo elemento non ha senso alla luce di ciò che ho già scritto, ma capisco che ci vuole un po 'di cambiamento di mentalità. Quindi risponderò in termini di DDD: un aggregato può contenere un link a un identificatore di un altro aggregato. È più semplice renderli distribuiti se necessario, dal momento che gli aggregati rappresentano le giuste giunture dei candidati per distribuire il sistema.

d) Dal momento che la maggior parte degli oggetti si rivelerà immutabile quando si applica un approccio nell'elemento b, anche questo non ha senso. Ma se non sei ancora pronto per questo - setters dovrebbe essere privato . Nessun setter è ancora meglio, dato che setters sono malvagi . Se devi modificare i dati, esporli come comportamento e utilizzare la tecnica nell'elemento b) non avrai mai bisogno di mutare lo stato dell'oggetto.

2.

a) Dovresti creare i tuoi oggetti usando la parola chiave "nuovo". Questo dovrebbe essere sufficiente. Non è necessario alcun DI-container . Nessun setter pubblico. Se insisti sull'utilizzo di un ORM - ok, associa la colonna del database alle proprietà dell'oggetto usando dati-mapper .

b) Vedi esempi qui .

c) Un campo campo sempre valido è prevalente in ddd-community. I modi più esotici per trattare la convalida sono con decoratori . Questa tecnica è apparsa a causa di una metafora di Lego-brick utilizzata da David West . Afferma che ci sono fondamentalmente due diversi stadi della vita dell'oggetto: la sua creazione e quando effettivamente funziona. Quindi niente dovrebbe impedirti di creare un oggetto. Tutti i comportamenti, i controlli dovrebbero essere fatti in fase di "lavoro". Ma ripeto che è un approccio piuttosto esotico.

  1. Qualcosa è già stato detto sulla convalida. Elencherò i collegamenti che mi sono stati utili: one , due , three , quattro .

  2. La domanda implica una mentalità incentrata sui dati. Penso che dopo aver letto i link che ho fornito avrai un'idea su cosa fare.

risposta data 31.10.2017 - 16:32
fonte