Pattern di repository, identificatori diversi

5

TLDR; Ho un'interfaccia di repository e più origini dati, ognuna con un identificatore di dati diverso: come posso mantenere un solo metodo nell'interfaccia?

Ho bisogno di leggere un oggetto dominio OrderData da due diversi repository: uno è un servizio esterno e l'altro è un database locale. Chiamiamoli OutsideServiceOrderRepo e LocalDbOrderRepo . Entrambi implementano un'interfaccia chiamata IOrderRepoRead :

Interface IOrderRepoRead
  +GetOrder(OrderEntityData) : Order

Non userò mai entrambe le implementazioni nello stesso caso d'uso, è una o / o situazione. Ho risolto come iniettare uno o l'altro repository nella radice della composizione, a seconda del caso d'uso. Il problema che ho è che ognuno di questi archivi di dati ha un identificatore diverso per i dati che sto cercando:

  • Il negozio locale ha un OrderId semplice che ho nel mio sistema.
  • Il servizio esterno richiede di interrogare utilizzando Tax_number+Name+what_not di una persona, ecc ...

Le idee che ho presentato finora sono le seguenti:

  1. Ho un tipo chiamato OrderEntityData con proprietà TaxNumber , Name , OrderId . Entrambe le implementazioni possono utilizzare questo tipo, ogni implementazione funziona con le proprietà richieste: OutsideServiceOrderRepo utilizza TaxNumber , Name , mentre LocalDbOrderRepo utilizza OrderId .
  2. Rilascio il OrderEntityData e ho due metodi diversi nel mio IOrderRepoRead .

.

Interface IOrderRepoRead
  +GetOrder(taxNumber, name, whatNot) : Order
  +GetOrder(orderId) : Order

Sono propenso ad andare con l'approccio n. 1, ma sembra comunque che ci sia una certa quantità di accoppiamento tra il repository e il codice client.

Come posso alternare in modo efficiente tra identificatori diversi?

    
posta robotron 16.01.2018 - 14:35
fonte

3 risposte

4

Mi sembra che i due servizi non siano la stessa cosa.

Se taxNumber, name, whatNot produce un id univoco, puoi unirlo a tutti insieme, usarlo come orderId e avere lo stesso GetOrder(taxNumber, name, whatNot) localmente del servizio.

Se non lo fanno, il servizio esterno e il repository locale non implementano la stessa interfaccia. Uno ottiene un record univoco e uno cerca le corrispondenze.

Non dovresti tentare di unirti a entrambi, perché anche se lo gestisci con un codice intelligente, la logica di Business non è la stessa. Avere:

Interface IOrderSearcher
  +GetOrder(taxNumber, name, whatNot) : Order

Interface IOrderRepository
  +GetOrder(orderId) : Order

Il tuo repository locale può implementare entrambi, se necessario.

    
risposta data 16.01.2018 - 18:08
fonte
3

Non c'è niente di sbagliato nell'approccio numero 2, e in effetti questo è l'approccio che ho visto di più. L'approccio n. 1 potrebbe funzionare fintanto che ci si appoggia all'incapsulamento per limitare il modo in cui i client possono utilizzare questo "oggetto finder di ordine":

public class OrderIdentifier
{
    public int OrderId { get; private set; }
    public string TaxNumber { get; private set; }
    public string Name { get; private set; }
    public string WhatNot { get; private set; }

    public OrderLookupMethod LookupMethod
    {
        get => OrderId > 0 ? OrderLookupMethod.ById : OrderLookupMethod.ByTaxInfo;
    }

    public OrderIdentifier(int orderId)
    {
        OrderId = orderId;
    }

    public OrderIdentifier(string taxNumber, string name, string whatNot)
    {
        // ...
    }
}

public enum OrderLookupMethod
{
    ById = 0,
    ByTaxInfo = 1
}

E poi nel tuo repository:

public Order GetOrder(OrderIdentifier id)
{
    Order order = null;

    switch (id.LookupMethod)
    {
        case OrderLookupMethod.ById:
            order = // Find by Id
        case OrderLookupMethod.ByTaxInfo:
            order = // Find by tax info
        default:
            throw new ArgumentException(...);
    }

    return order;
}

La combinazione dei due costruttori e rendere privati i setter per la classe OrderIdentifier garantisce che i client li istanzino correttamente:

OrderIdentifier byId = new OrderIdentifier(4); // Find by Id
OrderIdentifier byTaxInfo = new OrderIdentifier(taxNumber, name, whatNot); // Find by tax info
    
risposta data 16.01.2018 - 15:37
fonte
2

Se stai cercando la soluzione più semplice dal tuo punto attuale, l'opzione "due metodi e un condizionale" funziona perfettamente.

Se stai cercando una soluzione polimorfica più elegante, senza passare parametri ridondanti, ecco un paio di approcci che potresti prendere in considerazione.

Ciò che realmente hai sono due diverse implementazioni di un'interfaccia IOrderQuery (o qualsiasi altra cosa tu preferisca chiamarla). Il problema è che queste implementazioni necessitano di parametri di costruzione diversi, forniti al momento della richiesta e la composizione è fatta nella radice dell'applicazione.

Puoi risolvere il problema con una fabbrica.

public class InternalOrderQuery : IOrderQuery {
    public InternalOrderQuery(string orderId) { ... }

    public Order Get() { // fetch order by ID }
}

public class ExternalOrderQuery : IOrderQuery {
    public ExternalOrderQuery(string taxNumber, string name, ...) { ... }

    public Order Get() { // fetch order by things }
}

public interface IOrderQuery {
   Order Get();
}

public class OrderQueryFactory {
    public IOrderQuery Create(RequestData stuff) { ... }
}

Inietta la fabbrica e, in Create , crea la query ordine come appropriato: il resto del codice non conosce la differenza.

In alternativa, il tuo primo approccio funziona bene ma può beneficiare di una migliore incapsulamento:

public class OrderRequest {

    private String name;
    private String taxNumber;
    private String orderId;

    public OrderRequest( ... ) { // assign fields }

    public Order Execute(IOrderRequestContext context) {
        return context.Get(name, taxNumber, orderId);
    }
}

Implementa IOrderRequestContext due volte - una volta da eseguire contro il DB remoto e una volta contro il locale.

Potresti fare questo e rendere i parametri di richiesta coppie chiave / valore - in questo modo il contesto può solo leggere ciò di cui ha bisogno senza dover definire esplicitamente ogni parametro possibile per ogni possibile implementazione.

    
risposta data 16.01.2018 - 19:23
fonte

Leggi altre domande sui tag