Quale tipo di dati deve restituire il gateway in Pattern di deposito per eliminare il refactoring quando si cambiano i meccanismi di persistenza?

7

Seguendo questa descrizione del modello di deposito, abbiamo tre principali preoccupazioni che richiedono le proprie classi:

  1. Il "Repository", che accetta e restituisce i modelli di dominio.
  2. Il "Gateway", che prende i dati dal modello di dominio e restituisce una sorta di dati generici
  3. Il "Fabbrica", che prende i dati "generici" dal Gateway e li associa al Modello di Dominio

Un repository ha bisogno di due oggetti per fare il suo lavoro:

  1. Un "gateway" per l'interazione con l'archiviazione persistente
  2. Una "fabbrica" per tradurre i dati dal gateway nel modello di dominio

Quando si ha a che fare con linguaggi strongmente tipizzati, ogni variabile ha bisogno di un "Tipo". Se si specifica tale tipo e lo si utilizza nel repository, lo si abbina al modello di dati sottostante del livello di persistenza e quindi lo si passa al factory, ma gli esempi che trovo mostrano il repository che tratta direttamente sia il gateway < em> e Factory.

Un esempio delle interfacce per un "Blog"

public interface IBlogGateway
{
    // Returns generic data type so switching persistence
    // does not require refactoring in the Repository
    object Find(long id);
}

public interface IBlogFactory
{
    // Accepts generic data type so switching persistence
    // does not require refactoring in the Repository
    Blog Make(object data);
}

public interface IBlogRepository
{
    Blog Find(long id);
}

E l'implementazione delle interfacce:

public class SqlBlogGateway : IBlogGateway
{
    public object Find(long id)
    {
        DataTable table = // Find from database
        DataRow row = table.Rows.Count == 1
                    ? table.Rows[0]
                    : null;

        // Implicit cast from DataRow to object (this code smells)
        return row;
    }
}

public class SqlBlogFactory : IBlogFactory
{
    public Blog Make(object data)
    {
        // Explicit cast from object to DataRow (this code smells)
        DataRow row = (DataRow)data;
        Blog blog = new Blog(long.Parse(row["ID"].ToString()))
        {
            Title = row.Field<string>("TITLE")
        };

        return blog;
    }
}

public class BlogRepository : IBlogRepository
{
    private IBlogFactory factory;
    private IBlogGateway gateway;

    public BlogRepository(IBlogFactory factory, IBlogGateway gateway)
    {
        this.factory = factory;
        this.gateway = gateway;
    }

    public Blog Find(long id)
    {
        object data = gateway.Find(id);

        if (data == null)
            return null;

        return factory.Make(data);
    }
}

In particolare sto lavorando con C # in .NET, ma questo è applicabile a qualsiasi linguaggio orientato agli oggetti strongmente tipizzato. Quando persistiamo in un database, potremmo occuparci di DataSet , DataTable e DataRow oggetti (o array per PHP o HashTable per Java). Quando arriva il momento di ridefinire il "Gateway" per persistere in un servizio Web, questi tipi non sono sufficienti, perché un servizio web non è probabile che utilizzi un DataTable . Invece specificherà il proprio oggetto Data Transfer.

Potresti avere il Gateway che restituisce un tipo object , e "Factory" prende un tipo object e lo lancia sul tipo corretto, ma poi perdi tutti i vantaggi di un linguaggio strongmente tipizzato, come ad esempio compilare controllo del tempo e Intellisense / Completamento automatico in un IDE. Inoltre, se si esegue il cast di object e si torna a un tipo specifico per il gateway, è possibile introdurre alcuni errori di runtime funky e difficili da debugare. Sembra che il repository abbia bisogno di un gateway e un gateway ha bisogno di una fabbrica, ma il repository non dovrebbe conoscere la fabbrica.

Per eliminare la necessità di refactoring del repository quando si modificano i meccanismi di persistenza, quale tipo di dati dovrebbe restituire il gateway?

    
posta Greg Burghardt 26.10.2015 - 16:23
fonte

3 risposte

4

Idealmente, vuoi solo che la tua classe Gateway sappia con quale back-end di persistenza sta parlando, e tutte le altre parti dovrebbero essere agnostiche al back-end. Sfortunatamente, stai scoprendo che, data questa particolare implementazione, la classe Factory non può essere agnostica perché ha bisogno di sapere che tipo di object sta ottenendo.

Hai menzionato che non puoi semplicemente restituire un DataTable , ecc. perché un back-end di persistenza non di database non userebbe queste strutture. Possiamo invece passare una rappresentazione agnostica fuori dal Gateway? Diamo un'occhiata ad alcune opzioni:

  1. Restituisce DataTable . Se utilizzi solo per utilizzare la persistenza supportata dal database, esegui questa operazione. Gestisce molti dettagli per te mentre è generico tra i motori di database. Non vuoi farlo perché sei preoccupato che un back-end non di database non lo usi.

  2. Restituisce un oggetto DTO personalizzato semplice dal gateway. Per IBlogGateway , potrebbe restituire un oggetto BlogDTO con solo membri di dati. Ciò significa più classi, ma puoi restituire un oggetto con tutti i tipi corretti già presenti. Questo crea anche una dipendenza dalla classe DTO in tutte le altre classi, ma questa dipendenza è ragionevole dal momento che tutti già si occupano di Blog e BlogDTO rispecchierà tale dipendenza.

  3. Restituisce un contenitore generico che può essere creato da qualsiasi tipo di persistenza. Ad esempio, possiamo convertire DataTable in IEnumberable<Dictionary<string, object>> , dove l'enumerabile contiene le righe del database (o i risultati del servizio web, ecc.) Rappresentati come Dictionary s mappando le chiavi ai valori. Perdiamo alcune informazioni sul tipo poiché tutti i valori sono object s, ma DataRow ha una limitazione simile che sembra altrimenti accettabile. Questo è paragonabile al modo in cui i linguaggi dinamici restituiscono risultati (come il PHP che restituisce una matrice di array associativi).

Voglio anche sottolineare (come @Ewan ha osservato in un commento alla domanda) che il Gateway, Factory e Repository non hanno strettamente bisogno di essere classi / interfacce distinte. Un repository può riempire il ruolo di tutti e tre, soprattutto quando si inizia. Più tardi, possiamo rifattorizzare le parti secondo necessità. Anche se il gateway viene scomposto, spesso la factory e il repository possono essere la stessa classe poiché hanno responsabilità strettamente correlate (creando oggetti di un tipo).

Contrariamente, la separazione delle classi fornisce l'opportunità per la composizione, consentendo (ad esempio) al gateway di variare separatamente dal resto del codice. Diverse implementazioni di gateway, corrispondenti a diversi back-end, possono essere composte in fase di runtime (e tempo di test) per una maggiore flessibilità.

    
risposta data 26.10.2015 - 18:05
fonte
2

Esempi scritti in Java. Penso che qualcosa di simile possa essere fatto in C #.

public interface GenericRow {
  String getString(String fieldName);
  Integer getInteger(String fieldName);
  Long getLong(String fieldName);
  Timestamp getDate(String fieldName);
  // getters for other types
}

public interface BlogGateway {
  GenericRow find(long id);
}

Lo stesso, ma con un certo grado di sicurezza del tipo in fase di compilazione (più elegante IMHO).

public interface Row<T extends Enum<T>> {
  String getString(T field);
  Integer getInteger(T field);
  Long getLong(T field);
  Timestamp getDate(T field);
}

public enum BlogField {
  TITLE,
}

public interface BlogGateway {
  Row<BlogField> find(long id);
}
    
risposta data 26.10.2015 - 18:08
fonte
0

C'è un caso in cui posso vedere che l'aggiunta di questi layer aggiuntivi di Gateway e Factory potrebbe aiutare. Questo è quando crei un repository CRUD per modello

Questo è spesso visto con l'uso di farmaci generici. vale a dire

Repo<Customer> repo = new Repo<Customer>();
Customer c = repo.GetById("1");

Con questo modello puoi vedere che puoi creare una classe Repo generica se hai anche fabbriche / gateway generici per i vari oggetti.

In questo caso potrebbero usare / restituire il DataRow non sql specifico di un paio di coppie di valori chiave o simili.

    
risposta data 26.10.2015 - 18:05
fonte

Leggi altre domande sui tag