Come posso disaccoppiare query e contesto in uno scenario "oggetto query"?

2

Sto usando un modello oggetto query (simile a questo ) per gestire le query disparate evitando facciate / repository bloadati.

Un oggetto query accetta un numero di parametri del costruttore, che rappresentano gli argomenti di query. La query viene quindi passata a un IQueryHandler dal chiamante, nel quale viene iniettato IDataContext . Il IDataContext viene quindi passato a un metodo Execute di IQuery .

La cosa che non mi piace è questa:

public interface IQuery<TResult>
{
    public TResult Execute(IDataContext context);
}

Poiché IDataContext è passato al metodo e quindi dichiarato esplicitamente nell'interfaccia, non c'è alcuna opzione per avere una query che recuperi le cose tramite un meccanismo diverso - IQuery è accoppiato a qualsiasi cosa definisca l'interfaccia IDataContext . Supponiamo, per esempio, di passare da un archivio DB SQL a un DB del documento.

Ho provato un paio di alternative, ma nessuno dei due riesce a farmi dove voglio essere.

Il primo consiste nell'iniettare il contesto dei dati nel costruttore della query e utilizzare una factory per generare gli oggetti query. Questo disaccoppia correttamente le interfacce, ma ora lavorare con le query è più complicato. Invece di usare l'inizializzatore costruttore / oggetto per impostare la query, i chiamanti devono fare qualcosa del genere:

var query = _queryFactory.Create<PersonQuery, Person>(); // Second type argument can't be inferred from first due to limitations in generic type inference.
query.Name = "Bob";
query.Age = 32;

var result = query.Execute();

Il secondo è di astrarre l'esecuzione della query - e quindi la dipendenza da IDataContext - in una classe handler, quindi avere un'altra classe che risolve un gestore per ogni query, che di nuovo astrae la dipendenza da qualsiasi interfaccia:

public class ISomeQueryHandler : IQueryHandler<SomeQuery, SomeQueryResult>
{
    private readonly IDataContext _context = ...;

    public SomeQueryResult Execute(SomeQuery query) { ... }
}

Non mi piace l'opzione questa perché significa che ogni nuova query implica la scrittura di due classi, che, di nuovo, sono ingombranti e aumentano il potenziale per una query di esistere senza alcun modo di gestirla ( coinvolge anche un'interfaccia marker vuota per le query poiché il metodo Execute viene spostato sul gestore, che si sente sempre disattivato). Implica anche una risoluzione delle dipendenze basata su una convenzione piuttosto funky per ottenere il gestore di destra per ogni query.

La creazione di un'astrazione su IDataContext non è realmente fattibile a causa della sua complessità.

C'è un mezzo felice che non ho visto?

    
posta Ant P 16.01.2015 - 14:12
fonte

2 risposte

2

Suppose, for example, I want to switch from an SQL DB store to a document DB.

No, non lo sei. La possibilità che ciò accada è estremamente sottile e, anche se lo rendi conto, cambierai il tuo modello, perché probabilmente la modellazione relazionale sta trapelando.

Ma se dovessi farlo, separerei Execution da Initialization della query.

Per prima cosa vorrei creare interfacce:

public interface IQuery<TResult>
{
    TResult Execute();
}

public interface ISqlQuery
{
   void SetContext(IDataContext context);
}

Quindi, la query può implementare entrambi se ha bisogno del contesto SQL.

Quindi, nel gestore, controlla il tipo di anatra se la query implementa un'interfaccia specifica e se sì, eseguila. Questa interfaccia è una query che dice "Ho bisogno di questo servizio, qualcuno lo fornisce". E i gestori cercano di fornirlo.

public class Handler
{
    public TResult Execute<TResult>(IQuery<TResult> query)
    {
        if (query is ISqlQuery)
        {
            (query as ISqlQuery).SetContext(_datacontext);
        }

        return query.Execute();
    }
}

Questo ha due vantaggi. Innanzitutto, query diverse potrebbero essere indirizzate a diversi archivi di dati. In secondo luogo, le query possono avere più origini dati semplicemente implementando più interfacce. Quindi la query può accedere a DB, servizio esterno e forse qualcos'altro allo stesso tempo.

    
risposta data 13.12.2015 - 11:27
fonte
0

Una soluzione che ho usato per questo tipo di problema è aggiungere un livello di riferimento indiretto al contesto. In questo caso, rinominerò IDataContext in ISQLDataContext e creerò un nuovo IDataContext con una proprietà "SQLContext" per recuperare il contesto esistente. Gli oggetti di query possono quindi essere trasformati in modo banale per utilizzare la nuova interfaccia e puoi aggiungere nuovi tipi di contesto al livello più alto quando ne hai bisogno.

Questa soluzione non è l'ideale - non è conforme al principio aperto / chiuso per quanto riguarda l'aggiunta di nuovi archivi dati, ma (a) è improbabile che sia un tipo comune di modifica e (b) come l'implementazione l'oggetto contesto dati sarà molto semplice, la maggior parte della logica dietro l'OCP (ovvero che la modifica del codice esistente è più probabile che rompere qualcosa esistente rispetto all'aggiunta di un codice completamente nuovo) semplicemente non si applica.

    
risposta data 16.01.2015 - 15:17
fonte

Leggi altre domande sui tag