Come evitare le interfacce chatty

10

Sfondo: Sto progettando un'applicazione server e creando dll separate per diversi sottosistemi. Per semplificare le cose, supponiamo di avere due sottosistemi: 1) Users 2) Projects

L'interfaccia pubblica degli utenti ha un metodo come:

IEnumerable<User> GetUser(int id);

L'interfaccia pubblica di Projects ha un metodo come:

IEnumerable<User> GetProjectUsers(int projectId);

Quindi, ad esempio, quando abbiamo bisogno di visualizzare gli utenti per un determinato progetto, possiamo chiamare GetProjectUsers e questo restituirà oggetti con informazioni sufficienti da mostrare in un datagrid o simile.

Problema: Idealmente, il sottosistema Projects non dovrebbe inoltre memorizzare informazioni utente e dovrebbe solo memorizzare ID degli utenti che partecipano a un progetto. Per pubblicare GetProjectUsers , è necessario chiamare GetUser del sistema Users per ogni ID utente memorizzato nel proprio database. Tuttavia, ciò richiede molte chiamate GetUser separate, causando un sacco di query SQL separate all'interno del sottosistema User . Non ho davvero provato questo, ma avere questo design chiacchierone influenzerà la scalabilità del sistema.

Se metto da parte la separazione dei sottosistemi, I potrebbe memorizzare tutte le informazioni in un unico schema accessibile da entrambi i sistemi e Projects potrebbe semplicemente fare un JOIN per ottenere tutti gli utenti del progetto in una singola query. Projects avrebbe anche bisogno di sapere come generare User oggetti dai risultati della query. Ma questo rompe la separazione che ha molti vantaggi.

Domanda: Qualcuno può suggerire un modo per mantenere la separazione evitando tutte queste chiamate individuali di GetUser durante GetProjectUsers ?

Ad esempio, l'idea che avevo era che gli utenti potessero dare ai sistemi esterni la possibilità di "etichettare" gli utenti con una coppia valore-etichetta e di richiedere agli utenti un determinato valore, per esempio:

void AddUserTag(int userId, string tag, string value);
IEnumerable<User> GetUsersByTag(string tag, string value);

Quindi il sistema Projects può taggare ogni utente quando viene aggiunto al progetto:

AddUserTag(userId,"project id", myProjectId.ToString());

e durante GetProjectUsers, potrebbe richiedere a tutti gli utenti del progetto una singola chiamata:

var projectUsers = usersService.GetUsersByTag("project id", myProjectId.ToString());

la parte di cui non sono sicuro è: sì, gli utenti sono agnostici dei progetti, ma in realtà le informazioni sull'appartenenza al progetto sono memorizzate nel sistema Users, non in Projects. Non mi sento naturale, quindi sto cercando di determinare se c'è un grosso svantaggio qui che mi manca.

    
posta Eren Ersönmez 25.01.2015 - 08:24
fonte

1 risposta

10

Ciò che manca nel tuo sistema è la cache.

Dici:

However, this requires a lot of separate GetUser calls, causing a lot of separate sql queries inside the User subsystem.

Il numero di chiamate a un metodo non deve essere uguale al numero di query SQL. Ottieni le informazioni sull'utente una volta, perché dovresti interrogare per le stesse informazioni di nuovo se non cambia? Molto probabilmente, potresti persino memorizzare nella cache tutti gli utenti in memoria, il che comporterebbe zero query SQL (a meno che un utente non cambi).

D'altro canto, rendendo il sottosistema Projects interrogando sia i progetti che gli utenti con un INNER JOIN , si introduce un ulteriore problema: si sta interrogando lo stesso pezzo di informazione in due diverse posizioni nel codice, rendendo invalidazione della cache estremamente difficile. Di conseguenza:

  • O non introdurrai mai la cache in qualsiasi momento,

  • O passerai settimane o mesi a studiare ciò che dovrebbe essere invalidato quando cambia una parte di informazione,

  • Oppure aggiungerai l'invalidazione della cache in posizioni dirette, dimenticando gli altri e risultando in bug difficili da trovare.

Rileggendo la tua domanda, noto una parola chiave che ho perso la prima volta: scalabilità . Come regola generale, puoi seguire lo schema successivo:

  1. Chiediti se il sistema è lento (ovvero viola un requisito di prestazione non funzionale o è semplicemente un incubo da utilizzare).

    Se il sistema è non lento, non preoccuparti delle prestazioni. Preoccupatevi di codice pulito, leggibilità, manutenibilità, test, copertura delle filiali, design pulito, documentazione dettagliata e di facile comprensione, buoni commenti sul codice.

  2. Se sì, cerca il collo di bottiglia. Lo fai non indovinando, ma per profilazione . Tramite il profiling, si determina la posizione esatta del collo di bottiglia (dato che quando si indovina , si può quasi sempre sbagliare), e potrebbe ora concentrarsi su quella parte del codice.

  3. Una volta trovato il collo di bottiglia, cerca soluzioni. Lo fai indovinando, benchmark, profilazione, scrivendo alternative, comprendendo le ottimizzazioni del compilatore, comprendendo le ottimizzazioni che spetti a te, ponendo domande su Stack Overflow e passando a linguaggi di basso livello (incluso Assembler, quando necessario).

Qual è il problema attuale con il sottosistema Projects che richiede informazioni su Users sottosistema?

L'eventuale problema di scalabilità futura? Questo non è un problema. La scalabilità può diventare un incubo se si inizia a unire tutto in una soluzione monolitica o in una query per gli stessi dati da più posizioni (come spiegato di seguito, a causa della difficoltà di introdurre la cache).

Se c'è già un problema di prestazioni evidente, quindi, passaggio 2, cerca il collo di bottiglia.

Se sembra che, effettivamente, il collo di bottiglia esista e sia dovuto al fatto che Projects richiede agli utenti attraverso il sottosistema Users (e si trova a livello di query del database), solo allora dovresti cercare un'alternativa.

L'alternativa più comune sarebbe quella di implementare il caching, riducendo drasticamente il numero di query. Se ci si trova in una situazione in cui la memorizzazione nella cache non è di aiuto, l'analisi successiva potrebbe mostrarti che è necessario ridurre il numero di query, aggiungere (o rimuovere) indici di database o gettare altro hardware o riprogettare completamente l'intero sistema .

    
risposta data 25.01.2015 - 09:01
fonte

Leggi altre domande sui tag