Interfacce per le classi CRUD

1

In una soluzione interna a cui ho lavorato, non sono stato in grado di comprendere il vantaggio di come le interfacce sono spesso implementate nel progetto. Quale è il seguente:

  1. Vuoi fare operazioni CRUD su un database per un modello / tabella specifico? Crea un servizio di visualizzazione .
  2. Hai creato un servizio di visualizzazione? Crea un'interfaccia per questo .
  3. Visualizza il servizio gestisce la maggior parte della logica.
  4. Tale logica in realtà deve accedere al database? Crea un repository.
  5. Crea un repository? Crea un'interfaccia per questo .
  6. Il repository utilizza l'iniezione delle dipendenze per stabilire un oggetto di contesto del database sovrastante.

Ecco un esempio e poi condividerò ciò che mi hanno detto gli sviluppatori originali (e perché non capisco il ragionamento):

ILicenseViewService

public  interface ILicenseViewService
{
    Task AssignLicenseAsync( ... );
    Task CreateLicenseAsync( ... );
    Task DeleteLicenseAsync( ... );
    Task<License> GetLicenseAsync( ... );
    ...
}

LicenseViewService

public class LicenseViewService : ILicenseViewService
{
    private readonly ILicenseRepository licenseRepository_;

    public LicenseViewService(ILicenseRepository licenseRepository)
    {
        licenseRepository_ = licenseRepository;
    }

    public async Task AssignLicenseAsync( ... )
    {
       if (DateTime.UtcNow > license.ExpirationDate)
       {
          throw new Exception("License is expired.");
       }

       await licenseRepository_.AssignLicenseAsync( ... );
    }
}

ILicenseRepository

public interface ILicenseRepository
{
   Task AssignLicenseAsync( ... );
   Task RevokeLicenseAsync( ... );
   void DeleteLicense( ... );
}

LicenseRepository

public class LicenseRepository : ILicenseRepository
{
   private readonly ApplicationDbContext context_;

   public LicenseRepository(ApplicationDbContext context)
   {
      context_ = context;
   }

   public async Task AssignLicenseAsync( ... )
   {
      ...
      await context_.SaveChangesAsync();
   }
}

A quanto ho capito, interfacce fornisce ai programmatori numerosi vantaggi come il sollievo di non preoccuparsi delle classi derivate come Wolf e Monkey quando si inviano quegli oggetti in una routine che si aspetta IAnimal, quando voler imporre un contratto su come viene utilizzata una classe e quando si desidera utilizzare l'iniezione di dipendenza su oggetti unici.

Non ho visto quasi nulla di questo nell'uso di queste interfacce CRUD. Nessun oggetto ereditato, nessuna classe derivata e nessuna necessità di imporre un contratto. Uno sviluppatore ha spiegato che l'intenzione era l'iniezione di dipendenza, in cui ho chiesto perché qualcosa del tipo:

using (var myLicenseDbContext = new LicenseRepository()) { }

o

using (var myLicenseDbContext = new LicenseViewService()) { }

non lo farebbe. L'argomento era che un programmatore avrebbe voluto essere in grado di scambiare una classe di simulazione e non avere un vero accesso al database. Un altro motivo era per quanto riguarda l'imbroglio dei test, nel qual caso è necessaria l'iniezione di dipendenza. Non ci sono altre soluzioni per raggiungerlo? Seguire questa ideologia progettuale significa che ogni classe del modello ha due interfacce, un servizio di visualizzazione e un repository per CRUD. Non è necessario?

    
posta 8protons 22.05.2018 - 00:59
fonte

2 risposte

1

L'iniezione di dipendenza si basa su un concetto chiamato "Inversione del controllo". Invece di ogni classe che crea le sue dipendenze concrete (cioè utilizzando la parola chiave "nuova"), si passano invece queste dipendenze al costruttore della classe.

Per questo, hai bisogno di interfacce nei costruttori di classe.

public LicenseViewService(ILicenseRepository licenseRepository)
{
    licenseRepository_ = licenseRepository;
}

Questo ti dà due abilità. Il primo è la possibilità di cambiare l'implementazione della dipendenza passata alla classe. Questo ti permette di prendere in giro una dipendenza per i test delle unità, tra le altre cose.

ILicenseRepository mockRepository = new LicenseMockRepository();
var service = new LicenseViewService(mockRepository);

Il secondo è la possibilità di rinviare la costruzione di tipi a un contenitore IoC.

var licenseRepository = Container.Resolve<ILicenseRepository>();

Il contenitore mapperà l'interfaccia a una classe che implementa quell'interfaccia e restituirà un'istanza della classe. Ciò significa che è necessario fornire tali mapping in qualche modo al contenitore. Un modo è quello di registrare i tipi con la rispettiva interfaccia:

Container.Register<ILicenseRepository, LicenseRepository>();

Quindi perché dovresti fare tutto questo invece di usare solo la parola chiave new ?

Bene, una ragione è che il contenitore, data una particolare interfaccia, troverà la rispettiva classe e risolverà tutte le sue dipendenze in modo ricorsivo. Ovvero, risolverà le dipendenze per la classe richiesta e anche tutte le dipendenze delle sue dipendenze e così via. Pertanto, un'implementazione dell'interfaccia che potrebbe richiedere una dozzina di nuove parole chiave per risolvere tutte le dipendenze necessarie richiederebbe solo una chiamata di Container.Resolve .

I container consolidano anche la configurazione delle dipendenze in un'unica posizione per facilitare la manutenzione e fornire una gestione a vita per gli oggetti.

Quindi come funziona in pratica? Non ho personalmente messo in piedi un'architettura a livello aziendale, ma utilizzo un contenitore IoC nel mio progetto attuale, ed è un po 'bello sapere che è possibile utilizzare le interfacce e il contenitore IoC gestirà più o meno la creazione di i tipi necessari per te automaticamente e in modo trasparente.

    
risposta data 22.05.2018 - 18:27
fonte
0

L'uso delle interfacce si presta enormemente ai principi di progettazione SOLID. Consiglierei qualche ricerca su questo, e capisco perché le interfacce sarebbero utili, per esempio quando;

  • Progettare un'API intuitiva (come CRUD)
  • Verifica futura della tua app da modificare (puoi modificare rapidamente le implementazioni senza modificare altro codice)
  • Mantenere le interfacce semplici mantiene le classi semplici (e anche le classi che usano quelle classi sono semplici!)
  • La capacità di MOCK dell'interfaccia nei test di unità (TDD è il biglietto d'oro per lo sviluppo del software)

Per tornare alla tua domanda,

I have seen almost none of that in the use of these CRUD interfaces

  • Il tuo LicenseViewService può essere testato unitamente a causa dell'astrazione del repository.
  • Puoi cambiare il modo in cui il tuo servizio riceve i dati in futuro.
  • Quando crei un UserViewService, che può utilizzare un ILicenseViewService, sarai in grado di [unit test / develop / fix bugs] separatamente dal codice, perché puoi prendere in giro LicenseViewService.
  • Non hai dovuto scrivere i tuoi metodi Crea, Leggi, Aggiorna ed Elimina perché provengono da un'interfaccia IRepo e impl ... (in caso contrario, non lo fanno correttamente).

Potrei fornire collegamenti a varie risorse per i principi SOLID e perché le interfacce svolgono un ruolo così importante in tutto questo, ma tbh, esiste anche google e consiglierei un'intera serata di contenuti SOLID basati su youtube alla velocità del 150%. .

Infine, vorrei dire che NON farlo in questo modo, NON avendo interfacce e NON permettendo test delle unità e cambiamenti futuri, renderà la tua vita infernale in circa 3 mesi, quindi vale la pena capire perché è fatta in questo modo !

    
risposta data 22.05.2018 - 20:11
fonte

Leggi altre domande sui tag