Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Suggerimento
Questo contenuto è un estratto dell'eBook, Architect Modern Web Applications with ASP.NET Core and Azure, disponibile in .NET Docs o come PDF scaricabile gratuito che può essere letto offline.
"Se pensi che una buona architettura sia costosa, prova una cattiva architettura." - Brian Foote e Joseph Yoder
La maggior parte delle applicazioni .NET tradizionali viene distribuita come unità singole corrispondenti a un eseguibile o a una singola applicazione Web in esecuzione all'interno di un singolo dominio app IIS. Questo approccio è il modello di distribuzione più semplice e si adatta molto bene a molte applicazioni interne e pubbliche di dimensioni più ridotte. Tuttavia, anche considerando questa singola unità di distribuzione, la maggior parte delle applicazioni aziendali non semplici trae vantaggio da una separazione logica in diversi livelli.
Che cos'è un'applicazione monolitica?
Un'applicazione monolitica è un'applicazione completamente autonoma, in termini di comportamento. Può interagire con altri servizi o archivi dati durante l'esecuzione delle operazioni, ma il nucleo del comportamento viene eseguito all'interno del proprio processo e l'intera applicazione viene in genere distribuita come singola unità. Se un'applicazione di questo tipo deve essere ridimensionata orizzontalmente, in genere l'intera applicazione viene duplicata in più server o macchine virtuali.
Applicazioni all-in-one
Il minor numero possibile di progetti per un'architettura dell'applicazione è uno. In questa architettura, l'intera logica dell'applicazione è contenuta in un singolo progetto, compilata in un singolo assembly e distribuita come singola unità.
Un nuovo progetto ASP.NET Core, che sia creato in Visual Studio o dalla riga di comando, inizia come un semplice "all-in-one" monolite. Contiene tutto il comportamento dell'applicazione, tra cui la presentazione, l'azienda e la logica di accesso ai dati. La figura 5-1 mostra la struttura di file di un'app a progetto singolo.
Figura 5-1. Un'app ASP.NET Core per progetto singolo.
In un singolo scenario di progetto, la separazione delle problematiche viene ottenuta tramite l'uso di cartelle. Il modello predefinito include cartelle separate per le responsabilità del modello MVC di Modelli, Visualizzazioni e Controller, nonché cartelle aggiuntive per i dati e i servizi. In questa disposizione, i dettagli della presentazione devono essere limitati il più possibile alla cartella Views e i dettagli di implementazione dell'accesso ai dati devono essere limitati alle classi mantenute nella cartella Dati. La logica di business deve risiedere in servizi e classi all'interno della cartella Models.
Anche se semplice, la soluzione monolitica a progetto singolo presenta alcuni svantaggi. Man mano che le dimensioni e la complessità del progetto aumentano, anche il numero di file e cartelle continuerà a crescere. Le problematiche dell'interfaccia utente (modelli, visualizzazioni, controller) si trovano in più cartelle, che non sono raggruppate alfabeticamente. Questo problema peggiora solo quando vengono aggiunti costrutti aggiuntivi a livello di interfaccia utente, ad esempio Filtri o ModelBinder, nelle proprie cartelle. La logica di business è sparsa tra le cartelle Models e Services e non esiste un'indicazione chiara delle classi in cui le cartelle devono dipendere da quali altre. Questa mancanza di organizzazione a livello di progetto porta spesso a codice spaghetti.
Per risolvere questi problemi, le applicazioni si evolvono spesso in soluzioni multiprogetto, in cui ogni progetto viene considerato risiedere in un particolare livello dell'applicazione.
Che cosa sono i livelli?
Man mano che le applicazioni aumentano di complessità, un modo per gestire tale complessità consiste nel suddividere l'applicazione in base alle proprie responsabilità o preoccupazioni. Questo approccio segue il principio di separazione dei problemi e può aiutare a mantenere organizzata una codebase in continua crescita in modo che gli sviluppatori possano trovare facilmente dove viene implementata una determinata funzionalità. Tuttavia, l'architettura a più livelli offre numerosi vantaggi oltre all'organizzazione del codice.
Organizzando il codice in livelli, le funzionalità di basso livello comuni possono essere riutilizzate in tutta l'applicazione. Questo riutilizzo è utile perché significa che è necessario scrivere meno codice e perché può consentire all'applicazione di standardizzare su una singola implementazione, seguendo il principio non ripetersi (DRY).
Con un'architettura a più livelli, le applicazioni possono applicare restrizioni su cui i livelli possono comunicare con altri livelli. Questa architettura consente di ottenere l'incapsulamento. Quando un livello viene modificato o sostituito, devono essere interessati solo i livelli che funzionano con esso. Limitando i livelli da cui dipendono gli altri livelli, l'impatto delle modifiche può essere mitigato in modo che una singola modifica non influisca sull'intera applicazione.
I livelli (e l'incapsulamento) semplificano notevolmente la sostituzione delle funzionalità all'interno dell'applicazione. Ad esempio, un'applicazione potrebbe usare inizialmente il proprio database SQL Server per la persistenza, ma in un secondo momento potrebbe scegliere di usare una strategia di persistenza basata sul cloud o una dietro un'API Web. Se l'applicazione ha incapsulato correttamente l'implementazione di persistenza all'interno di un livello logico, tale livello specifico di SQL Server potrebbe essere sostituito da un nuovo che implementa la stessa interfaccia pubblica.
Oltre al potenziale di sostituzione delle implementazioni in risposta alle future modifiche apportate ai requisiti, i livelli dell'applicazione possono anche semplificare lo scambio di implementazioni a scopo di test. Anziché dover scrivere test che operano sul livello dati reale o sul livello dell'interfaccia utente dell'applicazione, questi livelli possono essere sostituiti in fase di test con implementazioni fittizie che forniscono risposte note alle richieste. Questo approccio rende in genere i test molto più facili da scrivere e molto più veloci da eseguire rispetto all'esecuzione di test sull'infrastruttura reale dell'applicazione.
Il layering logico è una tecnica comune per migliorare l'organizzazione del codice nelle applicazioni software aziendali e esistono diversi modi in cui il codice può essere organizzato in livelli.
Annotazioni
I livelli rappresentano la separazione logica all'interno dell'applicazione. Nel caso in cui la logica dell'applicazione venga distribuita fisicamente in server o processi separati, queste destinazioni di distribuzione fisica separate vengono definite livelli. È possibile, e piuttosto comune, avere un'applicazione a più livelli distribuita in un singolo livello.
Applicazioni di architettura "N-Layer" tradizionali
L'organizzazione più comune della logica dell'applicazione nei livelli è illustrata nella figura 5-2.
Figura 5-2. Livelli di applicazione tipici.
Questi livelli vengono spesso abbreviati come interfaccia utente, BLL (livello di logica di business) e DAL (livello di accesso ai dati). Usando questa architettura, gli utenti effettuano richieste tramite il livello dell'interfaccia utente, che interagisce solo con il BLL. Il BLL, a sua volta, può chiamare il DAL per le richieste di accesso ai dati. Il livello dell'interfaccia utente non deve effettuare richieste direttamente al DAL, né deve interagire direttamente con la persistenza tramite altri mezzi. Analogamente, il BLL deve interagire solo con la persistenza tramite il DAL. In questo modo, ogni livello ha la propria responsabilità nota.
Uno svantaggio di questo approccio tradizionale a livelli è che le dipendenze in fase di compilazione vengono eseguite dall'alto verso il basso. Ovvero, il livello dell'interfaccia utente dipende dal BLL, che dipende dal DAL. Ciò significa che il BLL, che in genere contiene la logica più importante nell'applicazione, dipende dai dettagli di implementazione dell'accesso ai dati (e spesso dall'esistenza di un database). Il test della logica di business in un'architettura di questo tipo è spesso difficile, richiedendo un database di test. Il principio di inversione delle dipendenze può essere usato per risolvere questo problema, come si vedrà nella sezione successiva.
La figura 5-3 mostra una soluzione di esempio, suddividendo l'applicazione in tre progetti in base alla responsabilità (o al livello).
Figura 5-3. Una semplice applicazione monolitica con tre progetti.
Anche se questa applicazione usa diversi progetti a scopo organizzativo, viene comunque distribuita come singola unità e i relativi client interagiranno con esso come singola app Web. Ciò consente un processo di distribuzione molto semplice. La figura 5-4 mostra come un'app di questo tipo potrebbe essere ospitata con Azure.
Figura 5-4. Distribuzione semplice dell'app Web di Azure
Man mano che le esigenze dell'applicazione aumentano, potrebbero essere necessarie soluzioni di distribuzione più complesse e affidabili. La figura 5-5 mostra un esempio di un piano di distribuzione più complesso che supporta funzionalità aggiuntive.
Figura 5-5. Distribuzione di un'app Web in un servizio app di Azure
Internamente, l'organizzazione di questo progetto in più progetti in base alla responsabilità migliora la gestibilità dell'applicazione.
Questa unità può essere ridimensionata o ridotta per sfruttare i vantaggi della scalabilità su richiesta basata sul cloud. L'aumento delle prestazioni implica l'aggiunta di CPU, memoria, spazio su disco o altre risorse ai server che ospitano l'app. L'aumento del numero di istanze implica l'aggiunta di istanze aggiuntive di tali server, indipendentemente dal fatto che si tratti di server fisici, macchine virtuali o contenitori. Quando l'app è ospitata in più istanze, viene usato un servizio di bilanciamento del carico per assegnare richieste a singole istanze dell'app.
L'approccio più semplice per ridimensionare un'applicazione Web in Azure consiste nel configurare il ridimensionamento manualmente nel piano di servizio app dell'applicazione. La figura 5-6 mostra la schermata appropriata del dashboard di Azure per configurare il numero di istanze che servono un'app.
Figura 5-6. Scalabilità del piano di servizio app in Azure.
Architettura pulita
Le applicazioni che seguono il principio di inversione delle dipendenze e i principi DDD (Domain-Driven Design) tendono ad arrivare a un'architettura simile. Questa architettura ha avuto molti nomi nel corso degli anni. Uno dei primi nomi era "Architettura esagonale", seguita da "Porte e Adattatori". Più di recente, è stato citato come architettura di cipolla o architettura pulita. Quest'ultimo nome, Clean Architecture, viene usato come nome per questa architettura in questo e-book.
L'applicazione di riferimento eShopOnWeb usa l'approccio Clean Architecture per organizzare il codice in progetti. È possibile trovare un modello di soluzione che è possibile usare come punto di partenza per le proprie soluzioni ASP.NET Core nel repository GitHub ardalis/cleanarchitecture o installando il modello da NuGet.
L'architettura pulita inserisce la logica di business e il modello di applicazione al centro dell'applicazione. Invece di avere la logica di business dipende dall'accesso ai dati o da altri problemi dell'infrastruttura, questa dipendenza viene invertita: i dettagli dell'infrastruttura e dell'implementazione dipendono dal core dell'applicazione. Questa funzionalità viene ottenuta definendo astrazioni o interfacce in Application Core, che vengono quindi implementate dai tipi definiti nel livello Infrastruttura. Un modo comune per visualizzare questa architettura consiste nell'usare una serie di cerchi concentrici, simili a una cipolla. La figura 5-7 mostra un esempio di questo stile di rappresentazione architetturale.
Figura 5-7. Architettura pulita; vista stratificata
In questo diagramma le dipendenze passano verso il cerchio più interno. Application Core prende il nome dalla sua posizione al centro di questo diagramma. E si può vedere nel diagramma che Application Core non ha dipendenze da altri livelli dell'applicazione. Le entità e le interfacce dell'applicazione si trovano al centro. Appena fuori, ma ancora in Application Core, sono servizi di dominio, che in genere implementano interfacce definite nel cerchio interno. All'esterno di Application Core, sia l'interfaccia utente che i livelli Infrastruttura dipendono dal core dell'applicazione, ma non l'uno dall'altro (necessariamente).
La figura 5-8 mostra un diagramma del livello orizzontale più tradizionale che riflette meglio la dipendenza tra l'interfaccia utente e gli altri livelli.
Figura 5-8. Architettura pulita; visualizzazione livello orizzontale
Si noti che le frecce solide rappresentano dipendenze in fase di compilazione, mentre la freccia tratteggiata rappresenta una dipendenza di sola esecuzione. Con l'architettura pulita, il livello dell'interfaccia utente funziona con le interfacce definite nel core dell'applicazione in fase di compilazione e idealmente non dovrebbe conoscere i tipi di implementazione definiti nel livello Infrastruttura. In fase di esecuzione, tuttavia, questi tipi di implementazione sono necessari per l'esecuzione dell'app, quindi devono essere presenti e collegati alle interfacce di base dell'applicazione tramite inserimento delle dipendenze.
La figura 5-9 mostra una visualizzazione più dettagliata dell'architettura di un'applicazione ASP.NET Core quando viene compilata seguendo queste raccomandazioni.
Figura 5-9. Diagramma dell'architettura di ASP.NET Core secondo la Clean Architecture.
Poiché Application Core non dipende dall'infrastruttura, è molto facile scrivere unit test automatizzati per questo livello. Le figure 5-10 e 5-11 mostrano come i test rientrano in questa architettura.
Figura 5-10. Eseguire test unitari sul Core dell'applicazione in isolamento.
Figura 5-11. Test di integrazione delle implementazioni dell'infrastruttura con dipendenze esterne.
Poiché il livello dell'interfaccia utente non ha alcuna dipendenza diretta dai tipi definiti nel progetto Infrastruttura, è altrettanto facile scambiare implementazioni, per facilitare il test o in risposta alla modifica dei requisiti dell'applicazione. ASP.NET Core, con il suo utilizzo integrato e supporto per l'iniezione di dipendenze, rende questa architettura il modo più appropriato per strutturare applicazioni monolitiche complesse.
Per le applicazioni monolitiche, i progetti Application Core, Infrastructure e UI vengono eseguiti come singola applicazione. L'architettura dell'applicazione di runtime potrebbe essere simile alla figura 5-12.
Figura 5-12. L'architettura di runtime di un'applicazione di esempio ASP.NET Core.
Organizzazione del codice in Architettura pulita
In una soluzione Clean Architecture ogni progetto ha responsabilità chiare. Di conseguenza, alcuni tipi appartengono a ogni progetto e spesso si trovano cartelle corrispondenti a questi tipi nel progetto appropriato.
Nucleo dell'Applicazione
Application Core include il modello di business, che include entità, servizi e interfacce. Queste interfacce includono astrazioni per le operazioni che verranno eseguite usando l'infrastruttura, ad esempio l'accesso ai dati, l'accesso al file system, le chiamate di rete e così via. A volte i servizi o le interfacce definite a questo livello dovranno funzionare con tipi non di entità che non hanno dipendenze dall'interfaccia utente o dall'infrastruttura. Questi possono essere definiti come semplici oggetti di trasferimento dati (DTO).
Tipi di base dell'applicazione
- Entità (classi di modelli aziendali persistenti)
- Aggregazioni (gruppi di entità)
- Interfacce
- Servizi di dominio
- Indicazioni
- Eccezioni personalizzate e clausole di protezione
- Eventi e gestori di dominio
Infrastruttura
Il progetto Infrastruttura include in genere implementazioni di accesso ai dati. In un'applicazione Web ASP.NET Core tipica, queste implementazioni includono Entity Framework (EF) DbContext, tutti gli oggetti EF Core Migration
definiti e le classi di implementazione dell'accesso ai dati. Il modo più comune per astrarre il codice di implementazione dell'accesso ai dati consiste nell'usare il modello di progettazione repository.
Oltre alle implementazioni di accesso ai dati, il progetto Infrastruttura deve contenere implementazioni di servizi che devono interagire con le problematiche dell'infrastruttura. Questi servizi devono implementare le interfacce definite nel core dell'applicazione e pertanto l'infrastruttura deve avere un riferimento al progetto Application Core.
Tipi di infrastruttura
- Tipi EF Core (
DbContext
,Migration
) - Tipi di implementazione dell'accesso ai dati (repository)
- Servizi specifici dell'infrastruttura (ad esempio,
FileLogger
oSmtpNotifier
)
Livello interfaccia utente
Il livello dell'interfaccia utente in un'applicazione MVC core ASP.NET è il punto di ingresso per l'applicazione. Questo progetto deve fare riferimento al progetto Application Core e i relativi tipi devono interagire con l'infrastruttura rigorosamente tramite interfacce definite in Application Core. Nel livello dell'interfaccia utente non deve essere consentita alcuna creazione diretta di istanze o chiamate statiche ai tipi di livello infrastruttura.
Tipi di livello interfaccia utente
- Controllori
- Filtri personalizzati
- Middleware personalizzato
- Visualizzazioni
- Modelli di Vista
- Nuova impresa
La Startup
classe o il file Program.cs è responsabile della configurazione dell'applicazione e del collegamento dei tipi di implementazione alle interfacce. La posizione in cui viene eseguita questa logica è nota come radice di composizione dell'app ed è ciò che consente l'inserimento delle dipendenze per funzionare correttamente in fase di esecuzione.
Annotazioni
Per collegare l'iniezione delle dipendenze durante l'avvio dell'app, potrebbe essere necessario che il progetto del livello dell'interfaccia utente faccia riferimento al progetto Infrastruttura. Questa dipendenza può essere eliminata, più facilmente utilizzando un contenitore DI personalizzato con supporto integrato per il caricamento di tipi dagli assembly. Ai fini di questo esempio, l'approccio più semplice consiste nel consentire al progetto dell'interfaccia utente di fare riferimento al progetto Infrastructure (ma gli sviluppatori devono limitare i riferimenti effettivi ai tipi nel progetto Infrastruttura alla radice della composizione dell'app).
Applicazioni e contenitori monolitici
È possibile compilare un'applicazione Web o un servizio basato su distribuzione monolitica e distribuirla come contenitore. All'interno dell'applicazione, potrebbe non essere monolitico ma organizzato in diverse librerie, componenti o livelli. Esternamente, si tratta di un singolo contenitore con un singolo processo, una singola applicazione Web o un singolo servizio.
Per gestire questo modello, distribuire un singolo contenitore per rappresentare l'applicazione. Per ridimensionare, è sufficiente aggiungere copie con un bilanciatore di carico davanti. La semplicità deriva dalla gestione di una singola distribuzione in un singolo contenitore o macchina virtuale.
È possibile includere più componenti/librerie o livelli interni all'interno di ogni contenitore, come illustrato nella figura 5-13. Tuttavia, seguendo il principio del contenitore di "un contenitore esegue un'unica operazione e lo esegue in un unico processo", il modello monolitico potrebbe rappresentare un conflitto.
Lo svantaggio di questo approccio è se/quando l'applicazione cresce, richiedendo la scalabilità. Se l'intera applicazione viene ridimensionata, non è un problema. Nella maggior parte dei casi, tuttavia, alcune parti dell'applicazione sono i punti di soffocamento che richiedono il ridimensionamento, mentre altri componenti vengono usati meno.
Usando l'esempio tipico di e-commerce, è probabile che sia necessario ridimensionare il componente delle informazioni sul prodotto. Molti più clienti esplorano i prodotti rispetto all'acquisto. Più clienti usano il carrello rispetto a quanti utilizzano il sistema di pagamento. Meno clienti aggiungono commenti o visualizzano la cronologia degli acquisti. E probabilmente hai solo pochi dipendenti, in una singola area, che devono gestire i contenuti e le campagne di marketing. Ridimensionando la progettazione monolitica, tutto il codice viene distribuito più volte.
Oltre al problema "ridimensiona tutto", le modifiche apportate a un singolo componente richiedono la ripetizione completa dell'intera applicazione e una ridistribuzione completa di tutte le istanze.
L'approccio monolitico è comune e molte organizzazioni stanno sviluppando con questo approccio architettonico. Molti hanno risultati sufficienti, mentre altri stanno raggiungendo i limiti. Molti hanno progettato le loro applicazioni seguendo questo modello, perché gli strumenti e l'infrastruttura erano troppo difficili per costruire architetture orientate ai servizi (SOA), e non sentivano la necessità fino a quando l'applicazione non è cresciuta. Se si rileva che si stanno raggiungendo i limiti dell'approccio monolitico, suddividere l'app per abilitarla per sfruttare meglio i contenitori e i microservizi può essere il passaggio logico successivo.
La distribuzione di applicazioni monolitiche in Microsoft Azure può essere ottenuta usando macchine virtuali dedicate per ogni istanza. Usando i set di scalabilità di macchine virtuali di Azure, è possibile ridimensionare facilmente le macchine virtuali. Servizi app di Azure può eseguire applicazioni monolitiche e ridimensionare facilmente le istanze senza dover gestire le macchine virtuali. Azure App Services eseguono anche istanze singole di contenitori Docker, semplificando la distribuzione. Usando Docker, è possibile distribuire una singola macchina virtuale come host Docker ed eseguire più istanze. Usando il servizio di bilanciamento di Azure, come illustrato nella figura 5-14, è possibile gestire il ridimensionamento.
La distribuzione nei vari host può essere gestita con tecniche di distribuzione tradizionali. Gli host Docker possono essere gestiti con comandi come l'esecuzione manuale di Docker o tramite l'automazione, ad esempio pipeline di recapito continuo (CD).
Applicazione monolitica distribuita come contenitore
Esistono vantaggi dell'uso dei contenitori per gestire le distribuzioni di applicazioni monolitiche. Il ridimensionamento delle istanze dei contenitori è molto più veloce e semplice rispetto alla distribuzione di macchine virtuali aggiuntive. Anche quando si usano set di scalabilità di macchine virtuali per ridimensionare le macchine virtuali, la creazione richiede tempo. Quando viene distribuita come istanze dell'app, la configurazione dell'app viene gestita come parte della macchina virtuale.
La distribuzione degli aggiornamenti sotto forma di immagini Docker è molto più veloce ed efficiente dal punto di vista della rete. Le immagini Docker vengono in genere avviate in secondi, velocizzando le implementazioni. L'eliminazione di un'istanza di Docker è semplice quanto eseguire il comando docker stop
, che di solito richiede meno di un secondo per completarsi.
Poiché i contenitori sono intrinsecamente immutabili per impostazione predefinita, non è mai necessario preoccuparsi delle macchine virtuali danneggiate, mentre gli script di aggiornamento potrebbero dimenticare di tenere conto di una configurazione o di un file specifico lasciato sul disco.
È possibile usare i contenitori Docker per una distribuzione monolitica di applicazioni Web più semplici. Questo approccio migliora l'integrazione continua e le pipeline di distribuzione continua e consente di ottenere un successo da distribuzione a produzione. Non c'è più "Funziona sul mio computer, perché non funziona nell'ambiente di produzione?"
Un'architettura basata su microservizi offre molti vantaggi, ma questi vantaggi comportano un aumento della complessità. In alcuni casi, i costi superano i vantaggi, quindi un'applicazione di distribuzione monolitica in esecuzione in un singolo contenitore o in pochi contenitori è un'opzione migliore.
Un'applicazione monolitica potrebbe non essere facilmente componibile in microservizi ben separati. I microservizi devono funzionare indipendentemente l'uno dall'altro per fornire un'applicazione più resiliente. Se non è possibile distribuire sezioni di funzionalità indipendenti dell'applicazione, separandola aggiunge solo complessità.
Un'applicazione potrebbe non dover ancora ridimensionare le funzionalità in modo indipendente. Molte applicazioni, quando devono essere ridimensionate oltre una singola istanza, possono eseguire questa operazione tramite il processo relativamente semplice di clonazione dell'intera istanza. Il lavoro aggiuntivo per separare l'applicazione in servizi discreti offre un vantaggio minimo quando è semplice e conveniente ridimensionare intere istanze dell'applicazione.
All'inizio dello sviluppo di un'applicazione, potrebbe non essere chiaro dove si trovano i limiti funzionali naturali. Man mano che si sviluppa un prodotto minimo funzionante, la separazione naturale potrebbe non essere ancora emersa. Alcune di queste condizioni potrebbero essere temporanee. È possibile iniziare creando un'applicazione monolitica e successivamente separare alcune funzionalità da sviluppare e distribuire come microservizi. Altre condizioni potrebbero essere essenziali per lo spazio dei problemi dell'applicazione, ovvero l'applicazione potrebbe non essere mai suddivisa in più microservizi.
La separazione di un'applicazione in molti processi discreti comporta anche un sovraccarico. La separazione delle funzionalità in processi diversi è più complessa. I protocolli di comunicazione diventano più complessi. Anziché le chiamate ai metodi, è necessario usare le comunicazioni asincrone tra i servizi. Quando si passa a un'architettura di microservizi, è necessario aggiungere molti dei blocchi predefiniti implementati nella versione dei microservizi dell'applicazione eShopOnContainers: gestione del bus di eventi, resilienza dei messaggi e tentativi, coerenza finale e altro ancora.
L'applicazione di riferimento eShopOnWeb molto più semplice supporta l'utilizzo di contenitori monolitici a contenitore singolo. L'applicazione include un'applicazione Web che include visualizzazioni MVC tradizionali, API Web e Razor Pages. Facoltativamente, è possibile eseguire il componente amministratore basato su Blazor dell'applicazione, che richiede anche l'esecuzione di un progetto API separato.
L'applicazione può essere avviata dalla radice della soluzione usando i docker-compose build
comandi e docker-compose up
. Questo comando configura un contenitore per l'istanza web, usando il Dockerfile
trovato nella radice del progetto web ed eseguendo il contenitore su una porta specifica. È possibile scaricare l'origine per questa applicazione da GitHub ed eseguirla in locale. Anche questa applicazione monolitica trae vantaggio dalla distribuzione in un ambiente contenitore.
Per uno, la distribuzione in contenitori significa che ogni istanza dell'applicazione viene eseguita nello stesso ambiente. Questo approccio include l'ambiente di sviluppo in cui si svolgono i primi test e lo sviluppo. Il team di sviluppo può eseguire l'applicazione in un ambiente in contenitori corrispondente all'ambiente di produzione.
Inoltre, le applicazioni in contenitori aumentano a un costo inferiore. L'uso di un ambiente contenitore consente una condivisione di risorse maggiore rispetto agli ambienti di macchine virtuali tradizionali.
Infine, la containerizzazione dell'applicazione forza una separazione tra la logica di business e il server di archiviazione. Con il ridimensionamento orizzontale dell'applicazione, tutti i contenitori dipendono da un unico dispositivo di archiviazione fisico. Questo supporto di archiviazione è in genere un server a disponibilità elevata che esegue un database di SQL Server.
Supporto di Docker
Il eShopOnWeb
progetto viene eseguito in .NET. Di conseguenza, può essere eseguito in contenitori basati su Linux o basati su Windows. Si noti che per la distribuzione di Docker si vuole usare lo stesso tipo di host per SQL Server. I contenitori basati su Linux consentono un footprint più piccolo e sono preferiti.
È possibile usare Visual Studio 2017 o versione successiva per aggiungere il supporto Docker a un'applicazione esistente facendo clic con il pulsante destro del mouse su un progetto in Esplora soluzioni e scegliendo Aggiungi>supporto Docker. Questo passaggio aggiunge i file necessari e modifica il progetto per usarli. L'esempio corrente eShopOnWeb
include già questi file.
Il file a livello docker-compose.yml
di soluzione contiene informazioni sulle immagini da compilare e sui contenitori da avviare. Il file consente di usare il docker-compose
comando per avviare più applicazioni contemporaneamente. In questo caso, viene avviato solo il progetto Web. È anche possibile usarlo per configurare le dipendenze, ad esempio un contenitore di database separato.
version: '3'
services:
eshopwebmvc:
image: eshopwebmvc
build:
context: .
dockerfile: src/Web/Dockerfile
environment:
- ASPNETCORE_ENVIRONMENT=Development
ports:
- "5106:5106"
networks:
default:
external:
name: nat
Il docker-compose.yml
file fa riferimento a Dockerfile
nel Web
progetto. Viene Dockerfile
usato per specificare quale contenitore di base verrà usato e come verrà configurata l'applicazione. L'oggetto Web
' Dockerfile
:
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /app
COPY *.sln .
COPY . .
WORKDIR /app/src/Web
RUN dotnet restore
RUN dotnet publish -c Release -o out
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build /app/src/Web/out ./
ENTRYPOINT ["dotnet", "Web.dll"]
Risoluzione dei problemi di Docker
Dopo aver eseguito l'applicazione in contenitori, l'applicazione continua a essere eseguita fino a quando non viene interrotta. È possibile visualizzare i contenitori in esecuzione con il docker ps
comando . È possibile arrestare un contenitore in esecuzione usando il docker stop
comando e specificando l'ID contenitore.
Si noti che l'esecuzione dei contenitori Docker può essere collegata a porte che altrimenti si potrebbero cercare di utilizzare nel proprio ambiente di sviluppo. Se si tenta di eseguire o eseguire il debug di un'applicazione usando la stessa porta di un contenitore Docker in esecuzione, verrà visualizzato un errore che informa che il server non può eseguire l'associazione a tale porta. Ancora una volta, l'arresto del contenitore deve risolvere il problema.
Se si vuole aggiungere il supporto Docker all'applicazione usando Visual Studio, assicurarsi che Docker Desktop sia in esecuzione quando si esegue questa operazione. La procedura guidata non verrà eseguita correttamente se Docker Desktop non è in esecuzione all'avvio della procedura guidata. Inoltre, la procedura guidata analizza la scelta attuale del contenitore per fornire il supporto Docker appropriato. Se si vuole aggiungere, il supporto per i contenitori di Windows, è necessario eseguire la procedura guidata mentre Docker Desktop è in esecuzione con i contenitori di Windows configurati. Se si vuole aggiungere, supportare i contenitori Linux, eseguire la procedura guidata durante l'esecuzione di Docker con contenitori Linux configurati.
Altri stili di architettura dell'applicazione Web
- Web-Queue-Worker: i componenti principali di questa architettura sono un front-end Web che gestisce le richieste client e un ruolo di lavoro che esegue attività a elevato utilizzo di risorse, flussi di lavoro a esecuzione prolungata o processi batch. Il front-end web comunica con il processo di lavoro tramite una coda di messaggi.
- Livello N: un'architettura a più livelli divide un'applicazione in livelli logici e livelli fisici.
- Microservizi: un'architettura di microservizi è costituita da una raccolta di servizi autonomi di piccole dimensioni. Ogni servizio è indipendente e deve implementare una singola funzionalità aziendale all'interno di un contesto delimitato.
Riferimenti : architetture Web comuni
-
Architettura pulita
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html -
Architettura Onion
https://jeffreypalermo.com/blog/the-onion-architecture-part-1/ -
Modello di repository
https://deviq.com/repository-pattern/ -
Modello di Soluzione Architettura Pulita
https://github.com/ardalis/cleanarchitecture -
E-book sulla progettazione dei microservizi
https://aka.ms/MicroservicesEbook -
DDD ( progettazioneDomain-Driven)
https://learn.microsoft.com/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/