Skip to content

API RESTful con ASP.NET Core e docker

Best practice architetturali per creare API RESTful con ASP.NET Core e docker

Dopo tanti anni che faccio questo lavoro, non ricordo quante volte mi sono trovato al cospetto di API mal documentate, scritte male o che non rispettassero nessuno standard o pattern, tanto da rendere l’integrazione un bagno di sangue. Se 10 anni fa ognuno aveva la sua idea di implementazione e forse poteva essere anche tollerato, oggi con tutti gli strumenti e gli standard a disposizione, questo non è più concepibile.

Se volete quindi risolvere una volta per tutte i problemi di incomprensibilità delle vostre API, o se solo volete trovare conferma sulla bontà del vostro lavoro, continuate la lettura di questo articolo, e troverete le risposte a tutte le vostre domande per creare API RESTful ben documentate e senza pesare sul tempo di realizzazione.

RESTful non è un termine nuovo. Si riferisce a uno stile architetturale in cui servizi Web ricevono e inviano dati da e verso le applicazioni client e con l’obiettivo di  centralizzare i dati che verranno utilizzati.

La scelta degli strumenti giusti per scrivere i servizi RESTful è fondamentale poiché dobbiamo preoccuparci di scalabilità, manutenzione, documentazione e tutti gli altri aspetti rilevanti. ASP.NET Core ci offre un’API potente e facile da usare, ottima per raggiungere questi obiettivi.

In questo articolo mostrerò come scrivere un’applicazione API RESTful ben strutturata per uno scenario “quasi” reale, usando il framework ASP.NET Core. Cercherò anche di dettagliare modelli e strategie comuni per semplificare il processo di sviluppo, alcuni pattern e l’architettura utilizzata. Mostrerò anche come integrare framework e librerie, come Dependency injection, AutoMapper , NLog  per fornire le funzionalità necessarie.

Ingredienti creare API RESTful

Sicuramente vi sarà capitato di leggere qualche articolo di cucina, di come si preparano squisiti dolci o primi piatti; la prima cosa è la lista degli ingredienti. Anche nel nostro caso abbiamo bisogno di alcuni “ingredienti” per poter realizzare il nostro “dolce”!

Per intraprendere questo percorso per cominciare è necessario avere una buona conoscenza dei concetti di programmazione orientata agli oggetti.

Andiamo avanti poi col supporre di sapere cos’è REST, come funziona il protocollo HTTP, cosa sono gli endpoint API e cos’è un JSON (esistono in rete ottimo tutorial introduttivo su questo argomento).

Un altro requisito necessario è avere una conoscenza di base dei database NoSQL e del loro funzionamento, in particolare mongodb, che sarà quello che utilizzeremo nella nostra applicazione. Giusto come introduzione generale, possiamo dire che i database NoSQL sono appositamente realizzati per modelli di dati specifici e hanno schemi flessibili per creare applicazioni moderne. I database NoSQL si sono affermati per la facilità di sviluppo, la funzionalità e la scalabilità delle prestazioni.

Infine è necessario una conoscenza di base di docker, e docker-compose, che anche se non necessario per eseguire l’applicazione, sarà comunque utile conoscere nel caso volessimo rilasciare la nostra applicazione in un container.

ASP.NET Core

ASP.NET Core è il progetto Microsoft per eseguire applicazione dotnet in sistemi operativi differenti oltre a Microsoft Windows. Sono in tutto quattro le caratteristiche distintive della nuova tecnologia:

  • è open source
  • è cross-platform
  • è ottimizzata per il cloud
  • ha una struttura modulare.

 

Non è la prima volta che Microsoft si approccia all’universo dell’open source, ma è indubbiamente la prima volta che lo fa per una tecnologia di tale portata, rendendo pubblici i codici sorgente delle librerie di supporto alle applicazioni. Un altro grande passo avanti è l’orientamento al cloud ed al versante multi-piattaforma, non presente invece in NET Framework.

La scelta di utilizzare container docker si sposa perfettamente con la scelta tecnologica fatta, infatti le applicazioni ASP.NET Core possono essere eseguite su container linux, così come su windows e macosx.

I container saranno tutti creati a partire da un’immagine unix di base (alpine), con sopra il framework ASP.NET Core.

L’applicazione che andremo a creare, seppur semplice, avrà una struttura estensibile e potrà essere presa come riferimento per applicazioni più complesse.

Architettura RESTful e API Web

Una domanda legittima che adesso può venire spontanea è: perchè RESTful? bene, il motivo è presto detto: scalabilità.

Per scalabilità non si intende sempre e solo un problema di server o infrastrutture, ma anche un problema squisitamente software.

Prendiamo per esempio i colossi del web, come Google, Facebook, Twitter o Linkedin, la loro popolarità e importanza sul mercato è sicuramente frutto di duro lavoro interno, ma è anche frutto del fatto che hanno messo a disposizione i loro servizi tramite un set di API ben documentate. Questi grandi BIG quindi usano REST per fare scalare il loro utilizzo anche verso sistemi di terze parti, come client web,  applicazioni mobile, ecc…

Questo per dire, che se facciamo bene il nostro lavoro, e forniamo ottimi servizi e ben documentati, in tanti potranno utilizzarli, permettendoci di crescere velocemente.

Esistono delle linee guida e regole da seguire quando si progettano delle API in modo che siano logicamente comprensibili anche da chi non le ha sviluppate. In questo articolo quindi cercherò anche di fornire un insieme di linee guida attraverso punti chiave / suggerimenti su come progettare API.

La documentazione dei servizi esposti è molto importante, ed è per questo che adotteremo SWAGGER, un tool di sviluppo riconosciuto come standard dalla community per la produzione di documentazione di API.

Terminologia
  • Risorsa: è un oggetto o la rappresentazione di qualcosa di significativo nel dominio applicativo. La risorsa viene comunemente chiamata entità a livello di database, e le API vengono utilizzate per interagire con tali risorse.
  • Collezioni: sono insiemi di risorse. Ad esempio Persone è una collezione di risorse Persona.
  • URL (Uniform Resource Locator): è il path attraverso la quale una risorsa può essere ottenuta
Nome delle risorse e design delle URL

Riporto di seguito le indicazione di base per la stesura di API RESTful:

  • Ogni risorsa deve avere un id univoco
  • Usiamo il plurale per le url delle risorse (es: persons)
  • Usiamo l’id per accedere alla risorsa
  • Potrebbe essere necessario rilasciare versioni differenti delle nostre API, quindi è consigliato utilizzare un numero di versione della Base URL delle API.
Metodi e codici HTTP

Perchè utilizzare i metodi  e i codici del Protocollo HTTP?

La risposta è semplice: cerchiamo di rendere più standard possibile la nostra applicazione, così che chiunque dovrà utilizzarla non dovrà studiarsi nulla di nuovo, ma troverà le cose esattamente dove se le aspetta e nel modo in cui se le aspetta.

In particolare:

  • I metodi (o verbi) HTTP comunicano al server cosa fare con i dati identificati dalla URL.
  • i codici di stato sono risposte chiare e rapide che un server dà ad ogni richiesta HTTP che riceve.
Metodi

Utilizziamo i metodi HTTP per le attività di modifica delle risorse (CRUD)

  • C: Create → Metodo POST
  • R: Read   → Metodo GET
  • U: Update → Metodo PUT
  • D: Delete → Metodo DELETE
Codici

Utilizziamo i codici di risposta HTTP per la risposta al client, così che ogni client possa interpretare correttamente l’esito della chiamata fatta:

  • Categoria 2XX (successo)
    • 200 : OK
    • 201: Created
    • 204: No content
  • Categoria 3XX (Redirect)
    • 304: Not Modified
  • Categoria 4XX (Errori)
    • 400: Bad Request
    • 401: Unauthorized
    • 403: Forbidden
    • 404: Not Found
Componenti
  1. Architettura
    1. BLL e servizi
    2. Controller e api RESTful
    3. DAL e repository
  2. Librerie / framework ASP.NET Core
    1. Dependency Injection
    2. Automapper
    3. NLog
  3. Autenticazione
    1. Oauth2
  4. Docker
    1. docker file
    2. docker-compose
Considerazioni finali
1.  Architettura

Lo scopo di questo articolo è illustrare in maniera semplice e chiara come strutturare un’applicazione RestFUL con ASP.NET Core ed eseguita su container docker, attraverso una architettura a servizi, avvalendosi di alcuni pattern di sviluppo  e di alcune librerie disponibili su ASP.NET Core che ci semplificheranno la vita!

L’architettura dell’applicazione finale avrà una struttura come in figura:

Una architettura a servizi è una architettura dove la logica di business (BLL business logic layer) e la logica di accesso al db (DAL data access layer) coesistono insieme nella stessa applicazione e perciò “girano” nello stesso applicativo ( o container).

Generalmente la BLL è costituita come una libreria che contiene tutti i modelli e i servizi per la logica applicativa, mentre il DAL contiene le entità che mappano il db e la logica di accesso ai dati. Vedamo nel dettaglio queste due parti fondamentali dell’applicazione.

1.1 BLL e Servizi

1.1.1 BLL (Business Logic Layer)

In questa parte dell’applicazione risiede gran parte del codice che andiamo a scrivere, perché è  qui che è inserita tutta la logica applicativa.

La struttura di tale layer può differire tra progetto e progetto, ma per una questione di organizzazione, di pratica, di abitudine, di pattern, o solo per giudizio, è sempre importante suddividere tale logica in varie cartelle, le collezioni di nomi di entità (o namespace).

1.1.2 Servizi

I servizi sono le classi che contengono la logica, o in altre parole, il codice che scriveremo. I servizi verranno resi disponibili in tutte le parti dell’applicazione dove saranno necessari, attraverso la dependency injection.

1.2 Controller

Ogni applicazione API Restful necessita di almeno un controller per poter funzionare: il controller è il componente al quale arrivano tutte le chiamate dal frontend (l’utente), e si preoccupa di richiamare i servizi necessari a soddisfare le richieste, ritornando poi al frontend il risultato.

1.3 DAL e Repository

1.3.1( Data Access Layer)

Il Data access layer (in italiano livello di accesso ai dati), è il livello applicativo che si preoccupa di accedere ai dati, quindi è il layer che comunica direttamente con la base dati.

1.3.2 Repository

All’interno del DAL è possibile trovare un ulteriore elemento architetturale, i repository, che sono le classi che implementano l’accesso al dato, specifico per ogni tipo di base dati. Nel nostro caso, l’implementazione del repository sarà verso il database mongodb, attraverso l’utilizzo di MongoClient, una libreria disponibile per il download su nuget.

2. Librerie e Framework ASP.NET Core

2.1 Dependency Injection (Framework)
Inserimento delle dipendenze in ASP.NET Core.

ASP.NET Core supporta il modello progettuale software per l’inserimento delle dipendenze, ovvero una tecnica per ottenere l’inversione del controllo (IoC) tra classi e relative dipendenze.

Una dipendenza è qualsiasi oggetto richiesto da un altro oggetto.

L’idea alla base della Dependency Injection è quella di avere un componente esterno (assembler) che si occupi della creazione degli oggetti e delle loro relative dipendenze e di assemblarle mediante l’utilizzo dell’injection.

ASP.NET Core mette a disposizione dei metodi per poter aggiungere i servizi necessari ad un contenitore, e poi si prenderà lui carico di iniettare le dipendenze necessarie quando richieste.

Sarà quindi possibile aggiungere i servizi necessari per l’applicazione al container, e definire l’ambito della durata dei servizi (Temporaneo, con ambito o per richiesta o singleton).

2.2 Automapper (Libreria)

La nostra applicazione utilizza un design pattern basata su tre elementi principali:

  1. DTO (Data Transfer Object)
  2. BO (Business Object)
  3. Entity (l’entità che mappa la tabella nel db).

I DTO e i BO sono utilizzati per trasferire dati tra layer in una architettura applicativa multistrato e distribuita.

Il DTO è il modello atto a trasferire i dati dal layer dove è contenuta la logica di business  (BLL) verso il layer di presentazione (frontend), quindi serve per presentare i dati. Non ha logiche al suo interno, ed è mappato partendo dall’entità di base.

Il BO è il modello che invece trasferisce i dati dal frontend (l’utente)  verso il layer di accesso ai dati (DAL) passando attraverso la BLL, e dovrà quindi essere rimappato sull’entità. Può contenere logiche di conversione o elaborazione dei dati.

Per fare questi mapping (da Entità a DTO o da BO a Entità ad esempio)  in maniera semplice e automatica, invece di procedere manualmente, è possibile utilizzare una libreria, Automapper, disponibile su nuget,

Automapper si basa su naming convention, ossia si basa sui nomi (e sul tipo) delle proprietà per mapparle da un oggetto su un altro.

I mapping possono comunque essere customizzati, quando ad esempio le proprietà hanno nomi o tipi differenti (es. una stringa in un enum).

2.3 NLog (Libreria)

I log sono un elemento fondamentale per ogni applicazione. Esistono moltissimi sistemi e metodi per collezionare i log, ma prima di ogni cosa, è necessario scriverli!

Nella nostra applicazione utilizzeremo una libreria, sempre disponibile su nuget, che si chiama NLog.

3. Autenticazione

Per poter utilizzare la nostra applicazione, dobbiamo fornire un sistema di autenticazione, così che solo gli utenti abilitati possano accedere e sia possibile per noi profilare le chiamate per ogni utente. Questo è possibile attraverso una autenticazione su un servizio di authority che ci permette di utilizzare un token (un gettone) che identifica l’utente. L’aggiunta del token all’header delle chiamate permetterà cosi di profilare le richieste in arrivo dal frontend. Tutto questo si basa su un protocollo standard, OAuth2(https://oauth.net/2/). Non approfondiremo l’argomento, ma ci sono moltissime risorse online per chi avesse bisogno di studiare tale flusso di autenticazione.

Per la gestione dell’autenticazione faremo puntare la nostra applicazione ad un servizio esterno oline: https://auth0.com/ (Auth0 è un servizio che mette a disposizione un sistema di autenticazione già fatto, personalizzabile tramite impostazioni o tramite script javascript)

4. Docker

Docker è un progetto open source nato con lo scopo di automatizzare la distribuzione di applicazioni sotto forma di “contenitori” leggeri, portabili ed autosufficienti che possono essere eseguiti su cloud (pubblici o privati) o in locale.

I “contenitori” di Docker (chiamati con il termine inglese Container), sono quindi l’insieme dei dati di cui necessita un’applicazione per essere eseguita: librerie, altri eseguibili, rami del file system, file di configurazione, script, ecc.

Non è scopo di questo articolo approfondire il funzionamento di docker, quindi si rimanda al lettore eventuali necessari approfondimenti, in rete è disponibile tantissimo materiale sull’argomento.

Quello che ci interessa sapere, è che la nostra applicazione sarà contenuta e eseguita all’interno di questi container.

Per fare funzionare quindi il tutto è necessario scaricare ed installare docker desktop sul proprio PC, reperibile all’indirizzo:

4.1 Docker-compose

Una considerazione aggiuntiva va fatta riguardo a docker-compose.

Compose è uno strumento per la definizione e l’esecuzione di applicazioni Docker multi-contenitore. In altre parole, se la tua applicazione è costituita da più container, è possibile utilizzare un file (docker-compose.yml) di configurazione con cui definire l’architettura della rete e tutti i servizi da eseguire.

L’uso di docker-compose è fondamentalmente un processo in tre fasi:

  1. Definisci l’ambiente della tua applicazione con un Dockerfile modo che possa essere riprodotto ovunque.
  2. Definisci i servizi che compongono la tua applicazione in docker-compose.yml, in modo che possano essere eseguiti insieme in un ambiente isolato.
  3. Infine, esegui docker-compose up e per eseguire  l’intera applicazione.

 

Quindi, ricapitolando, avremo:

  • un docker file per ogni container che conterrà una applicazione (es: le nostre API RESTful saranno su un container, il db server sarà eseguito su un altro container, ecc.. vedi immagine nella sezione architettura)
  • un docker-compose file che mette insieme e relazione tutti i container necessari perché la nostra applicazione funzioni

5. Conclusioni

Siamo così giunti alla fine di questo articolo, contenente  spunti e suggerimenti utili per poter creare un’applicazione API RESTful con ASP.NET Core e docker, e nel prossimo articolo andremo a vedere in pratica quello che abbiamo visto in teoria.

Creare una applicazione ben strutturata non vuole certo dire che funzionerà; quello che la rende funzionante è il codice che ci scriviamo dentro; ma avere una sana e forte struttura ci permette di sviluppare tutto più rapidamente, riducendo al minimo errori architetturali che possono venire fuori durante lo sviluppo, e che potrebbe rendere necessario un refactoring, cosa che è sempre dolorosa in termini di tempo e costo.