Pulisci architettura con C #: una progettazione migliore per eseguire la convalida in oggetti valore

1

Sto creando un'applicazione la cui architettura è basata su Architettura di Uncle Bob's Clean Architecture e DDD . Nota che è BASED su DDD, quindi mi sono dato la libertà di differire dal rigo DDD.

Per creare questa applicazione, sto usando C # con .Net Standard 2.0

Uno dei principi di DDD si riferisce a Value Objects. La definizione di oggetti valore, secondo Wikipedia è la seguente:

Value Object

An object that contains attributes but has no conceptual identity. They should be treated as immutable.

Example: When people exchange business cards, they generally do not distinguish between each unique card; they only are concerned about the information printed on the card. In this context, business cards are value object

Ora, voglio che il mio Value Objects non permetta la loro creazione se qualche convalida non ha successo. Quando succede, un'eccezione verrebbe generata durante l'istanziazione. In realtà intendevo lanciare un'eccezione, perché il nucleo dell'architettura non si aspetta che i dati non validi raggiungano quel punto.

Prima di andare oltre su questa domanda, per darti altri ragazzi, qui è la mia architettura (NOTA: ancora incompleto):

Le regole che seguo in questa architettura sono:

  1. Un livello può conoscere solo le interfacce del suo immediato prossimo strato più vicino
  2. Un livello non può sapere nulla su qualsiasi livello più esterno
  3. Tutte le comunicazioni tra livelli DEVONO essere eseguite tramite le interfacce
  4. Ogni livello deve essere distribuibile indipendentemente
  5. Ogni livello deve essere indipendentemente sviluppabile

Per comprendere meglio le frecce in questo diagramma, ti consiglio di leggere le domande di tali scambi dello stack:

link

Aggregation vs Composition

Ora, la sfida che sto affrontando adesso è trovare un buon modo per usare i validatori. Non sono soddisfatto della mia architettura in questo punto. Il problema è il seguente:

Dal momento che posso avere migliaia di oggetti valore istanziati in un dato momento, non voglio che ogni istanza degli oggetti valore abbia un metodo di istanza per eseguire la convalida. Voglio che il metodo di validazione sia statico, poiché la sua logica sarà la stessa per ogni istanza. Inoltre, desidero che la logica di convalida sia disponibile per il livello superiore dell'architettura da utilizzare per eseguire convalide senza tentare di creare un'istanza degli oggetti valore, causando così il lancio di un'eccezione costosa.

Il problema è: C # NON CONSENTE il polimorfismo con metodi statici, quindi non posso fare qualcosa del tipo:

internal interface IValueObject<T>
{
    T Value { get; }
    static bool IsValid(T value);
}

Come posso ottenere questa funzionalità senza ricorrere al polimorfismo dei metodi statici e, allo stesso tempo, non sprecare memoria?

    
posta André Marcondes Teixeira 15.03.2018 - 20:56
fonte

1 risposta

3

La risposta alla tua domanda è solo per mettere la logica nel costruttore. L'utilizzo di un metodo statico vs istanza non influisce sulle prestazioni nel modo in cui si pensa che lo farà, e anche metodi come isValid sono quasi sempre un segno di un modello anemico. Ma c'è un problema più grande in gioco qui:

Capisco la tentazione di convalidare gli attributi di ciascun oggetto valore sulla costruzione per generare eccezioni / catturare invarianti il prima possibile, ma ciò può portare a tutti i tipi di altri problemi man mano che il sistema cresce perché la convalida è quasi sempre un prodotto del contesto (di cui un costruttore non ne ha). Inoltre offre una certa tendenza a creare invarianti superficiali. Il posto migliore per applicare le regole relative ad alcuni dati è spesso al punto di utilizzo di tali dati. Ciò consente al contesto di svolgere un ruolo nel determinare quali invarianti esistono.

Ad esempio, anziché imporre un'invarianza per quanto riguarda i dati consentiti su BusinessCard durante la costruzione (ad esempio non c'è SwearWords o qualcosa), applica l'invariante al transfer di quel BusinessCard da un SalesAssociate a un Customer . posticipando l'applicazione (la decisione) il più tardi possibile (abbinata al caso d'uso), sei in grado di usare il contesto per aiutare a guidare la decisione (hai più dati a tua disposizione ).

Naturalmente, questa non è una legge del DDD (o di qualsiasi sistema) - potrebbe non esserci alcun caso d'uso concepibile per startDate per mai essere maggiore di endDate per esempio - ma questa idea di "differimento" è una pietra miliare della buona architettura. Quando ti ritrovi a ripetere un controllo per SwearWords in qualche altra transazione, ti sarà stata data un'opportunità preziosa: la possibilità di fare un po 'di conoscenza e possibilmente refactoring il tuo modello verso una visione più profonda. C'è qualche filo o idea che lega il transfer di un BusinessCard con questa nuova transazione? Può essere estratto e reso più esplicito? Altrimenti, nella peggiore delle ipotesi stiamo parlando dell'invocazione di un metodo statico (o privato) in due punti qui per eseguire il controllo effettivo.

Ora, per il mio stupido esempio non ci può essere assolutamente alcun motivo per un BusinessCard di includere un SwearWord e, alla fine, mettere l'applicazione di questo invariante nel costruttore per evitare ogni possibile duplicazione, ma io posso garantire che questo non sia sempre il caso. Inoltre, è molto più facile rifattorizzare l'applicazione "verso l'alto" (da applicare a tutti BusinessCards nella costruzione) che trovare, in un secondo momento, un motivo per cui deve essere rimosso e spingendolo "verso il basso" dove sarai costretto per dare la caccia singolarmente a ogni caso d'uso e "riapplicare" la regola, o peggio, trovare una sorta di work-around speciale.

La qualità del design di un sistema non dipende da quanto bene funzioni (ci sono molti sistemi mal progettati che funzionano bene), piuttosto quanto facilmente può essere modificato . Quando il tuo obiettivo è di progettare un sistema con un'architettura flessibile, manutenibile, in grado di svilupparsi e implementabile, una semplice regola empirica da seguire - che trovo tende a guidare gli sviluppatori nella giusta direzione -  è defer quante più decisioni possibili finché assolutamente è necessario.

    
risposta data 16.03.2018 - 21:53
fonte

Leggi altre domande sui tag