Implementazione di azioni per le entità incrociate

2

Ho un livello di dominio, che la mia architettura di applicazione contiene "sacro"; vale a dire, il livello del dominio non ha riferimenti né all'archiviazione dei dati, né ai problemi di presentazione (viceversa è consentito). Mi piacerebbe tenerlo così !

Tutto stava andando abbastanza bene finché non sono arrivato a un particolare requisito. Dato:

public class A
{
     public string IdA { get; private set; }
     public void DoWork()
}

public class B
{
    public string IdB { get; private set; }
    public string IdA { get; private set; }
    public Invaidate()
}

In alcune condizioni insolite, che A decide, quando DoWork è chiamato tutto B associato deve essere invalidato. Il problema, questa invalidazione richiede persistenza. Nota: questo caso limite è l'unica volta in cui A dovrebbe riguardare il suo B s associato. Il requisito per questa invalidazione non sarebbe ovvio per un consumatore di A .

Ho ideato e implementato una soluzione (1), oltre a pensare ad altre soluzioni. Tuttavia, non ne sono abbastanza soddisfatto, o degli altri, e desidero un input.

Soluzione 1: A Invalidates B s, chiamate Delegate per la persistenza

Vantaggio: trasparente per il consumatore, facile da consumare

Svantaggio: comportamento di salvataggio strano e incoerente, nessun'altra classe nel livello di dominio salva quando viene chiamato un metodo

class A
{
    private Action<B> SaveB; // Populated by ARepository when instance is retrieved, delegate points to sister repo BRepository
    public List<B> Bees { get; private set; }
    public DoWork()
    {
        // if edgeCase:
        foreach (B b in Bees)
        {
            b.Invalidate();
            SaveB(b);
        }
    }
 }

Soluzione 2: A Invalidates B s, A Repository Salva associato B s

Vantaggio: trasparente al consumatore, facile da consumare, coerente livello di dominio

Svantaggio: strato persistente incoerente, antipattern di persistenza monolitico *

* Forse il nome anti-pattern sbagliato?

class A
{
    public List<B> Bees { get; private set; }
    public DoWork()
    {
        // if edgeCase:
        foreach (B b in Bees)
            b.Invalidate();
    }
}
//...
class ARepository
{
     Update(A a)
     {
         Sql.SaveA(a);
         bRepository.Update(a.Bees);
     }
}

Soluzione 3: evento Trigger On Edge Case

Vantaggio: nessuna incongruenza nel comportamento del dominio o del livello di persistenza, nessuna preoccupazione per le entità incrociate

Svantaggio: difficile da consumare, espone la logica al consumatore, che dovrebbe essere responsabile del cablaggio dell'evento?!

class A
{
    public Event SaveBs/EdgeCaseHappened;
    public List<B> Bees { get; private set; }
    public DoWork()
    {
        // if edgeCase:
        foreach (B b in Bees)
            b.Invalidate();
        SaveBs.Fire();
       // or just...
       EdgeCaseHappened.Fire()
    }
}

Mentre ho già implementato 1, nessuna di queste tre soluzioni mi sembra del tutto giusta, c'è qualcosa qui che mi manca? Ho fatto un'assunzione errata da qualche parte?

Qual è il modo migliore di agire anche per me per mantenere le preoccupazioni trasversali come questa mantenibili e utilizzabili, isolando rigorosamente la mia logica aziendale?

Nota: questo è non un progetto DDD

    
posta TheCatWhisperer 10.12.2017 - 02:23
fonte

1 risposta

1

Dipende dal fatto che B s debba essere invalidato immediatamente quando il caso limite di A è successo, oppure no.

Se sì, proverei a rendere il progetto più simile al DDD, almeno a livello tattico. In questo caso A sarebbe una radice aggregata e B s si nasconderebbe dietro un limite aggregato. Quindi tutto ciò di cui hai bisogno è unire le responsabilità di ARepository e BRepository .

In caso contrario - implementerei qualcosa come Saga , anche se non sono a conoscenza delle tue specifiche. È più vicino alla tua soluzione 3. Poiché, ancora una volta, non sono a conoscenza della tua semantica, cercherò di dirla in modo astratto. Supponiamo che tu abbia un processo composto da diversi passaggi, quindi abbiamo consistenza finale . Uno di questi passaggi, ad esempio A::doWork() , potrebbe andare storto. Quindi richiede l'implementazione di alcune regole di rollback. In tal caso, si attiva un evento, che viene ascoltato da qualche controller, che delega la logica del dominio di rollback a A.Bees , quindi è possibile richiamare ogni B::invalidate() . Poiché sai in anticipo quale logica di business viene invocata, sai di quale repository hai bisogno - BRepository . Probabilmente questa soluzione è più facile da implementare se inverti i riferimenti: se non è A che ne conosce il B s, ma B s sa del loro A .

    
risposta data 10.12.2017 - 12:14
fonte

Leggi altre domande sui tag