Tech Blog

Cosa sono gli OSGi Remote µServices

OSGi Framework come alternativa ai Microservices

Antonio Musarra
Software Architect
21 minuti di lettura
OSGi, µServices, RSA e ZooKeeper
Questo articolo è disponibile anche in English 🇬🇧

Siamo in un periodo storico dell'informatica dove tutti vanno pazzi per i Microservices. Chiunque abbia coniato il termine qualche anno addietro, chiaramente non sapeva o non gliene importava che il concetto di µServices fosse già stato introdotto nel 2010 da Peter Kriens (Software Architect presso la OSGi Alliance). La sua idea è sostanzialmente quella estratta dal suo articolo e che riporto a seguire, in cui il termine "micro" ha effettivamente senso.

What I am promoting is the idea of µServices, the concepts of an OSGi service as a design primitive. (cit. Peter Kriens)

Adesso ci troviamo nella situazione che abbiamo due termini che si pronunciano allo stesso modo ma hanno un significato diverso per quanto riguarda l'implementazione! Fortunatamente possiamo scriverli in forma diversa.

Quindi, scriveremo di µServices per fare riferimento ai piccoli ma buoni servizi Java, indipendenti e coesi, che ci consentono di raggiungere gli stessi obiettivi senza il sovraccarico dei Microservices.

Per concludere questa breve disquisizione sui termini per poi passare alla sostanza, vi consiglio la lettura dell'articolo Software mixed with marketing: micro-services pubblicato sempre da Peter Kriens.

Nel corso di questo articolo, che direi essere la prosecuzione del mio intervento Liferay as Digital Experience Platform in the context of Microservices (tenuto al Liferay Boot Camp 2020 Summer Edition), vorrei portarvi a valutare il framework OSGi come valida soluzione per l'implementazione della vostra architettura a Microservices.

Per seguire con efficacia i contenuti dell'articolo è comunque consigliabile un minimo d'infarinatura sul framework OSGi, per questo posso consigliarvi la lettura di questa sintetica presentazione OSGi e Liferay 7 del 2016 tenuta allo User Group Italiano di Liferay a Bologna.

1. OSGi µServices

I servizi OSGi o µServices sono il concetto chiave utilizzato per creare basi di codice modulare. Al livello più basso OSGi riguarda il caricamento di classe; ogni modulo (o bundle) ha il proprio caricatore di classi o class loader. Un bundle definisce le dipendenze esterne usando la direttiva Import-Package. Solo i package esportati esplicitamente attraverso la direttiva Export-Package possono essere utilizzati da altri package.

Questo livello di modularità assicura che solo le API (interfacce Java) siano condivise tra i bundle e le classi di implementazione siano strettamente nascoste.

Altri bundle possono quindi utilizzare il servizio cercandolo dall'interfaccia del servizio. Il bundle può utilizzare il servizio utilizzando la sua interfaccia, ma non deve sapere quale implementazione viene utilizzata o chi ha fornito l'implementazione. In OSGi questo problema è risolto dal registro dei servizi o Service Registry. Il registro dei servizi fa parte del framework OSGi. Un bundle può registrare un servizio in questo registro. Ciò registrerà un'istanza di una classe di implementazione nel registro con la sua interfaccia.

Volendo brevemente riassumere, potremmo dire che:

  1. i µServices o servizi, sono costituiti da una serie d'interfacce e classi Java registrate all'interno del Service Registry;
  2. un componente o component può essere pubblicato come servizio;
  3. un componente o component può richiedere un servizio;
  4. i servizi sono registrati dal Bundle Activator.

La figura a seguire mostra il Service Registry dove ogni bundle può registrare i propri service ma anche dove ogni bundle può trovare i servizi da consumare; potremmo dire che sia una sorta di "faldone".

Figura 1 - Registrazione dei servizi sul registro e uso degli stessi da parte
dei bundle
Figura 1 - Registrazione dei servizi sul registro e uso degli stessi da parte dei bundle

La figura a seguire mostra il classico OSGi Service Pattern che le applicazioni modulari seguono. Una tipica applicazione è quindi constituita da:

  1. un bundle che definisce tutte le API (interfacce Java) e quest'ultime sono esportate verso "l'esterno" tramite la direttiva Export-Package;
  2. un bundle che implementa le interfacce definite dal bundle delle API. Questo utilizza la direttiva Import-Package. I servizi implementati sono registrati all'interno del Service Registry;
  3. un bundle Client (o Consumer) che consuma i servizi definiti dalle API. Questo utilizza la direttiva Import-Package. I riferimenti ai servizi che saranno consumati sono resi disponibili grazie all'interazione con il Service Registry.
Figura 2 - OSGi Service Pattern
Figura 2 - OSGi Service Pattern

La figura a seguire mostra un esempio di applicazione modulare OSGi in esecuzione all'interno di un container OSGi Apache Karaf dov'è evidente la separazione nei tre bundle: API, Service (impl) e Consumer (client).

Figura 3 - Esempio di un'applicazione modulare OSGi che segue il pattern OSGi
Service Pattern
Figura 3 - Esempio di un'applicazione modulare OSGi che segue il pattern OSGi Service Pattern

Il Source Code 1 mostra invece il MANIFEST.MF contenuto all'interno del bundle delle API dov'è possibile vedere la direttiva Export-Package che esprime il package che sarà esportato (quindi visibile all'esterno) e la versione specifica.

Manifest-Version: 1.0
Bnd-LastModified: 1595983877560
Bundle-Description: Aries Remote Service Admin Examples - Raspberry Pi API
Bundle-ManifestVersion: 2
Bundle-Name: Aries Remote Service Admin Examples - Raspberry Pi API
Bundle-SymbolicName: it.smc.techblog.apache.aries.rsa.examples.raspberrypi.api
Bundle-Version: 1.0.0.202007290051
Created-By: 1.8.0_181 (Oracle Corporation)
Export-Package: it.smc.techblog.apache.aries.rsa.examples.raspberrypi.api;version="1.0.0"
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
Tool: Bnd-4.2.0.201903051501
Source Code 1 - Manifest del bundle API con evidenza della direttiva Export-Package

I tre sorgenti Java a seguire, mostrano rispettivamente:

  1. la definizione dell'interfaccia (bundle API);
  2. l'implementazione del servizio la cui interfaccia è stata definita dal bundle API (bundle Impl o Service);
  3. uso o consumo del servizio (bundle Client o Consumer).
package it.smc.techblog.apache.aries.rsa.examples.whoiam.api;

/**
 * @author Antonio Musarra
 */
public interface WhoIamService {
    public String whoiam();
}
Source Code 2 - Definizione dell'interfaccia per il servizio WhoIamService
package it.smc.techblog.apache.aries.rsa.examples.whoiam.service;

import it.smc.techblog.apache.aries.rsa.examples.whoiam.api.WhoIamService;
...

/**
 * @author Antonio Musarra
 */
@Component(immediate=true)
public class WhoIamServiceImpl implements WhoIamService {

  @Override
  public String whoiam() {
    Bundle bundle = _bc.getBundle();

    String hostAddress = "NA";

    try {
      hostAddress = InetAddress.getLocalHost().getHostAddress();
    }
    catch (UnknownHostException uhe) {
      _log.log(_log.LOG_ERROR, uhe.getMessage());
    }

    String response =
      "Who I am : {Bundle Id: %s, Bundle Name: %s, Bundle Version: %s, Framework Id: %s, HostAddress: %s}";

    return String.format(
      response, bundle.getBundleId(), bundle.getHeaders().get(
        Constants.BUNDLE_NAME), bundle.getVersion(),
      _bc.getProperty(Constants.FRAMEWORK_UUID), hostAddress
    );
  }

  @Activate
  protected void activate(BundleContext bundleContext) {
    _bc = bundleContext;
  }

  private BundleContext _bc;

  @Reference
  private LogService _log;
}
Source Code 3 - Implementazione dell'interfaccia WhoIamService
package it.smc.techblog.apache.aries.rsa.examples.whoiam.consumer;

import it.smc.techblog.apache.aries.rsa.examples.whoiam.api.WhoIamService;
...

/**
 * @author Antonio Musarra
 */
@Component(immediate=true)
public class WhoIamConsumer {

    WhoIamService _whoIamService;

    @Activate
    public void activate() {
        System.out.println("Sending to Who I am service");
        System.out.println(_whoIamService.whoiam());
    }

    @Reference
    public void setWhoIamService(WhoIamService whoIamService) {
        this._whoIamService = whoIamService;
    }
}
Source Code 4 - Uso del servizio WhoIamService

I µServices che abbiamo visto fino a questo momento, in maniera molto sintetica e direi anche velocemente, sono in-VM, ovvero, l'intero processo funziona all'interno di una singola JVM con un sovraccarico praticamente prossimo allo zero. Non è richiesto nessun proxy, quindi alla fine una chiamata a servizio è solo una chiamata diretta al metodo. In questo modello, una buona pratica impone di far fare una cosa sola al servizio implementato, in questo modo i servizi sono facili da sostituire e facili da riutilizzare.

Adesso è tempo della fatidica domanda. È possibile distribuire i µServices su più container OSGi mantenendo lo stesso pattern (vedi Figura 2)? Certamente! Il come lo vedremo più avanti.

2. Microservices visti in forma OSGi

Si può trovare una diversa definizione di Microservices, ma tutti riconducono al concetto di uno stile architetturale che favorisca lo sviluppo di sistemi loose-coupling dove quindi i componenti abbiamo un basso livello di accoppiamento, siano distribuibili in modo indipendente, siano in grado di comunicare attraverso protocolli leggeri e siano testabili e scalabili in modo indipendente.

Ciò implica che i Microservices abbraccino la modularità, in tal senso OSGi è il framework orientato fin dalla nascita ai µServices in VM. Per essere più precisi, la tabella a seguire mostra come i diversi concetti espressi dai Microservices sono associati al mondo OSGi.

CapacitàFunzionalità OSGi abilitante
Configuration ManagementOSGi Config Admin definito nelle specifiche OSGi Compendium
Service DiscoveryOSGi Service Registry definito nelle specifiche OSGi Core
Dynamic RoutingIl routing dinamico tra servizi (in moduli potenzialmente diversi) può essere stabilito mediante filtri OSGi che consentono il recupero di un riferimento di servizio utilizzando una sintassi simile a LDAP (RFC-4515) basata sulle proprietà del servizio
API InterfaceLa specifica OSGi Remote Services definisce un meccanismo per l'esposizione dei servizi OSGi al mondo esterno (fornendo effettivamente un meccanismo per la definizione di un'API pubblica del modulo)
SecurityAlcune funzionalità (come il controllo delle autorizzazioni) sono fornite da Permission Admin e Conditional Permission Admin definiti dalla specifica OSGi. Ulteriori funzionalità di sicurezza sono fornite da altre specifiche come per esempio la User Admin Service
Centralized LoggingPuò essere ottenuto tramite le specifiche del Log Service di OSGi
PackagingI bundle OSGi sono distribuiti come artefatti JAR, pronti quindi anche per il deployment in ambienti che prevedono l'uso di container
DeploymentSia dalla console OSGi, dal file system (hot deploy) o tramite uno strumento/API dedicato (specifico per il runtime OSGi)
Tabella 1 - Associazione tra capacità dei Microservices è funzionalità offerte dal framework OSGi

Spostandoci fuori dai confini dell'ambiente OSGi, entrano in gioco delle sfide interessanti che includono ovviamente considerazioni circa il bilanciamento del carico (load balancing), ridimensionamento automatico (auto-scaling), resilienza, tolleranza ai guasti (fault tolerance) e back-pressure per citarne alcuni.

Tutti questi aspetti possono essere abilitati tramite le specifiche Remote Services e Remote Service Admin definite dalla specifica OSGi Compendium.

In particolare le specifiche Remote Service Admin definiscono un agente di gestione plugable chiamato Topology Manager in grado di soddisfare i diversi concetti menzionati che caratterizzano l'interazione tra i servizi OSGi remoti (effettivamente il canale di comunicazione tra i diversi container OSGi che formano l'insieme dei Microservices).

I framework più noti che forniscono un'implementazione delle due specifiche OSGi che riguardano i Remote Services, sono:

  1. Apache CXF Distributed OSGi che consente di:
    1. offrire e consumare servizi SOAP e basati su REST. Inoltre, l'uso dei Declarative Services è il modo più semplice per interagire con tali servizi;
    2. comunicare in modo trasparente tra i container OSGi.
  2. Apache Aries RSA (Remote Service Admin);
  3. Eclipse Communication Framework (ECF)

Nel corso di quest'articolo faremo riferimento all'implementazione Apache Aries RSA (Remote Service Admin), perché a mio avviso è quella più semplice e flessibile da usare, nonché quella più diffusa.

Dopo aver visto quali sono le specifiche che ci abilitano a distribuire i bundle OSGi e quindi i µServices ad uscire fuori dai confini del proprio container, è arrivato il momento di presentare un possibile scenario scendendo sul molto pratico.

3. Uno scenario d'esempio di µServices Remoti

Ipotizziamo uno scenario in cui abbiamo quattro diversi container OSGi, di cui due offrono esclusivamente servizi, assumono quindi il ruolo di service provider, uno offre servizi e assume anche il ruolo di client o consumer e per finire, un ultimo che ha esclusivamente il ruolo di consumer. Su due dei tre container OSGi che agiranno da service provider, installeremo lo stesso servizio.

Volendo fare quindi la lista della spesa, dobbiamo sviluppare due classici progetti in stile OSGi.

  • Uno che contenga tre bundle: API, Service e Consumer per il servizio che chiameremo Who I am.
  • Uno che contenga tre bundle: API, Service e Consumer per il servizio che chiameremo Raspberry Pi.

La tabella a seguire mostra il dettaglio per servizio e di quali saranno i bundle OSGi da sviluppare.

ServizioBundle NameDescrizione
Who I am ServiceWhoIam APIBundle che definisce tramite l'interfaccia Java il contratto del servizio Who I am. Il bundle esporta il package dell'interfaccia (vedi Source Code 2).
WhoIam ServiceBundle che implementa l'interfaccia del servizio Who I am. Il bundle importa il package dell'interfaccia (vedi Source Code 3).
WhoIam ConsumerBundle che consuma il servizio Who I am. Il riferimento al servizio è ottenuto in modo trasparente dal Service Registry. Il bundle importa il package dell'interfaccia (vedi Source Code 4).
Raspberry Pi ServiceRaspberry Pi APIBundle che definisce tramite l'interfaccia Java il contratto del servizio Raspberry Pi. Il bundle esporta il package dell'interfaccia.
Raspberry Pi ServiceBundle che implementa l'interfaccia del servizio Raspberry Pi. Il bundle importa il package dell'interfaccia.
Raspberry Pi ConsumerBundle che consuma il servizio Raspberry Pi. Il riferimento al servizio è ottenuto in modo trasparente dal Service Registry. Il bundle importa il package dell'interfaccia.
Tabella 2 - Bundle OSGi che implementeranno lo scenario di esempio per µServices Remoti

Il diagramma a seguire mostra lo scenario che andremo a implementare utilizzando un'architettura distribuita a µServices restando nel nostro contesto OSGi. Per coloro che sono ormai avvezzi a sviluppare utilizzando il framework OSGi, apprezzeranno il fatto che alla fine non cambierà nulla nel modo di scrivere codice.

Dal diagramma è possibile notare che abbiamo a disposizione quattro OSGi Container di cui, uno è all'interno di un'istanza di Liferay Portal Platform e il resto dei tre sono tutte istanze di Apache Karaf. Al fine di rendere le cose più interessanti, ho previsto volutamente per lo scenario l'introduzione di Liferay e di un'istanza Apache Karaf in esecuzione sul Raspberry Pi.

Figura 4 - Scenario d'esempio di µServices Remoti
Figura 4 - Scenario d'esempio di µServices Remoti

Dal diagramma è evidente anche la distribuzione dei bundle (indicati in Tabella 2) sui quattro container OSGi. Su Liferay Portal abbiamo i bundle che consentiranno di consumare il servizio Who I am e Raspberry Pi. Su due container Apache Karaf abbiamo i bundle che consentiranno di fornire il servizio Who I am, e su uno dei due container i bundle che consentiranno di consumare il servizio Raspberry Pi. Sul terzo ed ultimo container Apache Karaf abbiamo i bundle che forniranno il servizio Raspberry Pi.

Adesso che abbiamo chiaro in mente quale sarà il nostro scenario, dobbiamo capire come abilitare i container OSGi che abbiamo a disposizione affinché siano in grado di comunicare tra loro.

4. Apache Aries RSA (Remote Service Admin)

La specifica OSGi R7 Compendium ha due sezioni relative agli OSGi Remote Services: 100 Remote Services (RS) e 122 Remote Service Admin Service Specification (RSA). Queste specifiche, tuttavia, sono destinate principalmente a chi implementa RS/RSA, piuttosto che agli sviluppatori.

Nel corso dell'articolo ho fatto sempre riferimento alla release R7 delle specifiche OSGi, vorrei però ricordare che le specifiche per gli OSGi Remote Services sono state introdotte in OSGi Service Platform Enterprise Specification, Release 4, Version 4.2.

Ricapitolando. I servizi OSGi sono semplicemente oggetti Java che espongono un certo numero di interfacce Java. Le istanze sono registrate dinamicamente in base al nome dell'interfaccia insieme alle proprietà tramite il Service Registry. Come descritto nelle specifiche, i servizi OSGi presentano numerosi vantaggi, tra cui un supporto per la dinamica, la sicurezza, una netta separazione tra contratto di servizio e implementazione, Semantic Versioning e altri. Esistono tre parti in un'istanza del servizio OSGi:

  1. una o più interfacce di servizio (interfaccia java);
  2. un'implementazione di interfacce di servizio;
  3. un consumatore del servizio (tramite l'interfaccia di servizio).

OSGi Remote Services sta semplicemente estendendo il Service Registry per consentire l'accesso ai servizi OSGi da fuori processo ... ovvero per l'accesso remoto.

In che cosa differisce dai servizi remoti non OSGi come HTTP/REST? In primo luogo, i servizi remoti OSGi hanno accesso a tutte le proprietà dei servizi OSGi ... ad esempio dinamica, sicurezza, controllo delle versioni, separazione dei contratti dall'implementazione, etc., nonché pieno supporto per i servizi dichiarativi o Declarative Services e altri injection framework.

Un Distribution Provider può sfruttare il loose-coupling tra i bundle per esportare un servizio registrato creando un endpoint. Viceversa, il Distribution Provider può creare un proxy che accede a un endpoint e quindi registra questo proxy come servizio importato. Un framework può contenere contemporaneamente più Distribution Provider, ciascuno dei quali importa ed esporta in modo indipendente.

Un endpoint è un meccanismo di accesso alle comunicazioni verso un servizio in un altro framework, un servizio (web), un altro processo o una coda, etc., che richiede un protocollo per le comunicazioni. La mappatura tra servizi ed endpoint nonché le loro caratteristiche di comunicazione è chiamata topologia.

Figura 5 - Architettura dei Remote Services
Figura 5 - Architettura dei Remote Services

Il progetto Apache Aries Remote Service Admin (RSA) consente di utilizzare in modo trasparente i servizi OSGi per la comunicazione remota. I servizi OSGi possono essere contrassegnati per l'esportazione aggiungendo una proprietà del servizio service.exported.interfaces=*. Varie altre proprietà possono essere utilizzate per personalizzare il modo in cui il servizio deve essere esposto.

La figura a seguire mostra l'architettura di Apache Aries Remote Service Admin (RSA) che se notate riporta i componenti descritti dalle specifiche OSGi Remote Service (RS e RSA). L' Endpoint descrive un servizio utilizzando le interfacce di servizio, l'URL e tutte le altre proprietà necessarie per importare correttamente il servizio remoto. L'Endpoint Listener è un servizio che deve essere avvisato quando ci sono cambiamenti sugli endpoint remoti (descritti dai filtri OSGi) sono registrati o de-registrati.

Figura 6 - Architettura di Apache Aries Remote Service Admin (RSA)
Figura 6 - Architettura di Apache Aries Remote Service Admin (RSA)

Il Topology Manager per impostazione predefinita espone tutti i servizi locali che sono opportunamente contrassegnati per l'esportazione e importa tutti i servizi interessati con gli endpoint remoti corrispondenti. Il Topology Manager può anche aggiungere proprietà con lo scopo di modificare il modo in cui i servizi sono esposti. Per i servizi da esporre chiama il Remote Service Admin, poi quest'ultimo esegue l'esportazione effettiva, successivamente notifica agli endpoint listener il nuovo endpoint. Il Topology Manager ascolta le richieste di servizio da parte dei consumer e crea gli endpoint listener.

Il Topology Manager è il posto migliore per implementare le regole di governance al livello di sistema. Alcuni esempi di cosa si può fare:

  1. proteggere gli endpoint remoti con SSL/TLS, servizi di autenticazione e audit;
  2. esportazione dei servizi OSGi con annotazioni per JAX-WS e JAX-RS anche se non appositamente contrassegnati per l'esportazione.

Per il ruolo del Topology Manager, questo non implementa direttamente queste caratteristiche ma crea tutte le chiamate necessarie a un Remote Service Admin.

Il Remote Service Admin viene chiamato dal Topology Manager per esporre i servizi locali come endpoint remoti e creare servizi proxy locali come client per endpoint remoti. Apache RSA ha un SPI (Service Provider Interface), il Distribution Provider che consente di creare facilmente nuovi meccanismi di trasporto e di serializzazione affinché i servizi OSGi siano disponibili al di fuori del container. Il diagramma a seguire mostra appunto il ruolo del Distribution Provider.

Figura 7 - Ruolo del Distribution Provider
Figura 7 - Ruolo del Distribution Provider

Gli attuali provider supportati da Apache Aries sono:

  • Apache CXF Distributed OSGi
    • utilizza Apache CXF per il trasporto;
    • gli endpoint di servizio possono essere consumati anche da software != da Java grazie al supporto degli standard JAX-WS e JAX-WS.
  • TCP
    • serializzazione Java su TCP (una porta per servizio);
    • pochissime dipendenze;
    • semplice per implementare il proprio meccanismo di trasporto.
  • Fastbin
    • serializzazione Java Protocol Buffers (o Protobuf) ottimizzata su TCP tramite NIO (Non-blocking I/O);
    • multiplexing su un'unica porta;
    • gestisce in modo trasparente InputStreams e OutStreams nei servizi remoti;
    • chiamate sincrone e asincrone supportate.

Il Discovery utilizza gli endpoint listener per ascoltare gli endpoint locali e li pubblica per gli altri container OSGi. Ascolta anche gli endpoint remoti e notifica agli endpoint listener la loro presenza. Le attuali implementazioni supportate da Apache RSA sono:

  • Local Discovery mediante l'uso di descrittori XML;
  • Discovery basato di Apache Zookeeper.

Per l'implementazione del nostro scenario (mostrato in Figura 4) utilizzeremo il Distribution Provider TCP e Apache ZooKeeper per il Discovery. Ci siamo!

5. Implementazione dello scenario µServices Remoti

Mi rendo conto che per alcuni di voi arrivare fino a qui sia stata dura, ma ci siamo, è arrivata la parte davvero molto ma molto pratica.

Dallo schema mostrato in Figura 4 qualcuno di voi avrà immaginato lo scenario di deployment, ebbene si: container Docker e SBC (Single Board Computer) Raspberry Pi.

5.1 Configurazione ambiente di deployment

Al fine di favorire tutti voi per replicare lo scenario di Figura 4, non c'è niente di meglio che mettere su i servizi necessari utilizzando docker compose. Tramite il docker compose andremo a configurare i seguenti servizi.

  1. Due servizi Apache Karaf (versione 4.2.7)
  2. Un servizio Liferay Portal Community Edition (versione 7.3 GA4)
  3. Un servizio Apache ZooKeeper (versione 3.6.1)

A seguire è mostrato il contenuto del file docker-compose.yaml al cui interno sono definiti i dettagli di configurazione dei servizi di cui sopra. Tutte le immagini dei servizi definiti, sono disponibili sugli account ufficiali Docker Hub dei rispettivi proprietari.

# Docker Compose file for OSGi µServices demo
version: "3.3"
services:
  zookeeper-instance:
    image: zookeeper:3.6.1
    restart: always
    hostname: zookeeper-instance
    ports:
      - 2181:2181
      - 9080:8080
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "wget http://zookeeper-instance:8080/commands && echo 'OK'",
        ]
      interval: 5s
      timeout: 2s
      retries: 3
  karaf-instance-1:
    image: apache/karaf:4.2.7
    command:
      [
        "sh",
        "-c",
        "cp /opt/apache-karaf/deploy/*.cfg /opt/apache-karaf/etc/; karaf run
        clean",
      ]
    volumes:
      - ./karaf-instance-1/deploy:/opt/apache-karaf/deploy
    ports:
      - 8101-8105:8101    
    depends_on:
      - zookeeper-instance
  karaf-instance-2:
    image: apache/karaf:4.2.7
    command:
      [
        "sh",
        "-c",
        "cp /opt/apache-karaf/deploy/*.cfg /opt/apache-karaf/etc/; karaf run
        clean",
      ]
    volumes:
      - ./karaf-instance-1/deploy:/opt/apache-karaf/deploy
    ports:
      - 8106-8110:8101    
    depends_on:
      - zookeeper-instance
  liferay-instance-1:
    image: liferay/portal:7.3.3-ga4
    volumes:
      - ./liferay-instance-1/deploy:/etc/liferay/mount/deploy
      - ./liferay-instance-1/files:/etc/liferay/mount/files
    ports:
      - 6080:8080
      - 21311:11311
      - 9201:9201
    depends_on:
      - zookeeper-instance
Source Code 5 - Docker Compose file per la definizione dei servizi necessari all'implementazione dello scenario µServices Remoti

Il contenuto del docker-compose.yml dovrebbe essere abbastanza chiaro. Un'unica nota che vorrei fare riguarda il command del servizio Apache Karaf che ho voluto sovrascrive (vedi CMD) per due motivi.

  1. Aggiornare la configurazione dei repository Maven. In particolare il cambio di protocollo da http a https, questo per evitare l'errore HTTP/501 in caso di aggiornamento e/o installazione di nuove Apache Karaf Feature.
  2. Aggiornare la configurazione di Apache Karaf per installare di default le features di Apache Aries RSA. In questo modo evitiamo di doverlo fare successivamente dalla console.
  3. Avviare Apache Karaf con il parametro clean Cleaning the Karaf state.

In Console 1 è mostrata la struttura del progetto che consente di tirar su i servizi via docker compose. Ogni servizio ha la propria directory all'interno della quale sono poi contenute ulteriori directory (di deploy e configurazione) e file.

La directory deploy che fa riferimento al servizio Apache Karaf, contiene il file di configurazione per Apache ZooKeeper, Apache Karaf e i file JAR dei bundle che implementano il servizio Who I am (vedi Tabella 2).

La directory deploy che fa riferimento al servizio Liferay Portal, è la directory di hot-deploy dei bundle. Su questa directory andranno eventualmente posizionati i file JAR dei bundle API e Consumer dei servizi Who I am e Raspberry Pi.

La directory files/osgi/configs che fa riferimento al servizio Liferay Portal, contiene il file di configurazione per Apache ZooKeeper.

La directory files/osgi/modules che fa riferimento al servizio Liferay Portal contiene tutti bundle necessari per installare sul container OSGi di Liferay il supporto per Apache Aries RSA. Inoltre, all'interno della stessa directory, sono presenti i JAR dei bundle API e Consumer dei servizi Who I am e Raspberry Pi.

├── docker-compose.yml
├── karaf-instance-1
│   └── deploy
├── karaf-instance-2
│   └── deploy
└── liferay-instance-1
    ├── deploy
    └── files
        └── osgi
            ├── configs
            └── modules
Console 1 - Struttura del progetto docker-osgi-remote-services-example

5.2 Setup di Apache Aries RSA per i container OSGi

Prima di poter esporre remotamente in nostri µServices, è necessario che sia installato Apache Aries RSA su ognuno dei container OSGi, Liferay compreso.

Per fare quest'operazione ho preferito agire direttamente a livello di docker compose, in questo modo, una volta tirati su i servizi sarà tutto pronto per assaporare i µServices remoti in azione.

Ovviamente è possibile installare successivamente Apache Aries RSA, ma per quanto possibile cerchiamo sempre di sfruttare al massimo gli strumenti a nostra disposizione.

Lato Apache Karaf, l'installazione di Apache Aries RSA è davvero semplice, bastano i seguenti file di configurazione, già inclusi nel progetto docker compose (vedi directory karaf-instance-1/deploy).

  • org.apache.karaf.features.cfg. Tramite questo file di configurazione è stato aggiunto l'indirizzo della rsa-features e indicato quali bundle installare in fase di start-up di Apache Karaf.
  • org.apache.aries.rsa.discovery.zookeeper.cfg. Tramite questo file è stato configurato l'indirizzo e la porta del servizio di Discovery, che in questo caso è rappresentato da Apache ZooKeeper.

A seguire parte del contenuto del file di configurazione delle feature Apacke Karaf di nostro interesse. In particolare fare riferimento alla riga 9 e dalla riga 34 alla riga 36. In questo modo Apache Karaf è istruito affinché alla partenza installi i bundle SCR (Service Component Runtime), aries-rsa-provider-tcp e aries-rsa-discovery-zookeeper.

#
# Comma separated list of features repositories to register by default
#
featuresRepositories = \
    mvn:org.apache.karaf.features/standard/4.2.7/xml/features, \
    mvn:org.apache.karaf.features/spring/4.2.7/xml/features, \
    mvn:org.apache.karaf.features/enterprise/4.2.7/xml/features, \
    mvn:org.apache.karaf.features/framework/4.2.7/xml/features, \
    mvn:org.apache.aries.rsa/rsa-features/1.14.0/xml/features

#
# Defines if the boot features are started in asynchronous mode (in a dedicated thread)
#

featuresBoot = \
    instance/4.2.7, \
    package/4.2.7, \
    log/4.2.7, \
    ssh/4.2.7, \
    framework/4.2.7, \
    system/4.2.7, \
    eventadmin/4.2.7, \
    feature/4.2.7, \
    shell/4.2.7, \
    management/4.2.7, \
    service/4.2.7, \
    jaas/4.2.7, \
    deployer/4.2.7, \
    diagnostic/4.2.7, \
    wrap/2.6.1, \
    bundle/4.2.7, \
    config/4.2.7, \
    kar/4.2.7, \
    scr/4.2.7, \
    aries-rsa-provider-tcp/1.14.0, \
    aries-rsa-discovery-zookeeper/1.14.0
Source Code 6 - File di configurazione org.apache.karaf.features.cfg

Tramite le due property mostrate a seguire, sono impostati l'hostname e la porta del servizio di Apache ZooKeeper (vedi definizione del servizio zookeeper-instance sul file del docker compose).

zookeeper.host=zookeeper-instance
zookeeper.port=2181
Source Code 7 - File di configurazione org.apache.aries.rsa.discovery.zookeeper.cfg

Lato Liferay Portal, installare il supporto per Apache Aries RSA è un pochino più noioso. Quello che bisogna fare è scaricare i bundle di Apache Aries RSA e Apache ZooKeeper dal repository Maven e dal sito di Apache ZooKeeper, successivamente metterli all'interno della directory di hot-deploy ($LIFERAY_HOME/deploy) per l'installazione.

Il file di configurazione org.apache.aries.rsa.discovery.zookeeper.cfg per il servizio di Discovery va invece posizionato all'interno della directory $LIFERAY_HOME/osgi/configs.

Grazie al docker compose non dovrete fare nulla di tutto ciò, i servizi partiranno già configurati. Quella mostrata a seguire è la struttura del progetto docker compose, che consente di tirar su tutti i container OSGi già configurati e pronti all'uso, con installati anche i bundle OSGi dei progetti Who I am e Raspberry Pi distribuiti secondo il diagramma di Figura 4.

├── docker-compose.yml
├── karaf-instance-1
│   └── deploy
│       ├── it.smc.techblog.apache.aries.rsa.examples.whoiam.api-1.0.0-SNAPSHOT.jar
│       ├── it.smc.techblog.apache.aries.rsa.examples.whoiam.service-1.0.0-SNAPSHOT.jar
│       ├── org.apache.aries.rsa.discovery.zookeeper.cfg
│       ├── org.apache.karaf.features.cfg
│       └── org.ops4j.pax.url.mvn.cfg
├── karaf-instance-2
│   └── deploy
│       ├── it.smc.techblog.apache.aries.rsa.examples.whoiam.api-1.0.0-SNAPSHOT.jar
│       ├── it.smc.techblog.apache.aries.rsa.examples.whoiam.service-1.0.0-SNAPSHOT.jar
│       ├── it.smc.techblog.apache.aries.rsa.examples.raspberrypi.api-1.0.0-SNAPSHOT.jar
│       ├── it.smc.techblog.apache.aries.rsa.examples.raspberrypi.consumer-1.0.0-SNAPSHOT.jar
│       ├── org.apache.aries.rsa.discovery.zookeeper.cfg
│       ├── org.apache.karaf.features.cfg
│       └── org.ops4j.pax.url.mvn.cfg
└── liferay-instance-1
    ├── deploy
    └── files
        └── osgi
            ├── configs
            │   └── org.apache.aries.rsa.discovery.zookeeper.cfg
            └── modules
                ├── it.smc.techblog.apache.aries.rsa.examples.raspberrypi.api-1.0.0-SNAPSHOT.jar
                ├── it.smc.techblog.apache.aries.rsa.examples.raspberrypi.consumer-1.0.0-SNAPSHOT.jar
                ├── it.smc.techblog.apache.aries.rsa.examples.whoiam.api-1.0.0-SNAPSHOT.jar
                ├── it.smc.techblog.apache.aries.rsa.examples.whoiam.consumer-1.0.0-SNAPSHOT.jar
                ├── jansi-1.18.jar
                ├── org.apache.aries.rsa.core-1.14.0.jar
                ├── org.apache.aries.rsa.discovery.command-1.14.0.jar
                ├── org.apache.aries.rsa.discovery.config-1.14.0.jar
                ├── org.apache.aries.rsa.discovery.local-1.14.0.jar
                ├── org.apache.aries.rsa.discovery.zookeeper-1.14.0.jar
                ├── org.apache.aries.rsa.eapub-1.14.0.jar
                ├── org.apache.aries.rsa.examples.echotcp.api-1.14.0.jar
                ├── org.apache.aries.rsa.examples.echotcp.consumer-1.14.0.jar
                ├── org.apache.aries.rsa.provider.tcp-1.14.0.jar
                ├── org.apache.aries.rsa.spi-1.14.0.jar
                ├── org.apache.aries.rsa.topology-manager-1.14.0.jar
                ├── org.osgi.service.remoteserviceadmin-1.1.0.jar
                └── zookeeper-3.4.14.jar
Console 2 - Struttura completa del progetto docker-osgi-remote-services-example

Per quanto riguarda invece il container OSGi Apache Karaf per il servizio Raspberry Pi, dovreste disporre un Raspberry Pi (almeno 3 Model B+), installare Apache Karaf e seguire le attività di configurazione mostrate in precedenza. L'attività d'installazione e configurazione di Apache Karaf su Raspberry Pi è davvero semplice; consiglio di leggere la guida ufficiale.

Le due figure a seguire mostrano Apache Karaf in esecuzione sul mio Raspberry Pi 3 Model B+ e la lista dei bundle di Apache Aries RSA e Apache ZooKeeper.

Figura 8 - Apache Karaf in esecuzione su Raspberry Pi 3 Model B+
Figura 8 - Apache Karaf in esecuzione su Raspberry Pi 3 Model B+
Figura 9 - Bundle di Apache Aries (RSA) e Apache ZooKeeper installati sul
Raspberry Pi
Figura 9 - Bundle di Apache Aries (RSA) e Apache ZooKeeper installati sul Raspberry Pi

5.3 Start-up della soluzione via Docker Compose

Fino a questo momento credo che abbiamo visto tante belle cose interessanti. Adesso è giunta l'ora di avviare la nostra soluzione via docker compose. Gli step da fare sono veramente pochi e semplici (così come mostrato in Console 3).

Prima di avviare i servizi ed evitare d'incorrere a "strani errori", dovreste accertarvi che le risorse dedicate a Docker siano adeguate. Per questi servizi consiglio di dedicare almeno 2 CPU e 6 Gbyte di memoria.

Per quanto riguarda la versione di Docker, quella minima richiesta è la 18. Personalmente il mio ambiente è Docker Desktop con Engine versione 19.03.12 e Compose versione 1.26.2.

$ git clone https://github.com/smclab/docker-osgi-remote-services-example.git
$ cd docker-osgi-remote-services-example
$ docker-compose up
Console 3 - Comandi per il clone del repository docker-osgi-remote-services-example e start-up della soluzione

La prima volta consiglio di lanciare il docker compose non in modalità detached, così da seguire meglio lo start-up di tutta la soluzione. Per la maggior parte di voi, il primo start-up impiegherà parecchi minuti, spesi per il download delle immagini da Docker Hub.

Per accertarci che la soluzione sia salita senza alcun problema, potremmo utilizzare il comando docker-compose ps e dovremmo ottenere un risultato come quello mostrato dalla Figura 10 a seguire. In questo caso l'output del comando informa che tutti in container sono up e running.

La figura a seguire evidenzia in particolare le porte TCP delle console di amministrazione dei container OSGi, sia Liferay sia Apache Karaf. È evidenziata anche la porta TCP esportata per il servizio di Apache ZooKeeper.

La porta TCP per la Gogo Shell di Liferay è la 21311 per le rispettive istanze di Apache Karaf le porte TCP sono: 8104 e 8109.

La porta 2181 esportata per il servizio di Apache ZooKeeper è quella che sarà utilizza dai container OSGi per registrare le informazioni sui propri servizi.

Dal Docker Compose file (vedi Source Code 5), le porte di amministrazione esportate per le due istanze di Apache Karaf, sono impostate su base range, non è quindi assicurato che il numero di porta sia sempre lo stesso, potrebbe cambiare ad ogni start del servizio.

Console Session 1 - Start-up della soluzione via Docker Compose

Cliccando su Console Sessione 1, potete vedere la sessione di terminale che fa riferimento ai comandi mostrati in Console 3 per lo start-up del Docker Compose.

Figura 10 - Esecuzione del comando docker-compose ps per verificare lo stato di
container dei servizi.
Figura 10 - Esecuzione del comando docker-compose ps per verificare lo stato di container dei servizi.

Facciamo ulteriori verifiche con lo scopo di accertare che Apache Aries RSA e Apache ZooKeeper più i bundle dei nostri servizi siano stati correttamente installati. Per fare questa operazione e sufficiente collegarsi alla Gogo Shell di Liferay e utilizzare il comando ls per verificare che siano presenti i rispettivi bundle e che siano nello stato Active. La stessa attività può essere condotta sulle istanze di Apache Karaf, collegandosi alla console e utilizzare il comando list per verificare che siano presenti i rispettivi bundle e che siano anch'essi nello stato Active.

Le due figure a seguire mostrano l'output dei comandi ls e list per la Gogo Shell (Liferay) e Apache Karaf. Per la connessione alla console di Apache Karaf utilizzare il comando ssh -p 8104 karaf@127.0.0.1 Specificare la porta dell'istanza a cui volersi connettere (vedi Figura 10). La password per accedere alla console di Apache Karaf è: karaf.

Figura 11 - Output del comando lb da cui è possibile vedere i bundle di Apache
Aries (RSA), Apache ZooKeeper + i bundle dei servizi remoti sviluppati.
Figura 11 - Output del comando lb da cui è possibile vedere i bundle di Apache Aries (RSA), Apache ZooKeeper + i bundle dei servizi remoti sviluppati.
Figura 12 - Output del comando list su una delle istanze Apache Karaf da cui è
possibile vedere i bundle di Apache Aries (RSA), Apache ZooKeeper + i bundle dei
servizi remoti sviluppati.
Figura 12 - Output del comando list su una delle istanze Apache Karaf da cui è possibile vedere i bundle di Apache Aries (RSA), Apache ZooKeeper + i bundle dei servizi remoti sviluppati.

6. Come rendere i µServices Remoti

Tutto ciò che rende "magico" i µServices Remoti lo abbiamo visto nel dettaglio nel capitolo 4, dov'è stata introdotta la specifica OSGi Remote Services e come Apache Aries RSA renda possibile l'esportazione delle interfacce dei servizi all'esterno del container OSGi all'interno del quale vivono.

Per chi sviluppa, cambia qualcosa? Assolutamente no, a maggior ragione per chi utilizza i Declarative Services (o DS).

Prendiamo in esame il servizio Who I am. Questo servizio è molto semplice, attraverso l'interfaccia it.smc.techblog.apache.aries.rsa.examples.whoiam.api.WhoIamService (vedi Source Code 2) è stato definito il metodo whoiam(). Questo metodo restituisce qualche informazione circa l'ambiente di runtime del servizio. L'implementazione di quest'interfaccia, per così come mostrata al Source Code 3, non sarebbe candidata per l'esportazione come servizio remoto. Per far sì che questo accada, è necessario aggiungere delle proprietà al nostro componente, così come indicato a seguire.

Tramite la proprietà service.exported.interfaces=* candidiamo il componente per essere esportato come servizio remoto, inoltre occorre specificare anche il numero di porta tramite la proprietà aries.rsa.port=. Quest'ultima configurazione è specifica per il tipo di trasporto scelto, in questo caso il TCP, così come avevo anticipato nel capitolo 4 parlando del Distribution Provider.

package it.smc.techblog.apache.aries.rsa.examples.whoiam.service;

import it.smc.techblog.apache.aries.rsa.examples.whoiam.api.WhoIamService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.log.LogService;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @author Antonio Musarra
 */
@Component(
  property = {
    "service.exported.interfaces=*",
    "aries.rsa.port=8202"
  })
public class WhoIamServiceImpl implements WhoIamService {

  @Override
  public String whoiam() {
    Bundle bundle = _bc.getBundle();

    String hostAddress = "NA";

    try {
      hostAddress = InetAddress.getLocalHost().getHostAddress();
    }
    catch (UnknownHostException uhe) {
      _log.log(_log.LOG_ERROR, uhe.getMessage());
    }

    String response =
      "Who I am : {Bundle Id: %s, Bundle Name: %s, Bundle Version: %s, Framework Id: %s, HostAddress: %s}";

    return String.format(
      response, bundle.getBundleId(), bundle.getHeaders().get(
        Constants.BUNDLE_NAME), bundle.getVersion(),
      _bc.getProperty(Constants.FRAMEWORK_UUID), hostAddress
    );
  }

  @Activate
  protected void activate(BundleContext bundleContext) {
    _bc = bundleContext;
  }

  private BundleContext _bc;

  @Reference
  private LogService _log;
}
Source Code 8 - Abilitazione del componente che implementa l'interfaccia Who I am per essere esportato come servizio remoto

Per noi sviluppatori la "fatica" è finita qui, non occorre fare nient'altro. Anche per il consumer non cambia nulla (vedi Source Code 4), basta inserire l'annotazione @Reference al membro dichiarato di tipo WhoIamService, come sempre fatto. Sarà poi cura del Topology Manager iniettare il riferimento corretto al servizio remoto, quello che vive all'interno dell'altro container OSGi.

Connettiamoci alla Gogo Shell di Liferay e vediamo cos'è cambiamo sotto rispetto al solito. Con l'installazione di Apache Aries (RSA) abbiamo a nostra disposizione il comando per poter interrogare il Remote Service Admin e capire quali endpoint sono stati creati. Il comando si chiama rsa:endpoints e l'output di questo eseguito sulla Gogo Shell di Liferay dovrebbe essere simile a quello mostrato a seguire.

Figura 13 - Output del comando rsa:endpoints
Figura 13 - Output del comando rsa:endpoints

Quello mostrato in figura corrisponde a quanto mi sarei aspettato, ed effettivamente i conti tornano; forse qualcuno potrebbe obiettare. Ci sono tre endpoint. perché tre endpoint se i servizi che abbiamo esportato sono due? È vero che i servizi esportati sono due ma il servizio Who I am è installato su due differenti container OSGi. Le informazioni che il comando restituisce sono:

  • id: identificativo del servizio esportato espresso in formato URI. Protocollo, indirizzo ip del servizio e porta;
  • interface: è il nome completo dell'interfaccia che il servizio implementa;
  • framework: è l'identificato del framework OSGi all'interno del quale il servizio vive;
  • comp name: è il nome del componente che corrisponde alla classe Java che implementa l'interfaccia e di conseguenza il servizio.

Notate che l'identificativo del framework è diverso per ognuno degli endpoint, corretto, perché sono servizi che vivono in tre container OSGi diversi.

Cliccando su Console Sessione 2, potete vedere la sessione di terminale dove si evidenzia il processo di registrazione del nuovo endpoint che riguarda il servizio del Raspberry Pi. Il nuovo endpoint è infatti registrato subito dopo l'avvio dell'istanza di Apache Karaf installata sul Raspberry Pi.

Console Session 2 - Controllo Apache Aries (RSA) Enpoint

Quante volte avete utilizzato il comando bundle o b per fare l'inspection dei bundle? Immagino parecchie volte, soprattutto quando c'era da capire perché le cose non funzionavano. Proviamo ad eseguire il comando b sul bundle del consumer del servizio Who I am e vediamo cosa presenterà l'output.

Prima di eseguire il comando per l'inspection del bundle, utilizziamo il comando lb it.smc.techblog per ottenere la lista dei nostri bundle per poi ricavare l'id del bundle di cui siamo interessati di fare l'inspection.

Figura 14 - Output del comando lb it.smc.techblog
Figura 14 - Output del comando lb it.smc.techblog

Una volta ottenuto l'id del bundle, eseguiamo il comando b 1180. L'output mostrato in figura mostra delle informazioni leggermente diverse rispetto al solito, ovvero, la sezione Services in use mostra il riferimento al servizio remoto, da cui possiamo vedere tutte le caratteristiche dell'endpoint remoto. Qui sono entrati in gioco il Topology Manager e il Remote Service Admin.

Figura 15 - Output comando b per l'inspection del bundle consumer del servizio
Who I am
Figura 15 - Output comando b per l'inspection del bundle consumer del servizio Who I am

In questo caso il binding è stato fatto con il servizio remoto che vive sul container OSGi che ha l'indirizzo 172.29.0.4.

Cosa dovrei vedere interrogando il framework OSGi tramite comando services it.smc.techblog.apache.aries.rsa.examples.whoiam.api.WhoIamService? Quello che otteniamo è l'informazione che abbiamo la disponibilità di due servizi remoti che implementano la stessa interfaccia (in questo caso WhoIamService) ma che solo uno di questi è utilizzato dal bundle del consumer.

Figura 16 - Output del comando services
it.smc.techblog.apache.aries.rsa.examples.whoiam.api.WhoIamService
Figura 16 - Output del comando services it.smc.techblog.apache.aries.rsa.examples.whoiam.api.WhoIamService

Cosa succederebbe nel caso in cui stoppassi il servizio WhoIamService tcp://172.29.0.4:8202 attualmente referenziato dal consumer (bundle 1180)? Si verrebbe a creare un disservizio? Assolutamente no. Quello che in breve accade è questo:

  1. stop del servizio sull'istanza karaf-instance-2_1;
  2. rimozione dell'endpoint tcp://172.29.0.4:8202 da ZooKeeper;
  3. il servizio di Discovery provvedere a notificare l'avvenuto cambiamento sull'endpoint rimosso;
  4. il Topology Manager e il Remote Service Admin provvedono affinché il riferimento richiesto dal consumer del servizio WhoIamService venga associato al servizio remoto dell'istanza karaf-instance-1_1.

La figura a seguire mostra la verifica degli endpoint sull'istanza karaf-instance-2_1, l'esecuzione dello stop per il bundle Who I am Service (bundle 12) e la successiva verifica degli endpoint. Dopo lo stop del bundle è evidente come sia stato rimosso l'endpoint tcp://172.29.0.4:8202.

Figura 17 - Stop del bundle 12 Who I am Service e verifica degli endpoint
Figura 17 - Stop del bundle 12 Who I am Service e verifica degli endpoint

Sull'istanza Liferay, se andassimo a verificare gli endpoint, vedremo che correttamente è stato rimosso anche qui l'endpoint tcp://172.29.0.4:8202 e eseguendo nuovamente il comando services, vedremo che il riferimento richiesto dal consumer verso il servizio Who I am, è stato correttamente cambiato verso l'endpoint tcp://172.29.0.3:8202.

Le due figure a seguire mostrano gli avvenuti cambiamenti lato Liferay, l'ultima in particolare mostra l'output della chiamata al metodo whoiam() del servizio remoto, dov'è evidente che il servizio remoto che ha risposto è quello che fa riferimento all'endpoint tcp://172.29.0.3:8202, in questo caso l'istanza karaf-instance-1_1.

Figura 18 - Verifica endpoint e service binding dopo lo stop del servizio remoto
Figura 18 - Verifica endpoint e service binding dopo lo stop del servizio remoto
Figura 19 - Evidenza della chiama al servizio remoto fatta dal consumer e la
risposta attesa dall'endpoint tcp://172.29.0.3:8202
Figura 19 - Evidenza della chiama al servizio remoto fatta dal consumer e la risposta attesa dall'endpoint tcp://172.29.0.3:8202

Anche se facessimo ripartire il bundle stoppato in precedenza, il consumer rimarrebbe agganciato all'attuale endpoint. Concludiamo questo capitolo con la figura a seguire che mostra l'output della chiamata al servizio remoto Raspberry Pi Service, chiamata eseguita dall'istanza Liferay.

Figura 20 - Chiama al servizio remoto Raspberry Pi Service che restituisce una
serie d'informazioni di sistema
Figura 20 - Chiama al servizio remoto Raspberry Pi Service che restituisce una serie d'informazioni di sistema

7. I progetti sull'account GitHub di SMC

Penso che arrivare fino a qui sia stata una bella fatica, vi lascio però un bel regalo. Tutto quello che avete avuto modo di vedere nel corso di quest'articolo è disponibile in forma di repository git. Questo progetto è organizzato in diversi repository e in particolare:

  • docker-osgi-remote-services-example: È il repository che contiene tutto il progetto Docker Compose che vi consentirà di tirar su la soluzione rappresentata dal diagramma di Figura 4 (a meno dell'istanza Apache Karaf su Raspberry Pi);
  • aries-rsa-whoiam-examples: Progetto Maven che contiene i bundle per il servizio Who I am Service (vedi Tabella 2);
  • aries-rsa-raspberrypi-examples: Progetto Maven che contiene i bundle per il servizio Raspberry Pi Service (vedi Tabella 2).

I repositori dei due progetti OSGi contengono quelli che possiamo definire bonus, ovvero, due OSGi command per interagire con il servizio Who I am e con il servizio Raspberry Pi. Vi ricordo che gli OSGi command sono dei veri e propri comandi che possiamo richiamare dalla console dei container OSGi.

Ogni repository ha al suo interno il README con le istruzioni basilari per l'uso.

8. Conclusioni

Per i temi che abbiamo trattato, questo articolo è una conseguenza dell'intervento che ho tenuto al Liferay Boot Camp Summer Edition 2020, dove in particolare ho discusso della piattaforma Liferay all'interno di contesti di Microservices.

Per quanto ho cercato di essere sintetico, con questo articolo ho voluto dimostrare come il framework OSGi con i suoi veri µServices sia in grado di favorire lo sviluppo di sistemi distribuiti leggeri, affidabili e puliti.

Mi rendo conto che sarebbe necessario più spazio, ma con quello che sono riuscito ad occupare spero di essere stato chiaro nell'esposizione e di aver suscitato la vostra curiosità sull'argomento.

Credo di poter affermare che in giro per la rete è difficile trovare articoli con questo livello di dettaglio e che hanno affrontato lo sviluppo di una soluzione pratica mettendo a vostra disposizione tutto il materiale.

Se siete arrivati fino a qui, immagino che abbiate anche accumulato una serie di domande che vi invito a lasciare in forma di commento all'articolo. Prossimamente saranno pubblicati altri articoli di approfondimento.

9. Risorse

A seguire lascio una serie di risorse che sono il punto di riferimento per i contenuti di quest'articolo.

scritto da
Antonio Musarra
Software Architect
In SMC ricopre il ruolo di Senior Software Architect e si occupa in genere di consulenze specialistiche su progetti che riguardano la piattaforma Liferay, curando in particolare gli aspetti d'integrazione con altri sistemi. Crede nella condivisione della conoscenza come mezzo per la crescita personale e per questo motivo, circa otto anni fa, ha creato un suo blog personale: Antonio Musarra's Blog www.dontesta.it. Con la voglia ancora di condividere, ha pubblicato su Amazon nel 2015 il suo primo eBook, Sviluppo Liferay con Maven, arrivando alla sua ultima pubblicazione del 2018 con l'eBook Liferay Portal Security Audit.

Potrebbero interessarti anche…