Giugno 2019

Volume 34 Numero 6

Il presente articolo è stato tradotto automaticamente.

[Patterns and Practices]

Sviluppo super DRY per ASP.NET Core

Dal Thomas Hansen

DRY è uno degli acronimi di architettura software molto importante. Indica che "Non ' t Repeat Yourself" e indica un principio fondamentale per tutti coloro che sono state mantenute un progetto di codice sorgente legacy. Vale a dire, se si ripete manualmente nel codice, si noterà che ogni correzione di bug e aggiornamenti delle funzionalità verranno hanno è ripetuto le modifiche.

Ripetizione del codice consente di ridurre la gestibilità del progetto e rende più difficile applicare le modifiche. E la ripetizione di più, il codice è disponibile più complicati otterrà. Se, d'altra parte, si evita di ripetizioni, è possibile finire con un progetto che è molto più facile da gestire e correzione di bug e sono uno sviluppatore di software di produttività e soddisfazione, eseguire l'avvio. In breve, il commit di codice DRY consente di creare codice eccellente.

Dopo aver iniziato a pensare in modo asciutto, è possibile specificare questo principio architettura importante per un nuovo livello, in cui mi sembra come se il progetto è magicamente solleva fin dall'inizio, ovvero letteralmente senza la necessità di applicare qualsiasi tentativo di creare funzionalità. Per XML, potrebbe sembrare come se codice viene visualizzata all'esterno dell'aria thin tramite i meccanismi di "prova con privilegi avanzati". Codice eccellente è quasi sempre leggera, ma è anche tinier codice brillante.

In questo articolo verrà presentata anche per le funzionalità avanzate di sviluppo Super-DRY e alcuni trucchi di che ho utilizzato nel corso degli anni che consentono creare le API Web di ASP.NET Core con molto meno sforzo. Tutti gli elementi in questo articolo si basa su soluzioni generalizzate e il concetto di codice sorgente e utilizza solo le procedure consigliate dal settore. Ma prima di tutto, alcuni in background teoria.

CRUD, HTTP REST e SQL

Crea, lettura, aggiornamento ed eliminazione (CRUD) è il comportamento di base della maggior parte dei modelli di dati. Nella maggior parte dei casi, i tipi di entità di dati necessario questi quattro operazioni e in realtà sia HTTP sia SQL vengono compilati senza dubbio attorno a esse. Richiesta HTTP POST è per la creazione di elementi, HTTP GET è per la lettura degli elementi, HTTP PUT consente di aggiornare gli elementi e DELETE HTTP è per l'eliminazione degli elementi. SQL si evolve allo stesso modo attorno CRUD con insert, select, update e delete. Una volta che si fare molta attenzione, è evidente che è sostanzialmente tutto sulle operazioni CRUD, presupponendo che non si vuole passare "in" e implementare un'architettura CQRS.

È quindi necessario i meccanismi di linguaggio necessari per descrivere i verbi HTTP in modo che essi vengono propagate completamente dal livello HTTP del client, tramite il C# codice e nel database relazionale. A questo punto, tutto quello che serve è un modo generico per implementare queste idee attraverso i livelli. E si vuole eseguire questa operazione senza ripetere manualmente, con una base dell'architettura ad alte prestazioni. Bene, cominciamo.

Prima di tutto, scaricare il codice nella github.com/polterguy/magic/releases. Decomprimere il file e aprire magic.sln in Visual Studio. Avviare il debugger e osservare come hai già cinque endpoint HTTP REST nell'interfaccia utente Swagger. In cui è stato originato questi endpoint HTTP? Anche se esaminiamo il codice, poiché potrebbe sorprendere la risposta alla domanda.

Cerca Mom, nessun codice!

La prima cosa, si noterà che quando si inizia il codice di esplorazione è che il progetto Web ASP.NET Core stesso è letteralmente vuoto. Ciò è possibile a causa di una funzionalità di ASP.NET Core che è possibile includere in modo dinamico i controller. Se si desidera visualizzare gli elementi interni protetti da questo, è possibile estrarre il file Startup.cs. In sostanza, viene aggiunto in modo dinamico ogni controller da tutti gli assembly nella cartella in AppDomain. Questo concetto semplice consente di riutilizzare i controller e a pensare in modo modulare, se si creano le soluzioni. La possibilità di riusare il controller di più progetti è passaggio1 possibile per diventare un Super-DRY professionisti del settore.

Aprire il progetto web/controller/magic.todo.web.controller ed esaminare il file TodoController.cs. Si noterà che è vuota. Pertanto, queste cinque endpoint HTTP REST da dove provengono? La risposta è tramite il meccanismo della programmazione orientata agli oggetti (OOP) e C# generics. La classe TodoController eredita da CrudController, passando il modello di visualizzazione e il relativo modello di database. Inoltre, utilizza inserimento delle dipendenze per creare un'istanza di ITodoService, il quale trasmette alla classe di base CrudController.

Poiché l'interfaccia ITodoService eredita da ICrudService con la classe generica corretta, la classe di base CrudController Fortunatamente accetta l'istanza del servizio. Inoltre, a questo punto già possibile usare il servizio polymorphistically, come se fosse un ICrudService semplice, che naturalmente è un'interfaccia generica con i tipi con parametri. Ciò fornisce accesso a cinque metodi di servizio definito in modo generico nel CrudController. Per comprendere le implicazioni di questo oggetto, tenere presente che con il semplice codice seguente è stata creata letteralmente tutte le operazioni CRUD che più completo ed è stata propagata li dal livello HTTP REST, attraverso il livello di servizio, nella gerarchia di classe di dominio, che terminano nel livello di database relazionale. Ecco tutto il codice per l'endpoint di controller:

[Route("api/todo")]
public class TodoController : CrudController<www.Todo, db.Todo>
{
  public TodoController(ITodoService service)
    : base(service)
  { }
}

Questo codice ti offre cinque endpoint HTTP REST, consentendo di creare, leggere, aggiornare, eliminare e contare quasi magicamente elementi del database. E l'intero codice è stato "dichiarato" e non contiene una singola riga di funzionalità. Ora ovviamente è vero che non genera codice stesso e gran parte del lavoro viene svolto dietro le quinte, ma qui il codice è diventato "Prova con privilegi avanzati". È un vero vantaggio all'utilizzo di un livello superiore di astrazione. Una valida analogia sarebbe la relazione tra un C# istruzione if-then e il codice in linguaggio assembly sottostante. L'approccio che ho delineato è semplicemente un livello superiore di astrazione più impostare come hardcoded il codice del controller.

In questo caso, il tipo www.Code è il modello di visualizzazione, il database. Tipo di attività è il modello di database e ITodoService è l'implementazione del servizio. Da semplicemente dell'hint per la classe di base quali tipo che si desidera salvare in modo permanente, è stata completata senza dubbio il processo. Il livello di servizio anche in questo caso è ugualmente vuoto. Tutto il relativo codice sono disponibili qui:

public class TodoService : CrudService<Todo>, ITodoService
{
  public TodoService([Named("default")] ISession session)
    : base(session, LogManager.GetLogger(typeof(TodoService)))
  { }
}

Zero metodi, proprietà, zero campi e ancora è ancora presente un livello di servizio completo per gli elementi TODO. In effetti, anche l'interfaccia del servizio è vuoto. Di seguito viene illustrato tutto il codice per l'interfaccia del servizio:

public interface ITodoService : ICrudService<Todo>
{ }

Anche in questo caso è vuoto. Senza perdere comunque, salabim sim, e magicamente e si dispone di un'applicazione API Web di attività HTTP REST completa. Se si apre il modello di database, si noterà quanto segue:

public class Todo : Model
{
  public virtual string Header { get; set; }
  public virtual string Description { get; set; }
  public virtual bool Done { get; set; }
}

Ancora una volta, non c'è niente qui, ovvero un paio di proprietà virtuali e una classe di base. E ancora, si è in grado di rendere persistente il tipo in un database. Nella classe TodoMap.cs all'interno del progetto magic.todo.model si verifica il mapping tra il database e il tipo di dominio effettivo. In questo caso, è possibile visualizzare l'intera classe:

public class TodoMap : ClassMap<Todo>
{
  public TodoMap()
  {
    Table("todos");
    Id(x => x.Id);
    Map(x => x.Header).Not.Nullable().Length(256);
    Map(x => x.Description).Not.Nullable().Length(4096);
    Map(x => x.Done).Not.Nullable();
  }
}

Questo codice indica la libreria ORM per usare la tabella di elementi TODO, con la proprietà Id come chiave primaria e imposta un paio di proprietà aggiuntive per il resto delle colonne/proprietà. Si noti che quando è stato avviato il progetto, non ancora è un database. Questo avviene perché NHibernate crea automaticamente tabelle del database se non sono già presenti. E poiché Magic per impostazione predefinita Usa SQLite, non è necessario anche una stringa di connessione. Automaticamente verrà creato un database di SQLite basate su file in un percorso relativo del file, a meno che non si esegue l'override delle relative impostazioni di connessione in appsettings.config per usare MySQL o MSSQL.

Che ci si creda o No, la soluzione supporta già in modo trasparente quasi tutti i database relazionali che è facile immaginare. In effetti, l'unica riga di codice effettivo che è necessario aggiungere per rendere questa funzionalità di cosa è reperibile nel progetto magic.todo.services, all'interno della classe ConfigureNinject, che esegue semplicemente l'associazione tra l'interfaccia del servizio e l'implementazione del servizio. Pertanto, in effetti aggiunti una riga di codice e ha ricevuto un'intera applicazione come risultato. Di seguito è l'unica riga dell'effettivo "code" considerata di creare l'applicazione TODO:

public class ConfigureNinject : IConfigureNinject
{
  public void Configure(IKernel kernel, Configuration configuration)
  {
    // Warning, this is a line of C# code!
    kernel.Bind<ITodoService>().To<TodoService>();
  }
}

È stato diventano i prestigiatori Super-DRY, tramite l'uso intelligente di OOP, generics e i principi di DRY. Pertanto, la domanda nasce spontanea: Come è possibile utilizzare questo approccio per migliorare il codice?

Ovviamente Iniziare con il modello di database e creare la propria classe di modello, che può essere eseguita mediante l'aggiunta di un nuovo progetto all'interno della cartella di modello o tramite l'aggiunta di una nuova classe al progetto magic.todo.model esistente. Creare quindi l'interfaccia del servizio nella cartella contratti. A questo punto implementare il servizio nella cartella di servizi e creare il modello di visualizzazione e controller. Assicurarsi che si esegue l'associazione tra l'interfaccia del servizio e l'implementazione del servizio. Quindi se si sceglie di creare nuovi progetti, è necessario verificare che ASP.NET Core carica l'assembly aggiungendo un riferimento a esso nel progetto magic.backend. Si noti che solo il progetto di servizio e il controller è necessario fare riferimento al back-end.

Se si sceglie di usare i progetti esistenti, l'ultima parte non è ancora necessario. Una semplice riga di codice effettivo per l'associazione tra l'implementazione del servizio e l'interfaccia del servizio ed è stata creata un'intera soluzione API Web ASP.NET Core. Che è una riga sfidata di codice se mi si chiede. È possibile visualizzare me passano attraverso l'intero processo per una versione precedente del codice nel mio "Super DRY Magic per ASP.NET Core" video youtu.be/M3uKdPAvS1I.

Si supponga quindi che cosa accade quando si rende conto che è possibile eseguire lo scaffolding di questo codice e generare automaticamente in base allo schema del database. A questo punto, il sistema software di scaffolding computer probabilmente sta eseguendo la codifica, producendo un'architettura di domain-Driven Design (DDD) perfettamente valido nel processo.

Nessun codice, non sono presenti errori, nessun problema

La maggior parte dei framework di scaffolding applicare i tasti di scelta rapida o non consentono di estendere e modificare il codice risulta, in modo che vengono usate per applicazioni reali diventa impossibile. Magic, questo difetto non è semplicemente true. Crea un livello di servizio per l'utente e Usa inserimento delle dipendenze per inserire un'interfaccia del servizio nel controller. Lo strumento produce anche gli schemi DDD perfettamente validi per l'utente. E come è stato creato il codice iniziale, ogni singola parte della soluzione può essere esteso e modificato nel modo desiderato. Il progetto è perfettamente convalida a ogni lettera singola in tinta unita.

Ad esempio, in uno dei miei soluzioni, ho un thread di fetcher POP3 server nel servizio, dichiarato per un tipo di modello di dominio EmailAccount. Questo servizio POP3 memorizza i messaggi di posta elettronica in un database dal server POP3, in esecuzione su un thread in background. Quando viene eliminato un messaggio di posta elettronica, desidera assicurarsi che si elimino anche fisicamente relativi allegati in storage, e se l'utente elimina un EmailAccount, è ovviamente necessario eliminare i relativi messaggi di posta elettronica associati.

Il codice nel figura 1 indica come ho sostituito l'eliminazione di un EmailAccount, che è anche necessario eliminare tutti i messaggi di posta elettronica e allegati. Per il record, Usa ibernazione Query Language (HQL) per comunicare con il database. Ciò garantisce che NHibernate creerà automaticamente la corretta sintassi SQL, in base al quale database è fisicamente connesso.

Figura 1 viene sottoposto a override dell'eliminazione di un EmailAccount

public sealed class EmailAccountService : CrudService<EmailAccount>,
  IEmailAccountService
{
  public EmailAccountService(ISession session)
    : base(session, LogManager.GetLogger(typeof(EmailAccountService)))
  { }
  public override void Delete(Guid id)
  {
    var attachments = Session.CreateQuery(
      "select Path from EmailAttachment where Email.EmailAccount.Id = :id");
    attachments.SetParameter("id", id);
    foreach (var idx in attachments.Enumerable<string>())
    {
      if (File.Exists(idx))
        File.Delete(idx);
    }
    var deleteEmails = Session.CreateQuery(
      "delete from Email where EmailAccount.Id = :id");
    deleteEmails.SetParameter("id", id);
    deleteEmails.ExecuteUpdate();
    base.Delete(id);
  }
}

Eseguire i calcoli matematici

Dopo aver iniziato philosophizing queste idee, trovare l'ispirazione colpisce. Si supponga ad esempio un framework di scaffolding basato Magic. Da un punto di vista matematico, se si dispone di un database con 100 tabelle, ognuna con una media di 10 colonne, sono disponibili che è possibile aggiungere il costo in termini di totale righe di codice rapido aumento. Ad esempio, per eseguire il wrapping di tutte queste tabelle in un'API REST HTTP richiede sette righe di codice per ogni interfaccia del servizio, mentre 14 righe di codice sono necessari per ogni tabella per ogni servizio e 19 righe sono necessari per ogni tabella per ogni controller. Figura 2 disposta gli elementi coinvolti e le righe di codice necessari.

Figura 2 sommando il costo nel codice

Componente Contratti Media righe di codice Totale righe di codice
Interfacce del servizio 100 7 700
Servizi 100 14 1,400
Controller 100 19 1,900
Implementazione e l'interfaccia di servizio 100 1 100
Visualizzazione di modelli 100 17 1,700
Modelli di database 100 17 1,700
Mapping del database 100 20 2,000
Totale righe di codice: 9,500

 

Dopo che tutti alla fine, si osservano 9.500 righe di codice. Se si compila un servizio di metadati in grado di estrarre uno schema di database esistente, è quindi abbastanza evidente che è possibile generare il codice con scaffolding, ovvero facilmente senza dubbio evitando qualsiasi codifica affatto, comunque ancora producer 9.500 righe di codice perfettamente strutturata, esteso, con tutti i modelli di progettazione rilevanti e le procedure consigliate. Con solo due secondi di scaffolding, il computer ha fatto l'80% del lavoro.

È necessario eseguire questo punto è analizzare i risultati del processo di scaffolding ed eseguire l'override di metodi per i servizi e i controller per i tipi di dominio che richiedono particolare attenzione per qualsiasi motivo. È terminata con l'API Web. Poiché gli endpoint di controller tutti hanno la stessa struttura esatta, la duplicazione di questo processo di scaffolding nel livello client è semplice come leggere i file di dichiarazione di API JSON generati da Swagger. In questo modo è possibile creare il livello di servizio per un elemento, ad esempio Angular o React. E tutto questo perché il codice e l'API Web hanno strutture prevedibile, basati sui principi di generalizzazione e la prevenzione di ripetizione.

Per inserire il codice seguente nel punto di vista, si è riusciti a creare un progetto API Web REST HTTP che è probabilmente due volte le stesse dimensioni del progetto open source Sugar CRM in termini di complessità ed è stato fatto l'80% del lavoro in pochi secondi. È stata fornita una catena di montaggio factory software basato sulla standardizzazione dei componenti e il riutilizzo della struttura, rendendo molto più semplice da leggere e gestire il codice per tutti i progetti. Anche le parti che richiedono la modifica e un comportamento speciale possono essere riutilizzate in tuo prossimo progetto, grazie a endpoint controller modo e servizi in modo dinamico vengono caricati in API Web, senza alcuna dipendenza.

Se si lavora per una società di consulenza, probabilmente avviare nuovi progetti diversi ogni anno con tipi simili di requisiti, in cui è necessario risolvere i punti in comune per ogni nuovo progetto. Con un'analisi dei requisiti del client e un'implementazione iniziale, un approccio estremamente DRY consente di terminare letteralmente un intero progetto in pochi secondi. E naturalmente, la composizione di elementi nei progetti può essere ulteriormente riutilizzata, identificando i moduli comuni, ad esempio l'autenticazione e autorizzazione. Implementando questi moduli in un progetto API Web comune, è possibile applicarli a ogni nuovo progetto che pone problemi simili a quelle che si è visto prima.

Per il record, rendo il suono semplice, ma il fatto è che è difficile evitare la ripetizione. Richiede la disponibilità ad eseguire il refactoring, refactoring, eseguire il refactoring. E dopo aver completato il refactoring, è necessario effettuare il refactoring di un po' più. Ma il vantaggio è troppo bello per ignorare. Principi di DRY possano consentono di creare quasi magicamente codice, semplicemente salire con Bacchetta magica di scaffolding e comporre i moduli da parti preesistenti.

Alla fine del giorno, i principi articolati in questo caso, possono trarre esistenti e le procedure consigliate per creare API Web personalizzate e si evitano la ripetizione. C'è molto buone che possono provenire da questo approccio, e si spera consente di valutare l'ottimizzazione di prova.


Thomas Hansenè una procedura guidata del software Zen attualmente che vivono in Cipro, in cui si cerca di distribuire codice del software in modo tramite Tecnofinanza e sistemi commerciali.

Grazie al seguente esperto tecnico Microsoft per la revisione dell'articolo: James McCaffrey


Discutere di questo articolo nel forum di MSDN Magazine