Modelli di mapping API (DTO) per modelli di dominio avanzati

4

Come implementare il PUT di HTTP che funziona con le raccolte secondarie quando si utilizzano i modelli di dominio ricco di DDD?

Supponiamo di avere una radice aggregata con una raccolta di elementi ben incapsulata:

(Ho omesso le proprietà specifiche di persistenza come Id per brevità)

public class Foo : IAggregateRoot
{
    private readonly List<Bar> _items = new List<Bar>();

    public IReadOnlyCollection<Bar> Items => _items;

    public void AddItem(Bar bar)
    {
        _items.Add(bar);
    }

    public void RemoveItem(Bar bar)
    {
        _items.Remove(bar);
    }
}

Ora vogliamo implementare PUT /foos/{id} dove passi DTO come:

public class FooDto
{
    public IEnumerable<BarDto> Items { get; set }
}

Ora il problema è che non possiamo semplicemente mappare FooDto a Foo . Diventa un problema complesso da risolvere, specialmente quando vuoi rimuovere del Bar da Items .

Restiamo con due opzioni:

  • Crea 2 percorsi separati per aggiungere e rimuovere elementi, come POST /foos/{id}/items e DELETE /foos/{fooId}/items/{itemId}
  • Scrivi una logica distorta per confrontare le modifiche nella raccolta e in base a quella deduci ciò che è stato aggiunto e rimosso e chiama AddItem o RemoveItem rispettivamente

C'è qualcosa che mi manca qui o che sbaglio? Sarebbe meglio usare modelli di dominio anemici per semplificare l'implementazione dell'API Web? Ciò significherebbe che le API Web detterebbero come dovrebbero essere i miei modelli di dominio e penso che sia un po 'sbagliato.

Una domanda simile che ho trovato durante la ricerca di soluzioni: Le API RESTful tendono a incoraggiare modelli di dominio anemici?

    
posta Konrad 06.09.2018 - 22:16
fonte

3 risposte

2

Non considererei la tua opzione n. 2 come "logica distorta".

Hai solo bisogno di una funzione di uguaglianza (qualcosa per determinare se due Bar s sono uguali) e poi:

toAdd = elements in newItems that don't exist in currentItems
toDelete = elements currentItems that don't exist in newItems

Non conosco C #, ma in pseudo-codice potrebbe essere qualcosa del genere:

for e in newItems:
    if not e in currentItems:
        currentItems.add(e)

for e in currentItems:
    if not e in newItems:
        currentItems.remove(e)
    
risposta data 07.09.2018 - 02:37
fonte
0

How to implement HTTP's PUT that works with child collections when using DDD's rich domain models?

Con grande difficoltà.

PUT , come DELETE e PATCH , hanno semantica di authoring remoto. "Rendi la risorsa simile a questa".

L'idea di base è che posso prendere qualsiasi editor HTTP che capisca il tipo di media, e usarlo per caricare una nuova rappresentazione, apportare modifiche ad esso, quindi memorizzare le modifiche - senza bisogno di sapere nulla su come l'origine il server memorizza tali informazioni.

Pertanto, l'onere è sul server per capire come convertire la nuova rappresentazione in comandi da inviare alla propria memoria di supporto.

Una possibile risposta è mettere più peso sul tuo tipo di media. Le risorse possono avere più di una rappresentazione e non è necessario supportare PUT per tutte loro. 415 Unsupported Media Type è lì per te nel caso in cui tu debba rifiutare un PUT che non puoi convertire in messaggi che il tuo modello di dominio comprenderà.

Il flusso potrebbe essere simile a

GET /foo
Content-Type: application/json

E il consumatore che guarda questa rappresentazione vede che desidera modificare la risorsa. Quindi chiede la rappresentazione modificabile

GET /foo
Content-Type: application/vnd.hacky-workarounds.edit+json

PUT /foo
Content-Type: application/vnd.hacky-workarounds.edit+json

Potresti immaginare una rappresentazione analoga a application / json-patch + json , con operazioni che corrispondono al messaggio di comando che il tuo modello di dominio comprende e operazioni di query che descrivono lo stato iniziale (ad esempio, l'hash dell'albero corrente).

A seconda dello schema, il client potrebbe essere in grado di convertire il DTO originale nella rappresentazione modificabile da solo, senza dover eseguire un GET. HTTP è senza stato, quindi il successivo PUT dovrebbe comportarsi allo stesso modo a prescindere.

Un altro approccio da considerare è che il modello di dominio ha due responsabilità: assicurarsi che lo stato sia internamente coerente e assicurarsi che le transizioni siano valide. In altre parole, il server di origine non ha necessariamente bisogno di applicare le stesse regole aziendali, ma invece di verificare che le regole siano state applicate correttamente.

Pertanto, verifichi che il DTO sia internamente coerente e controlli che la transizione dallo stato precedente a quello nuovo sia legale, e se entrambi ti trattengono tieni semplicemente il risultato senza preoccuparti di ripetere il lavoro da solo.

Ma la vera risposta è quella che odi - diff le due rappresentazioni, decodificare i comandi necessari per trasformare l'una nell'altra e quindi applicare tali comandi tramite il modello di dominio.

PUT può essere usato per creare risorse, oltre a modificarle (a condizione che tu sia a tuo agio con la nozione che un client ottiene un certo controllo dell'URI). Quindi potresti mettere una rappresentazione di un messaggio di comando in qualche nuova risorsa. Probabilmente vorresti utilizzare l'intestazione If-None-Match per assicurarti che l'URI particolare non sia già occupato . Quindi ogni comando otterrebbe un URI univoco, selezionato dal client. Quello che non ottieni in quel caso è l'invalidazione della cache del DTO (che ha già il proprio URI)

Permettere al client di selezionare un URI per la risorsa del messaggio di comando è analogo a consentire al client di selezionare il proprio identificatori di correlazione .

    
risposta data 09.09.2018 - 18:00
fonte
-1

Il problema è che hai Foo e FooDto . Cos'è FooDto ? È quella parte del "Dominio"? Probabilmente è una cosa tecnica solo a causa di qualche framework che ne ha bisogno.

Quindi fondamentalmente già hai oggetti anemici, ecco perché stai lottando. Devi liberarti di tutte le cose anemiche per essere in grado di fare davvero un modello di dominio "ricco". potresti ricevere solo il Foo completo su PUT . Sei tu a definire come deserializzare da JSON (o qualsiasi altra cosa), in modo da poter creare facilmente l'oggetto "giusto" immediatamente, senza intermediari, quindi potresti utilizzare immediatamente i "veri" metodi di business per far funzionare qualsiasi richiesta.

    
risposta data 07.09.2018 - 21:49
fonte

Leggi altre domande sui tag