Passare una callback fino in fondo o lanciare un'eccezione?

3

Ho un servizio, chiamalo Service A , che è basato su Storage Layer , chiamalo DB , cioè profondo a 5-6 livelli. Questo livello dipende da molti altri servizi.

Service A sta avendo problemi di memoria e la correzione è di generare un'eccezione da DB se la risorsa che stiamo cercando di recuperare è troppo grande per Service A . La risorsa stessa viene recuperata in più roundtrip, quindi contiamo il numero di roundtrip ed è troppo alto, gettiamo e interrompiamo e proviamo un percorso di codice diverso, più lento, più costoso.

Il problema è che questo limite è specifico per Service A . Tutti gli altri servizi che dipendono da DB non hanno bisogno di questo limite.

Soluzione 1: leggi le impostazioni globali impostate dal servizio

FetchResource(...) {
    if (numberOfRoundTrips > ServiceDefinedLimit) {
        throw new Exception("Resource is too large");
    }
}

In questo momento il limite è Infinity per tutti i servizi ma Service A , che sembra approssimativo. Altrimenti non è concettualmente diverso dall'impostare alcune impostazioni di configurazione che dicono "ThrowIfResourceTooLarge".

Soluzione 2: passaggio in un booleano

FetchResource(..., bool throwIfSizeTooLarge) {
    if (numberOfRoundTrips > HardCodedThreshold && throwIfSizetooLarge) {
        throw new Exception("Resource is too large");
    }
}

Il problema qui è che sembra una versione più complessa di Soluzione 1 . Inoltre, devo modificare ogni livello dal chiamante fino a questa funzione.

Soluzione 3: passaggio in una funzione

FetchResource(..., Func<State> condition) {
    if (condition(numberOfRoundTrips, ...)) {
        throw new Exception("Resource is too large");
    }
}

Il problema con il terzo approccio è, ancora una volta, la modifica di tutti i livelli sopra di esso per convogliare questo valore dal chiamante a questo livello. Sembra concettualmente giusto per me.

Cercare il feedback delle persone su quale sarebbe l'approccio migliore. Più ci penso, più il numero 1 sembra corretto e il # 3 mi sembra carino ma ha troppo lavoro da implementare ora.

    
posta Vishaal Kalwani 24.07.2018 - 02:37
fonte

4 risposte

5

The problem is this limit is specific to Service A. All other services that depend on DB do not need this limit.

Soluzione P: riassunto il problema

Resource r = service.fetchResource();    

Non aspettarti di usare il codice per gestire i tuoi piccoli problemi. Non chiedergli nemmeno di sapere a cosa sta parlando.

class ServiceA implements Service  {
    public Resource FetchResource(...) {
        if (numberOfRoundTrips > ServiceDefinedLimit) {
            throw new Exception("Resource is too large");
        }          
        return DB.resource("A");  
    }
}

Non aspettarti che altri servizi sappiano qualcosa sui tuoi strani problemi.

class ServiceB implements Service {
    public Resource FetchResource(...) {
        //what round trips?
        return DB.resource("B");
    }
}
    
risposta data 24.07.2018 - 05:42
fonte
0

Non puoi implementare una sorta di interfaccia di pre-verifica e passare le informazioni invece che verso il basso e prendere la decisione al di fuori dell'oggetto DB?

Ad esempio (pseudocodice):

tripLimit = 20
db = new DatabaseObject(...params);
db.prepareFetch(...request);
roundtrips = db.getRoundTrips();
if (roundtrips > tripLimit) {
    executeAlternatePlan();
}else{
    db.executeFetch();
}
    
risposta data 24.07.2018 - 03:35
fonte
0

Ho tre pensieri riguardo a questo design.

  1. Il chiamante sa davvero o si preoccupa se quel particolare servizio ha un limite di dimensioni? È supposto a saperlo? Sembra che tu stia violando la separazione delle preoccupazioni. Soprattutto se ci sono veramente 5 livelli da passare.

  2. Sono un grande fan di limitare la flessibilità quando la flessibilità supera i vincoli di sistema. Qual è il punto di passare questo flag o callback come parametro di input, se è lo stesso ogni volta? Tutto quello che stai facendo è interrogare il tuo sviluppatore per vedere se conosce il codice magico da mettere tra parentesi - la flessibilità non è effettivamente necessaria o utilizzata-- e se fallisce il quiz, il sistema si blocca.

  3. Cosa succede quando aggiungi memoria al server? Vuoi tornare indietro e cambiare un po 'di codice? Cosa succede se l'hai aggiunto solo su determinati server?

Per questi motivi, suggerirei

Opzione 1. Rendilo una soglia di dimensione configurabile che è impostata nel file di configurazione per il livello più vicino al problema dei dati / dimensioni.

Opzione # 4. Rendilo un parametro costruttore del servizio stesso, non del metodo. Impostalo nella tua routine di inizializzazione, ad es. quando imposti il tuo contenitore Inversion of Control o qualunque cosa tu stia utilizzando. E guidare l'inizializzazione con la configurazione, in modo da renderlo specifico per una particolare istanza dell'applicazione.

    
risposta data 24.07.2018 - 03:45
fonte
0

Questa è una delle aree in cui le caratteristiche del linguaggio C ++ lo rendono una scelta molto superiore a Java (che sembra tu usi, ma non hai detto).

Il modo migliore che conosco per configurare una funzionalità di un componente che è 'distante' (non direttamente chiamato da) da uno dei tuoi moduli, ma usato in modo diverso da altri moduli, è con le variabili di configurazione thread_local. Impostando le variabili di configurazione thread_local, non è necessario inquinare le interfacce e il codice che intervengono con le informazioni di configurazione che vengono passate attraverso i livelli del codice, e si può ancora avere codice distante configurare altri moduli distanti, utilizzati indirettamente (a differenza dei chiamanti).

QUESTO - la variabile statica thread_local - puoi farlo facilmente anche in Java. Dove c ++ brilla, (come al solito) - è con RAII ( link ). In C ++, puoi dichiarare

struct StorageLayer {...
    static thread_local optional<size_t> sMaxResourceSize;
    ....
};

struct StorageLayer::AdjustMaxResourceSize {
     optional<size_t> prev;
     AdjustMaxResourceSize (size_t sz) 
       : prev (sMaxResourceSize)
         {
           sMaxResourceSize = sz;
         }
     ~AdjustMaxResourceSize () 
      {
           sMaxResourceSize = prev;
      }
      // no assign/no copy CTOR etc...
};

e quindi nel tuo codice, dichiara semplicemente:

StorageLayer::AdjustMaxResourceSize adjustMaxSizeForServiceA { 300 };
...
rest of code which operates with this constraint, doesn't directly call StorageLayer
but does INDIRECTLY call it
...

L'unico modo in cui so di fare qualcosa di simile in Java è con i blocchi try / finally per salvare / ripristinare il valore di configurazione, e questo sarebbe altamente duplicativo.

Un esempio di utilizzo di questa tecnica nella vita reale (Stroika) eliminerebbe l'interruzione del thread:

e quindi l'utilizzo di esempio:

risposta data 24.07.2018 - 03:16
fonte

Leggi altre domande sui tag