Implementare un sistema Cloud Native chiavi in mano con Dapr

Nei mesi scorsi riceviamo da una multinazionale operante nel settore dell’automazione industriale la richiesta di consulenza per implementare un sistema di monitoraggio dei macchinari. I sensori da rilevare sono migliaia, il sistema deve scalare autonomamente, ma soprattutto ci vengono dati 4 requisiti da non sottovalutare:

  1. Il sistema dev’essere agnostico al cloud provider che verrà scelto dal cliente, ma deve avere la possibilità di essere installato anche on-premise;
  2. Gli sviluppatori devono poter setuppare la propria macchina e le dipendenze tra servizi in modo semplice;
  3. I team di sviluppo che si occuperanno di implementare le varie componenti del sistema non devono avere vincoli tecnologici dal punto di vista del linguaggio da utilizzare, per poter sfruttare le competenze già in possesso dei vari team;
  4. L’applicazione dovrà poter supportare diversi protocolli di messaggistica (AMQP o MQTT) e database engine differenti

All’interno del team di CodicePlastico è nata quindi una discussione su quali tool e approcci architetturali indirizzare la nostra attenzione. Il confronto ha permesso di dedicare del tempo a uno spike che ci ha consentito di approfondire uno strumento estremamente interessante: Dapr!

Problemi noti delle applicazioni a microservizi

Dai, diciamolo… i microservizi sono come i cani “degli altri”: sono belli finchè non devi portarli fuori te, a fare la passeggiata una sera d’inverno con la neve ed il vento sferzante, o a giocare al parco a mezzogiorno del 15 di agosto. Perché quest’affermazione tanto forte? Perchè le applicazioni a microservizi, volente o nolente, “soffrono” dei seguenti problemi:

  • Complessità della soluzione dovuta al fatto di dover gestire tante micro applicazioni;
  • Rendere sicura la comunicazione tra i microservizi non è sempre facile;
  • Introdurre (e gestire) uno strumento di
    • Service Discovery per evitare di disseminare indirizzi che fanno riferimento ad altri microservizi o applicativi;
    • Collezionare informazioni per il monitoraggio, in modo da avere un’unica dashboard che mostri lo stato dei servizi e le loro performance;
  • Gestire la pubblicazione e la sottoscrizione dei messaggi su code e la persistenza dello stato delle nostre entità di dominio.

Questi problemi vengono comunemente risolti:

  • Garantendo una comunicazione service-to-service criptata, in quando essendo potenzialmente distribuiti su più macchine, il traffico può transitare per dispositivi di rete che possono essere violati da terzi. Per un discorso di “security in depth” può avere senso in certi casi che i servizi dialoghino su connessione sicura;
  • Gestendo l’osservabilità grazie a tracing, metriche e logs, ma questo comporta l’introduzione e la gestione di strumenti quali Zipkin oppure OpenTelemetry;
  • Gestendo i retries per far fronte a periodi di non disponibilità dei servizi;
  • Basando le applicazioni distribuite su strumenti di comprovata affidabilità come Apache Kafka o RabbitMQ che garantiscono il delivery dei messaggi;

Si ok… ma nella pratica cosa dobbiamo fare? Possiamo introdurre un Service Mesh, cioè un layer a livello di infrastruttura che, in modo trasparente allo sviluppatore, aggiunge funzionalità come osservability, sicurezza, traffic management. I Service Mesh più diffusi sono LinkerdIstio e Open Service Mesh.

Dapr non è una soluzione service mesh completa: ponendosi come obiettivo quello fornire le funzionalità che ci consentono la realizzazione di applicazioni distribuite, fornisce quegli strumenti che servono agli sviluppatori per costruire in modo agevole applicazioni a microservizi. Nel caso ci servissero altre funzionalità come il traffic splitting, utile ad esempio per fare A/B testing, allora dovremo aggiungere una soluzione Service Mesh che fornisce questa funzionalità.

Si, ma… Cosa è Dapr?

Il termine Dapr sta per Distributed APplication Runtime.

E’ un runtime open source scritto in Go che ha come obiettivo quello di facilitare agli sviluppatori la costruzione di applicazioni resilienti e portabili, supportando diverse tecnologie, come Python, Node.js, .NET, Java e Go.

L’adozione di soluzioni basate sul cloud è sempre più diffusa, ma è comune che gli sviluppatori abbiano dimestichezza con applicazioni web con un proprio database dedicato. Meno comune è invece che la stessa dimestichezza sia presente se si parla di architetture distribuite composte da molti microservizi.

Ad entrare in gioco, qui, è proprio Dapr, che codifica le più recenti best practices per la creazione di microservizi in API aperte e indipendenti, chiamate “Building Blocks”. L’accesso alle funzionalità offerte da tali “blocchi” può avvenire tramite lo standard HTTP oppure tramite gRPC APIs, in modo tale che possano essere chiamati da qualsiasi linguaggio di programmazione, così da facilitare la comunicazione tra diversi microservizi. I Building Blocks quindi nascondono allo sviluppatore la complessità della logica da essi implementata ed esposta, consentendogli di concentrarsi sulle logiche applicative invece di preoccuparsi di che connettore usare o di dove e come salvare le configurazioni; tutto questo senza impattare sulle performance che, come mostrato nella sezione Service invocation performance della documentazione ufficiale, sono comunque elevate.

Dapr, è inoltre indipendente dalla piattaforma, ovvero è possibile eseguire applicazioni localmente, su cluster Kubernetes, su macchine virtuali o fisiche, o su altri ambienti di hosting, con i quali il runtime si integra (caspita… proprio quello che ci chiede il cliente!!! Bingo!)

Riassumendo, i punti di forza sono:

  • Permettere agli sviluppatori di scegliere tra i più popolari linguaggi per sviluppare applicazioni distribuite;
  • Risolvere i problemi più comuni delle applicazioni basate sui microservizi, consentendoci di realizzare applicazioni che adottino le best practice;
  • Essere indipendente dalla piattaforma cloud scelta e quindi di realizzare applicazioni vendor agnostic;
  • Fornire estendibilità e componenti collegabili senza vincoli del vendor

L’architettura di Dapr

Vediamo nello specifico come funziona Dapr.

Dapr utilizza il pattern sidecar, cioè le Dapr API vengono eseguite ed esposte attraverso un processo separato (il Dapr sidecar) in esecuzione “vicino” alla nostra applicazione.

Questo “sidecar” interagisce con i trigger di eventi, e comunica con l’applicazione tramite i protocolli standard HTTP o gRPC; ciò è il motivo per il quale sono supportati diversi linguaggi di programmazione senza alcun coinvolgimento di framework o librerie.

Dapr grazie ai building blocks può interfacciarsi con diversi stores per memorizzare lo stato dei componenti e comunicare tramite message bus come Redis o RabbitMQ per offrire un’ampia gamma di metodi di comunicazione, oppure effettuare chiamate dapr-to-dapr dirette tramite HTTP, gRPC e Pub-Sub asincrono.

Vediamo nel dettaglio questi due aspetti di Dapr.

Building Blocks

Dapr è costituito da un insieme di building blocks, con estensibilità per aggiungerne di nuovi.

Il diagramma seguente mostra come i Building Blocks predefiniti espongono un’API pubblica chiamata dal codice, utilizzando i componenti per implementare le varie funzionalità.

Nell’esempio viene mostrato come la nostra applicazione possa usufruire di Redis e di un Blob storage semplicemente chiamando le API esposte dal building block secondo la configurazione da noi desiderata

Building Blocks più famosi

Questo è l’elenco dei Building block forniti da Dapr

Vediamo qualcuno tra i building blocks più famosi (e che abbiamo avuto modo di testare):

Publish and Subscribe

Pub/Sub è un modello di messaggistica debolmente accoppiato in cui i publisher pubblicano messaggi su un topic, a cui i subscriber si iscrivono. Dapr supporta il modello pub/sub tra le applicazioni.

Configuration

Dapr fornisce un’API di configurazione che consente di recuperare e iscriversi agli elementi di configurazione dell’applicazione per gli archivi supportati. Ciò consente a un’applicazione di recuperare informazioni specifiche.

Resource Bindings

Un binding fornisce una connessione bidirezionale a un servizio o sistema cloud/locale esterno. Dapr consente di richiamare il servizio esterno tramite l’apposita API e permette all’applicazione di essere attivata da eventi inviati dal servizio connesso.

Service-To-Service Invocation

La chiamata del servizio consente alle applicazioni di comunicare tra loro tramite endpoint noti sotto forma di messaggi HTTP o gRPC. Dapr fornisce un endpoint che funge da combinazione di un proxy inverso con service discovery integrato, sfruttando al contempo il tracing distribuito e la gestione degli errori.

Side-Car

Come anticipato in precedenza, il funzionamento di Dapr prevede l’utilizzo di pattern sidecar, ovvero che le API del servizio di supporto vengano esposte su un processo separato, in esecuzione insieme alla nostra applicazione. Solitamente condivide lo stesso lifecycle della nostra applicazione. Il processo sidecar viene denominato daprd, e viene avviato in modi diversi a seconda dell’ambiente di hosting.

Vediamo un esempio tratto dalla documentazione ufficiale di Dapr di come avviene la comunicazione tra servizi che sfruttano i sidecar:

  1. Il servizio A effettua una chiamata HTTP o gRPC al servizio B. La chiamata viene intercettata dal sidecar locale di Dapr.
  2. Dapr recupera l’indirizzo del servizio B utilizzando il componente di name resolution in esecuzione sulla piattaforma di hosting.
  3. Dapr inoltra il messaggio al sidecar Dapr del servizio B
    Nota: tutte le chiamate tra i sidecar Dapr avvengono tramite gRPC per migliorare le performance.
  4. Il sidecar Dapr del servizio B inoltra la richiesta all’endpoint (o metodo) specificato. Il servizio B esegue la logica prevista dall’implementazione della chiamata.
  5. Il servizio B invia una risposta al servizio A. La risposta viene intercettata dal sidecar Dapr del servizio B.
  6. Dapr inoltra la risposta al sidecar Dapr del servizio A.
  7. Il servizio A riceve la risposta.

Sporchiamoci le mani…

Per verificare il funzionamento in locale di questo fantastico strumento, abbiamo realizzato una semplice (ma dai… diciamolo… mica poi così tanto) applicazione che simula uno scenario IoT.

L’applicativo che eseguiremo in modalità self-host è costituito da 3 parti principali:

  • Il simulatore, costituito da due console applications che simulano l’invio, ogni secondo, dei valori provenienti rispettivamente da un sensore di temperatura ed uno di umidità tramite protocollo MQTT. Ciascuna console application/simulatore scrive pubblica messaggi sul relativo topictemperature per il sensore di temperatura, humidity per quello di umidità
  • Il broker MQTT, costituito da una console application che implementa un broker MQTT
  • componenti relativi ai bounded context di:
    • Temperatura ed umidità che consentono di:
      • Memorizzare gli eventi pubblicati dai sensori
      • Memorizzare lo stato del sensore, quindi l’ultima lettura
      • Esporre un’API di lettura dello stato del sensore
    • Geolocalizzazione, per la risoluzione della località a partire dalle coordinate geografiche del sensore

Il diagramma dell’applicazione è il seguente:

Il broker

DaprMqtt.Broker è il progetto che ci ha permesso di implementare un broker MQTT. È stato implementato con la libreria MQTTnet disponibile su nuget. Al netto di qualche riga di codice per loggare in console lo stato ed i messaggi ricevuti, il cuore pulsante del broker è il seguente:

MqttServerOptionsBuilder options = new MqttServerOptionsBuilder()
            .WithDefaultEndpoint()
            .WithDefaultEndpointPort(707)
            .WithConnectionValidator(OnNewConnection)
            .WithApplicationMessageInterceptor(OnNewMessage);

IMqttServer mqttServer = new MqttFactory().CreateMqttServer();
await mqttServer.StartAsync(options.Build());

Per avviare il broker, eseguire dalla CLI il comando dotnet run.

I simulatori dei sensori di temperatura e umidità

Dapr.Iot.Devices.Temperature e Dapr.Iot.Devices.Humidity sono due semplicissimi progetti che, mediante la libreria MQTTnet, ci consentono di pubblicare le letture simulate tramite protocollo MQTT. Il codice per pubblicare i messaggi è il seguente (l’esempio si riferisce al sensore di umidità, ma è molto simile per quanto riguarda il sensore di temperatura e non riporta il codice necessario per loggare messaggi in console):

MqttClientOptionsBuilder builder = new MqttClientOptionsBuilder()
                                  .WithClientId("Dapr.Iot.Devices.Umidity")
                                  .WithTcpServer("localhost", 707);

ManagedMqttClientOptions options = new ManagedMqttClientOptionsBuilder()
                          .WithAutoReconnectDelay(TimeSpan.FromSeconds(60))
                          .WithClientOptions(builder.Build())
                          .Build();

IManagedMqttClient _mqttClient = 
                              new MqttFactory().CreateManagedMqttClient();
...
await _mqttClient.StartAsync(options);

Random rand = new Random(10);

while (true)
{
     string fromHumiditySensor = JsonSerializer.Serialize(
         new DeviceEvent ( 
                    Id: Guid.Parse("8b8f4142-ac68-4478-bf1c-0ddfd95a5641"), 
                    TS: DateTimeOffset.UtcNow,
                    Value: rand.Next(0, 100),
                    Coordinates: DeviceCoordinates.Generate()
                ));
     await _mqttClient.PublishAsync(nameof(Topics.humidity), fromHumiditySensor);

     Task.Delay(1000).GetAwaiter().GetResult();
}

Per avviare i simulatori, eseguire dalla CLI il comando dotnet run.

Lanciando broker e simulatori di sensori, nella console del broker vedremo il log dei messaggi

Che la produzione di dati abbia inizio!

I componenti

I progetti Dapr.IoT.GeoLocationDapr.IoT.Subscribers.Humidity e  Dapr.IoT.Subscribers.Temperature sono i componenti realizzati con Dapr. Partiamo dal più semplice Dapr.IoT.GeoLocation dal quale dipendono “gli altri due” della combriccola.

Il componente Dapr.IoT.GeoLocation

Il componente espone una sola API REST:

GET tolocation/{latitude}/{longitude}

A fronte di una chiamata che fornisce latitudine e longitudine nell’URL, restituisce sempre solo una località: Lumezzane. Beh, conoscerete tutti il vecchio detto… tutte le coordinate geografiche portano a Lumezzane.

All’interno del file Program.cs abbiamo la registrazione dei servizi e l’avvio dell’applicazione.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers().AddDapr(); // <====
var app = builder.Build();

app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

app.Run();

L’API è stata implementata in questo modo

[HttpGet("tolocation/{latitude}/{longitude}")]
public async Task<IActionResult> Get([FromRoute] string latitude,
                                     [FromRoute] string longitude,
                                     [FromServices] DaprClient daprClient)
    {
        var key = $"{latitude}|{longitude}";
        var locationEntry = 
            await daprClient.GetStateEntryAsync<string>("cache", key);
        if (locationEntry is null || locationEntry.Value is null)
        {
            await daprClient.SaveStateAsync<string>(Cache, key, "Lumezzane");
            locationEntry = 
                 await daprClient.GetStateEntryAsync<string>("cache", key);
        }
        return Ok(new { Location = locationEntry.Value });
    }

L’aspetto interessante di questa API è l’implementazione delle cache su Redis.

E’ interessante come la classe DaprClient fornisca le funzionalità per leggere e scrivere lo stato. L’aspetto ancor più interessante è che oggi si è deciso di utilizzare Redis, ma se domani dovessi decidere di utilizzare Mongo o Memcached il codice non cambierebbe e sarebbe necessario intervenire solo a livello di configurazione.

La configurazione la troviamo all’interno del file redis_state.yaml nella cartella Components

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: cache
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""
  - name: ttlInSeconds
    value: 10

Nella configurazione vengono riportate le seguenti informazioni

  • In metadata:name abbiamo il nome del componente, lo stesso valore che abbiamo usato nel servizio per referenziare la configurazione della cache
  • In spec:metadata:redisHost abbiamo l’indirizzo di Redis
  • In spec:metadata:ttlInSeconds abbiamo il TTL del valore in cache

Gli state store disponibili si trovano sulla documentazione ufficiale di Dapr ospitata su docs.dapr.io. Maggiori informazioni sulla gestione dello stato sono disponibili al seguente link.

Non ci resta che scegliere il nostro strumento preferito, valorizzare la configurazione del file components e tadaaaaa… ecco cambiata la cache.

Il comando per avviare il componente è il seguente:

dapr run --app-id "dapr-iot-geolocation" 
         --app-port 5002 
         --dapr-http-port 5012 
         --components-path "./components" 
         dotnet run

Il comando:

  • lancia l’applicazione con dotnet run all’interno dell’environment di Dapr
  • assegna all’applicazione il nome dapr-iot-geolocation (vedremo poi a cosa serve…)
  • esegue il binding tra la porta 5002 dell’applicazione sulla 5012 esposta da Dapr
  • recupera la configurazione dalla cartella locale Components (in alternativa andrà a leggere il file di configurazione dalla cartella .dapr presente nella cartella del mio utente)

I componenti Dapr.IoT.Subscribers.Humidity e Dapr.IoT.Subscribers.Temperature

I due componenti sono uguali per cui, per brevità, ne analizzeremo solo uno dei due (e visto il clima torrido di questi giorni sceglierei quello della temperatura).

Il file Program.cs è molto simile a quello visto nel precedente servizio, ma è utile riportarlo per evidenziare alcuni aspetti interessanti che riguardano la sottoscrizione dei messaggi provenienti dal broker MQTT e la possibilità di specificare che il formato dei messaggi è quello definito dalla specifica CloudEvents: nel nostro caso i messaggi non adotteranno questo formato e quindi, semplicemente, commentiamo la relativa riga di codice

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers().AddDapr(); // <====

var app = builder.Build();

app.UseRouting();
// app.UseCloudEvents();      // <==== NO SE NON VOGLIO UTILIZZARE IL FORMATO DEGLI EVENTI DI TIPO https://cloudevents.io/

app.UseEndpoints(endpoints =>
    {
        endpoints.MapSubscribeHandler();    // <====
        endpoints.MapControllers();
    });

app.Run();

Nel file TemperatureController.cs avremo tre metodi per salvare gli eventi, salvare lo stato ed esporre l’ultimo valore ricevuto.

Il primo metodo si sottoscrive ai messaggi pubblicati sul topic temperature e logga gli eventi su un database documentale MongoDB.

public const string EventStoreStoreName = "mongo_eventstore";
public const string PubSubEventStore = "mqtt_eventstore";

...

[Topic(PubSubEventStore, nameof(Topics.temperature))]
[HttpPost("temperature_subscribe_eventstore")]
public async Task<ActionResult> EventStore(
                           DeviceEvent deviceEvent, 
                           [FromServices] DaprClient daprClient)
    {
        logger.LogInformation("Saving temperature device event..");
        await daprClient.SaveStateAsync(EventStoreStoreName, Guid.NewGuid().ToString(), deviceEvent);
        logger.LogInformation($"Temperature device event saved!");
        return new OkResult();
    }

Il metodo recupera dalla cartella locale Components le configurazioni dai files mongo_eventstore.yaml.

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: mongo_eventstore
spec:
  type: state.mongodb
  version: v1
  metadata:
  - name: host
    value: localhost:27017
  - name: databaseName
    value: dapr
  - name: collectionName
    value: dapr_temperature_eventstore

mqtt_eventstore.yaml

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: mqtt_eventstore
  namespace: default
spec:
  type: pubsub.mqtt
  metadata:
  - name: consumerID
    value: "{uuid}"
  - name: url
    value: mqtt://localhost:707

Il secondo metodo anch’esso si sottoscrive ai messaggi pubblicati sul topic temperature, traduce le coordinate associate alla lettura (solo per quelle provenienti da nuovi dispositivi) in una località e persiste lo stato sullo stesso database documentale MongoDB presente nella configurazione del metodo precedente, ma su una collezione differente.

public const string StateStoreName = "mongo_state";
public const string PubSubState = "mqtt_state";


...

[Topic(PubSubState, nameof(Topics.temperature))]
[HttpPost("temperature_subscribe_state")]
public async Task<ActionResult> SaveState(
                 DeviceEvent deviceEvent, 
                 [FromServices] DaprClient daprClient)
{
        logger.LogInformation("Enter temperature device start");

        var state = await daprClient.GetStateEntryAsync<TemperatureModel>(StateStoreName, deviceEvent.Id.ToString());
        if (state.Value is null)
        {
            var location = 
await daprClient.InvokeMethodAsync<GeoLocationResponse>(HttpMethod.Get, "dapr-iot-geolocation", $"tolocation/{deviceEvent.Coordinates.Latitude}/{deviceEvent.Coordinates.Longitude}");
            state.Value = deviceEvent.ToModel(location.Location);
        }
        else
        {
            state.Value.TemperatureInC = deviceEvent.Value;
            state.Value.TemperatureInF = deviceEvent.Value.ToFarenheit();
            state.Value.TS = deviceEvent.TS.DateTime;
        }
        await state.SaveAsync();

        logger.LogInformation("State of temperature device {deviceEventId} updated!", deviceEvent.Id);
        return new OkResult();
    }

Aspetti interessanti di questo metodo sono legati all’implementazione della chiamata HTTP al componente della geolocalizzazione che, grazie al Service Discovery fornito da Dapr, si traduce in questa riga di codice semplicemente indicando il nome dato al servizio di geolocalizzazione nei parametri del comando di avvio (ricordate il parametro ` –app-id “dapr-iot-geolocation” ` ?)

await daprClient.InvokeMethodAsync<GeoLocationResponse>(
HttpMethod.Get, 
"dapr-iot-geolocation",      $"tolocation/{deviceEvent.Coordinates.Latitude}/{deviceEvent.Coordinates.Longitude}"
);

La persistenza dello stato del dispositivo è stata implementata con queste due righe

var state = await daprClient.GetStateEntryAsync<TemperatureModel>(StateStoreName, deviceEvent.Id.ToString());
... 
await state.SaveAsync();

Infine l’ultimo metodo, l’esposizione dello stato relativo a un device identificato dal suo identificativo univoco, sfruttando sempre la chiamata di lettura dello stato \

[HttpGet("temperature/device/{id}")]
public async Task<ActionResult<DeviceEvent>> Get(
                  [FromRoute]string id, 
                  [FromServices] DaprClient daprClient)
    {
        var state = 
    await daprClient.GetStateEntryAsync<TemperatureModel>(StateStoreName, id);
        return state.Value is null ? NotFound(): Ok(state.Value);
    }

Ė importante sottolineare che sottoscrivendo lo stesso topic tramite due metodi differenti, nel file di specifiche della configurazione di binding è necessario aggiungere questo valore:

- name: consumerID
    value: "{uuid}"

per specificare in modo univoco il client connesso al broker MQTT.

La chiamata per avviare il servizio è la seguente:

dapr run --app-id "dapr-iot-temperature" 
         --app-port 5000 
         --dapr-http-port 5010 
         --components-path "./components" 
         dotnet run

Possiamo recuperare l’ultimo valore di temperatura ricevuto con la seguente richiesta

curl http://localhost:5010/v1.0/invoke/dapr-iot-temperature/method/temperature/8b8f4142-ac68-4478-bf1c-0ddfd95a5641

oppure possiamo usare la get-by-id built-in

curl http://localhost:5010/v1.0/state/mongo_state/8b8f4142-ac68-4478-bf1c-0ddfd95a5641

Ed in entrambi i casi avremo la seguenti risposta

{
  "id": "8b8f4142-ac68-4478-bf1c-0ddfd95a5641",
  "temperatureInC": 33.054,
  "temperatureInF": 91.497,
  "latitude": 45.647897,
  "longitude": 10.264874,
  "location": "Lumezzane",
  "ts": "2022-06-03T12:44:23.2135325"
}

Per recuperare lo stato del sensore di umidità, invece, invece avremo la seguente chiamata

curl http://localhost:5011/v1.0/invoke/dapr-iot-humidity/method/humidity/device/8b8f4142-ac68-4478-bf1c-0ddfd95a5641

oppure possiamo usare la get-by-id built-in

curl http://localhost:5011/v1.0/state/mongo_state/8b8f4142-ac68-4478-bf1c-0ddfd95a5641

Ed in entrambi i casi avremo la seguenti risposta

{
  "id": "8b8f4142-ac68-4478-bf1c-0ddfd95a5641",
  "humidity": 12,
  "latitude": 45.6479,
  "longitude": 10.2649,
  "location": "Lumezzane",
  "ts": "2022-06-03T12:57:12.9088407"
}

La dashboard

Grazie al comando dapr dashboard possiamo accedere alla pagina web della dashboard, di default disponibile all’indirizzo http://localhost:8080/

La dashboard metterà a disposizione le seguenti pagine:

  • Un’overview dei servizi in esecuzione
  • I componenti (building blocks) utilizzati
  • La configurazione

La cosa sensazionale è che entrando nel dettaglio della configurazione è possibile vedere lo yaml

e navigando all’indirizzo mostrato dalla sezione zipkin, avrò gratuitamente il portale per il tracing della chiamate

Conclusioni

Nonostante la lunghezza dell’articolo, questa è solo una mini introduzione al magico mondo di Dapr. Ciò che ha affascinato di più è la possibilità di cambiare “al volo” l’infrastruttura sottostante la nostra applicazione semplicemente cambiando la configurazione dei componenti e voilà, tutto continua a funzionare. Anche la possibilità di usufruire di funzionalità come il service discovery è sicuramente un aspetto da non sottovalutare. E cosa ne pensate della dashboard?! Beh… da leccarsi le dita! (e poi non dimenticatevi di igienizzarle con l’apposito gel igienizzante 🙂 ).

Naturalmente, essendo Dapr è un’astrazione, perderemo un po’ di controllo sulla tecnologia sottostante con il rischio di non poter beneficiare appieno di tutte le potenzialità.

Ma molte sono le funzionalità di interesse non trattate in questo articolo, come la possibilità di implementare un’applicazione basata su Actor Model e la possibilità di deployare su Azure sfruttando il servizio Azure Container Apps, … ma questa è un’altra storia che magari vi racconteremo a breve e magari grazie al TUO progetto!

Repository

Guide, articoli, librerie:

Video:

Libri:

Corsi: