Semplici esempi di applicazioni Asp.Net correttamente progettate che illustrano diversi livelli di servizio?

2

Ho iniziato a programmare professionalmente per anni in diverse aziende e mi considero un programmatore abbastanza competente. Tuttavia, ovunque io abbia lavorato, ci sono dozzine di ingegneri e programmatori di software diversi con dozzine di stili e schemi di codifica diversi. Ho letto tonnellate di letteratura sull'argomento di applicazioni ben progettate, ma onestamente non penso di averne mai visto o pienamente realizzato uno io stesso. Sono curioso di sapere se ci sono delle vere mani su esempi di diversi principi di progettazione, specialmente le applicazioni che implementano un'architettura di servizio adeguata per dare un'idea di tutto.

Ad esempio, la nostra attuale applicazione è iniziata con molte idee di design contrastanti, ma la maggior parte degli ingegneri originali è stata lasciata andare. Ora, ho praticamente un regno libero con un'intera schiera di sviluppatori molto alle prime armi in cui aiutare a formare ed educare mentre iniziano la loro carriera. Sto cercando di dare un esempio positivo implementando progetti facili da seguire, ma comunque robusti.

Dato che la nostra applicazione si trova oggi, abbiamo in qualche modo un'area legacy e la nuova area. L'area legacy è composta da:

  • Un livello di repository (creato con ADO.Net), ma è abbastanza stretto e quasi impossibile da testare o scrivere test per.
  • Un livello dominio - si basa sui repository, ma mescola la logica tra molte classi e aree diverse
  • Un progetto di modello - riflette vagamente i modelli di database
  • Un progetto viewmodel
  • Un livello logico Web: uno sviluppatore ha passato (in qualche modo) una logica distratta di tutto il web / business e lo ha inserito nel proprio progetto.
  • Il livello web - contiene controller, viste e apis web.

Le nuove cose sono simili, ma con un approccio diverso

  • Livello entità - Questo progetto contiene solo le entità e i contesti delle entità entità. Ha anche un metodo factory che restituisce il contesto appropriato e ottiene le informazioni sulla stringa di connessione dai nostri file di configurazione per ogni edmx.
  • Repository EF - Ogni repository gestisce un sottoinsieme minore di funzionalità, praticamente solo un tipo di entità viene interagito con questo livello a meno che non siano necessari determinati join.
  • Livello dominio - Questo livello crea unità di lavoro per ogni funzionalità successiva. Non ha molte / eventuali funzionalità di crossover ed è molto specializzato.
  • Progetto test unità - Il progetto test unità crea repository simulati e controlla tutta la logica implementata nel livello dominio.
  • Livello Web - responsabile del cablaggio dell'iniezione delle dipendenze e della pubblicazione di viste, controller e metodi API.

Nel nuovo materiale, ogni singolo repository implementa un'interfaccia (quasi un errore) e tutto funziona tramite l'integrazione delle dipendenze. Lo trovo piuttosto semplice, ma temo di fare troppo o troppo poco.

Ad esempio, diciamo che ho un contesto chiamato MainContext . Nel mio livello di repository, ho un repository chiamato GenericRepository . GenericRepository implementa un'interfaccia chiamata IGenericRepository che espone metodi come Get, Save, ecc. Questi metodi sono gli stessi per la maggior parte degli archivi, ma possono essere sovrascritti se necessario. Potrebbe sembrare qualcosa di simile:

public class GenericRepository<TEntity> : GenericRepository<TEntity> where TEntity : class
{
    internal SchoolContext context;
    internal DbSet<TEntity> dbSet;

    public GenericRepository(SchoolContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }

    public virtual void Insert(TEntity entity)
    {
        dbSet.Add(entity);
    }

    public virtual void Update(TEntity entityToUpdate)
    {
        dbSet.Attach(entityToUpdate);
        context.Entry(entityToUpdate).State = EntityState.Modified;
    }
    // -- more methods
}

Tutti gli altri repository ereditano dal GenericRepository , ma tutti implementano anche la propria interfaccia che implementa anche IGenericRepository .

A questo punto ci sono 2 o 3 interfacce profonde, e sto iniziando a sentire che è un po 'eccessivo.

Sto esagerando con il design? Ci sono esempi validi e completi che illustrino il percorso migliore con un design simile?

    
posta JD Davis 02.02.2018 - 02:30
fonte

2 risposte

2

L'architettura generale che descrivi sembra molto simile a quella che ho visto per applicazioni di medie / grandi dimensioni in un ambiente aziendale con un livello medio / alto di complessità. Ci vuole esperienza e pratica per vedere dove ha più senso aggiungere un altro strato di astrazione i.e. inizi a vedere dove ha senso dopo essere stato bruciato abbastanza volte.

Ecco alcuni principi guida che uso per decidere se qualcosa è troppo o poco ingegnerizzato.

Incoraggia i test automatici? Personalmente, sono un grande sostenitore dei test automatici (unità e integrazione) per garantire che chiunque controlli il codice dopo di me possa eseguire i test ed essere ragionevolmente sicuro che ( 1) il codice funziona e (2) comprende il comportamento previsto e le condizioni di guasto. Questo può essere fatto solo quando il codice base è suddiviso in modo da facilitare i test significativi . Il codice scomposto troppo non lascia nulla di sostanza da testare.

Semplifica lo scambio di componenti? Può aiutare a identificare i diversi strumenti che l'applicazione dipende da quelli che potrebbero essere ragionevolmente modificati in futuro. Ciò renderà i cambiamenti futuri meno meno dolorosi. I wrapper possono essere particolarmente utili qui. Esempi:

Quando un software diventa così fragile da non poter più essere aggiornato / modificato / migliorato, è solo questione di tempo prima che sia obsoleto.

L'astrazione aggiunge valore? Va bene che i livelli di interfacce 2 o 3 siano profondi finché c'è uno scopo per questo. Pensa ai test automatici che hai. Il loro scopo è quello di rendere testabile il codice (unità) che fornisce valore documentando, stabilendo il comportamento previsto e forzando la codifica organizzata. I test a scopo di test aggiungono solo ulteriore confusione. Allo stesso modo, il livello delle astrazioni è utile se ti consente di lavorare in modo più pulito, supportare il test e disaccoppiare il codice dove è ragionevole cambiarlo (vedi cuciture ).

Puoi utilizzare la " regola del 3 " per determinare quando qualcosa può essere refactored o reso riutilizzabile.

Utilizza nomi significativi - quali nomi usi possono essere quasi altrettanto importanti dei metodi / astrazioni stesse. Ad esempio, le interfacce e l'implementazione non hanno bisogno di essere nominate allo stesso modo. Ad esempio, potresti avere un IDatabaseRepository implementato da OracleRepository e SqlRepository . I nomi dicono qualcosa sul perché l'astrazione esiste e su come le implementazioni sono diverse. IGenericShape e GenericShape non ti dicono tanto quanto IShape e Triangle do.

    
risposta data 31.07.2018 - 21:49
fonte
0

Devo dire che il pattern del repository può essere fonte di confusione. L'ho usato in due applicazioni in modo diverso. Uno è il 95% di dati letti mentre l'altro è CRUD.

Sto usando il tuo esempio nell'applicazione CRUD, dove sto usando l'IOC per iniettare le interfacce nei servizi e poi in UnitTests che scherno il repository. Funziona abbastanza bene. Ma direi che è abbastanza abbinato a EF in quanto devo istanziare il contesto quando si usa il repository.

In un'altra applicazione non utilizzo il pattern di repository nello stesso modo in cui utilizzo Dapper per ottenere velocità e non ho bisogno di CRUD. In questo caso ci sono centinaia di repository e fondamentalmente si sta utilizzando il CQRS per ottenere i dati. Questo progetto consiste principalmente di test di integrazione poiché i dati effettivamente recuperati utilizzano query complesse e dobbiamo garantire la correttezza.

Il mio punto è che separare il codice in un repository che ha la responsabilità di connettersi ai dati è una buona separazione per molte ragioni, non importa quale sotto-modello si utilizza.

    
risposta data 31.07.2018 - 23:48
fonte