Quali sono gli strumenti e i pattern che Drupal mette a disposizione per sviluppare un CMS headless?

Prima un ripassino: Drupal è un software free e open-source scritto in PHP, nato agli inizi degli anni 2000 come progetto universitario dello studente belga Dries Buytaert. Nel corso del tempo si è evoluto da CMS fino a diventare un vero e proprio framework; infatti, oggi si parla frequentemente di Drupal come CMF - Content Management Framework - più che come CMS.

POTREBBE INTERESSARTI ANCHE: Perché scegliere Drupal per siti aziendali complessi

Il grosso salto in questo senso si è avuto durante il passaggio dalla versione 7, che presentava ancora un’architettura basata su codice proprio, alla versione 8, rilasciata nel novembre 2015, in cui sono stati integrati molti componenti Symfony direttamente all’interno del core di Drupal e il template engine di default per il frontend è diventato Twig. Le versioni 8 e 9, quelle ad oggi supportate, utilizzano Symfony 4, mentre per la 10, attualmente in sviluppo, è previsto l’utilizzo di Symfony 5 o, se compatibile con i relativi release schedule, direttamente Symfony 6. Tra i componenti utilizzati, abbiamo DependencyInjection, EventDispatcher, HttpFoundation, Routing, Serializer, Yaml. Queste sono le fondamenta sopra le quali Drupal costruisce a sua volta con i propri mattoni.

Fra le altre cose, dalla 8 Drupal ha anche iniziato a versionare il proprio codice seguendo il Semantic Versioning, tanto che la 9, l’attuale, è sostanzialmente l’ultima versione del branch 8 ripulita dal codice deprecato. Il passaggio per molti siti dalla 7 alla 8 è stato un vero e proprio cambio di paradigma; il passaggio dalla 8 alla 9 è sostanzialmente un upgrade del codice, solo un po’ più sostanzioso.

Tutte queste scelte hanno permesso a Drupal di entrare a far parte dell’ecosistema PHP moderno.

POTREBBE INTERESSARTI ANCHE: Drupal 9, perché e come fare l’upgrade

Drupal come CMS

Drupal è nato come CMS, e ha ancora tutte le caratteristiche che ci possiamo aspettare da un classico Content Management System, anche se più versatile e robusto di altre soluzioni disponibili sulla piazza. 

In primo luogo, in quanto CMS, Drupal ha un’interfaccia amministrativa. Da qui, è possibile sia aggiungere contenuti che, in base ai permessi assegnati agli utenti, configurare i tipi di entità stessi del sito web.

Le entità sono un concetto cardine di Drupal. Un’entità è un elemento che rappresenta una risorsa del CMS, e può essere di diversi tipi. I tipi principali sono i contenuti (e in quel caso le istanze si chiamano nodi), le tassonomie (ossia le categorie dei contenuti), i media per la gestione dei file e delle immagini, gli utenti, i blocchi (che sono le sezioni più o meno fisse all’interno delle pagine), e le viste (ossia le liste di elementi). Queste sono solo alcune delle entità disponibili di default, e inoltre è possibile crearne anche di personalizzate.

Sono poi presenti anche altre funzionalità tipiche di un CMS, come una robusta gestione dei menù, un sistema molto granulare di assegnazione dei permessi agli utenti, la possibilità di installare e sviluppare temi grafici, e una gestione delle traduzioni e del multilingua implementato direttamente nel core.

Infine, uno degli altri capisaldi di Drupal sono i moduli che permettono di estenderne le funzionalità. A volte sono molto piccoli o aggiungono funzionalità puntuali, come ad esempio il modulo per minificare il JavaScript; a volte sono invece enormi, come Drupal Commerce, che aggiunge completamente le funzionalità di e-commerce a Drupal. Alcuni sono già presenti nel core di Drupal, altri invece sono moduli contrib da richiedere con Composer.

Drupal come framework

Nel 2016 è stato dato il via a quella che ha preso il nome di API-first initiative. L’obiettivo era rendere Drupal un framework che potesse essere utilizzato con sicurezza anche come CMS headless. Nel corso dei mesi - e poi degli anni - questa iniziativa si è spostata sempre più nettamente verso l’integrazione di JSON:API all’interno del core, finalizzata a marzo del 2019 con la versione 8.7 di Drupal. Fra poco ne parleremo più estesamente.

Un altro aspetto che rende Drupal un ottimo framework di lavoro è il set di API interne che espone e che permettono di modificare tramite codice praticamente ogni aspetto di cui possiamo avere bisogno. Ci sono API per la gestione delle entità, per la gestione della cache e della configurazione, per la gestione dei form (probabilmente fra le più usate dagli sviluppatori), API per effettuare migrazioni di dati e per il rendering dei contenuti nei template… Insomma, se dobbiamo modificare qualcosa, quasi certamente c’è un’API che fa al caso nostro.

Infine, un ultimo aspetto degno di nota è la facilità di estensione e personalizzazione di Drupal. Abbiamo già accennato ai moduli, ma qui facciamo un passo in più. Dato che Drupal è basato su Symfony e la codebase è gestita tramite Composer, aggiungere nuovi componenti è semplice quanto fare un require del componente stesso. Drupal mette a disposizione molte funzionalità, ma se per qualche motivo avessimo necessità di estenderle ulteriormente, ad esempio integrando un componente che gestisca in maniera diversa il filesystem rispetto a quello di Drupal, è sufficiente aspettare il tempo di download, e il meccanismo di autoloading si occuperà di renderlo disponibile al resto della codebase come se fosse stato sempre lì.

Dopo questa carrellata introduttiva, partiamo adesso con il cuore del nostro discorso: come usare Drupal come base di dati per un CMS headless.

POTREBBE INTERESSARTI ANCHE: Qual è il miglior Headless CMS? I vantaggi di Drupal

L'ecosistema JSON:API

Drupal mette a disposizione diverse possibilità. Qui ne vedremo una in particolare, più completa e supportata rispetto alle altre, e che soprattutto permette di raggiungere il nostro scopo senza aggiungere codice custom, ma solo tramite l’utilizzo di moduli core o contrib appositamente configurati. Questo approccio ruota intorno al modulo core JSON:API.

JSON:API è una specifica che definisce uno standard per l’esposizione e la gestione di dati in formato JSON tramite HTTP. La sua prima stesura risale al maggio 2013, e oggi è alla versione 1.0. Ha un proprio MIME media type registrato, ed è progettato per minimizzare le richieste fra client e server. La specifica è stata definita indipendentemente da Drupal che, in questo senso, si limita a implementarla nel proprio modulo core. Va comunque sottolineato che uno dei responsabili della API-first initiative per Drupal, Gabe Sullice, è anche diventato manutentore della specifica stessa.

Il modulo Drupal JSON:API si trova nel core (anche se non è attivo di default), e permette di esporre i nostri dati in formato JSON:API su endpoint appositi. Gli altri moduli dell’ecosistema che andremo a vedere sono invece moduli contrib, ed estendono le funzionalità del modulo core in modo da rispondere a specifiche necessità di sviluppo. Tutti questi moduli sono supportati da Acquia, l’organizzazione dietro a Drupal, o da altre aziende importanti e attive nel settore, come ad esempio Lullabot o Centarro.

Il primo modulo contrib della lista è JSON:API Extras, che permette di estendere e sovrascrivere il comportamento di default del modulo JSON:API. Ci sono poi Decoupled Router e Subrequests, che possono essere utilizzati, in combo, per recuperare le entità tramite path alias anziché id. Questi non sono tutti i moduli dell’ecosistema, ma sono quelli che ci servono oggi per i nostri scopi.

JSON:API

JSON:API è nato come modulo contrib nel maggio 2016 all’interno del contesto della già citata API-first initiative. Dal 2016, il modulo è diventato sempre più centrale nell’iniziativa e, raggiunta una versione stabile e feature complete, è stato inserito, nel marzo 2019, all’interno del core di Drupal. Oggi, quindi, è già presente nella codebase all’installazione, ed è sufficiente attivarlo affinché sia pronto e funzionante.

Il modulo espone tutti i dati a partire da un unico endpoint, /jsonapi appunto, non modificabile di default, ed espone tutte le entità disponibili sul sito, sia quelle standard che quelle custom. Rispetta tutti i livelli di sicurezza di Drupal, come i permessi di accesso alle entità e ai campi, e non aggiunge ulteriori layer di restrizioni. Quindi, è pienamente allineato agli standard di Drupal.

D’altro canto, l’unica opzione che il modulo mette a disposizione è la possibilità di fornire solamente i dati in lettura, oppure di permettere tutte le 4 operazioni CRUD - creazione, lettura, aggiornamento, eliminazione. Non avere configurazione complessa da un lato semplifica il suo utilizzo, ma dall’altro significa anche non avere spazio per la personalizzazione di ciò che viene esposto e di come viene esposto. Tra l’altro, di default vengono esposte tutte le risorse presenti sul sito, a eccezione solo di quelle flaggate come internal.

Ci sono quindi alcuni punti su cui il modulo mostra qualche limite. Ma qui ci viene in soccorso il secondo in comando del nostro eroe, ossia il modulo JSON:API Extras.

JSON:API Extras

JSON:API Extras è un po’ l’aiutante di bottega di JSON:API. JSON:API fa il grosso del lavoro, ed Extras si occupa di sistemare i dettagli. Di default l’endpoint è /jsonapi? Con Extras possiamo cambiarlo in ciò che vogliamo. Di default vengono esposte tutte le entità? Con Extras possiamo decidere quali non mostrare, e così via.

Extras è un modulo contrib e ci permette di definire in maniera più rigorosa ciò che vogliamo venga esposto, e in che modo. Nel dettaglio, possiamo:

  • cambiare il prefisso dell’endpoint, ad esempio da /jsonapi ad /api;
  • decidere di mostrare i contatori nelle collection delle entità; e soprattutto
  • fare l’override del comportamento di ogni risorsa esposta.

In particolare, su quest’ultimo punto: per ogni risorsa è possibile cambiare il nome esposto, il path, definire i campi disponibili, oppure disabilitarla del tutto. Ad esempio, se non abbiamo interesse ad esporre gli utenti presenti sul sito, possiamo decidere di rimuoverli da JSON:API appunto tramite Extras. Tutto questo è gestibile direttamente da pannello di amministrazione, senza necessità di scrivere codice apposito.

Abbiamo visto che Drupal espone le risorse quando viene specificato sui loro endpoint l’id univoco della risorsa. Cosa accade però se il nostro front end non conosce l’id dell’entità che deve richiamare, ma solamente il path alias?

Decoupled router

Decoupled Router ci consente di risolvere proprio questa casistica. Decoupled Router è un altro modulo contrib che fa parte dell’ecosistema di JSON:API e permette di richiamare le risorse tramite appunto path alias, anziché tramite uuid.

In pratica, funziona così: una volta attivato il modulo, è possibile inviare le richieste per le risorse a un path specifico,/router/translate-path?path=, concatenando il path alias della risorsa desiderata. Il modulo si occupa quindi di cercare la risorsa nei contenuti del sito, seguendo anche eventuali redirect, fino a recuperare l’identificativo della risorsa. A questo punto, restituisce una pagina che contiene non la risorsa direttamente, ma l’URL di JSON:API della risorsa.

Quindi, ad esempio, facendo una richiesta in GET a /router/translate-path?path=/books/flowers-algernon nel nostro progetto, ci viene restituita una pagina contenente l’URL con lo uuid della nostra risorsa, da poter usare per recuperare i dati che ci servono.

Ovviamente qui rimane un problema, ossia quello della doppia richiesta:- una prima per recuperare l’URL, e una seconda per recuperare l’entità. È a questo punto che ci viene in aiuto il modulo Subrequests. Con questo, abbiamo completato il giro delle funzionalità del nostro progetto.

Subrequests

Subrequests permette di aggregare più richieste in una sola POST e farle risolvere autonomamente a Drupal, che poi restituisce un’unica risposta. La cosa in particolare che ci interessa nel nostro caso è che il modulo permette di aggregare richieste anche se una dipende dalla risposta di una richiesta precedente, evitandoci così il problema di dover effettuare due o più chiamate per ottenere una sola risorsa.

Subrequests funziona inviando una POST all’endpoint /subrequests; il body della POST deve contenere una blueprint delle subrequest da effettuare. Questo è un esempio della blueprint per ottenere il nostro libro Flowers for Algernon:

[
  {
    "requestId": "req-1",
    "uri": "/router/translate-path?path=/books/flowers-algernon",
    "action": "view"
  },
 {
   "requestId": "req-2",
    "waitFor": ["req-1"],
     "uri": "/api/books/",

     "action": "view"
 }

]

  • Il requestId è l’identificativo con cui etichettiamo ogni nostra richiesta.
  • L’uri è il path a cui facciamo la richiesta. RequestId e uri sono obbligatori.
  • La action è il tipo di richiesta.

Nella richiesta 2 è inoltre presente la proprietà waitFor: questa viene usata per indicare che questa sottorichiesta dipende dalla risposta di una richiesta precedente. Possiamo infatti vedere che abbiamo costruito l’uri della 2 con i valori ottenuti dalla risposta alla richiesta 1, nello specifico con lo uuid del libro. In definitiva, la richiesta 1 è al path di Decoupled Router che ci restituisce l’id del libro che ci interessa, e la richiesta 2 è al path che ci riporta i dati del libro.

Altre parti dell'ecosistema

Riassumendo, quindi, abbiamo visto come nella nostra architettura ciascuno dei moduli dell’ecosistema prenda le mosse dal precedente e permetta di definirne e specializzarne sempre meglio le funzioni. Il risultato è un set di API facilmente configurabile, maneggevole, e altamente personalizzabile, nonché, essendo basato appunto su JSON:API, facilmente comunicabile e portabile.

  • Se poi vogliamo estendere ulteriormente le funzionalità del nostro progetto, prima di scrivere codice custom abbiamo a disposizione molti altri moduli:JSON:API Search API permette di effettuare richieste sugli indici di ricerca del sito tramite una modalità analoga a quella appena vista.
  • Entity share sfrutta le JSON:API esposte per permettere la sincronizzazione dei contenuti di due siti Drupal.
  • Commerce API integra le funzionalità richieste da Drupal Commerce sfruttando le JSON:API.

Questo elenco di moduli non è esaustivo, ma è comunque un buon punto di partenza per farsi un’idea più completa sull’ecosistema JSON:API su Drupal.

Considerazioni finali

Qui ci siamo soffermati su JSON:API, ma questo non è l’unico modulo o ecosistema disponibile per ottenere il nostro scopo. È quello al momento più completo e supportato, ma ci sono anche altre opzioni, oltre ovviamente al codice custom.

Nel core di Drupal abbiamo un altro modulo, RESTful Web Services, che per un po’ di tempo è stato il diretto concorrente di JSON:API per diventare lo standard de facto per questa casistica d’uso. Alla fine si è affermato JSON:API, principalmente per la semplicità con cui riesce a coprire la maggior parte degli use cases. JSON:API permette di esporre tutte le entità presenti sul sito senza praticamente effort aggiuntivo, mentre RESTful Web Services richiede una configurazione custom più complessa, le opzioni per il filtering dei dati sono più limitate e complicate da configurare, le richieste per le risorse sono meno strutturate rispetto a quelle di JSON:API, e le collection possono essere esposte solo previa configurazione di apposite viste. Insomma, un buon modulo, ma che rimane indietro su alcuni rispetto alla semplicità e flessibilità di JSON:API. D’altro canto, per specifici casi d’uso, come ad esempio operazioni con logiche custom o che non siano legate strettamente alle entità, RESTful Web Services offre un framework sicuramente più flessibile da poter sfruttare, al costo appunto di una maggiore complessità generale.

GraphQL invece è supportato grazie a un modulo contrib. Non è quindi nel core, e dei tre è l’ultimo arrivato sulla scena. In generale, essendo basato appunto su GraphQL, ha delle ottime specifiche ben documentate, ma alcune cose sono meno agili, come le operazioni di scrittura e il filtro lato client sui contenuti, che invece abbiamo visto essere piuttosto semplice con l’implementazione Drupal di JSON:API.

Comunque, il modulo GraphQL è una via sicuramente percorribile, molto sponsorizzata inizialmente dallo stesso Dries Buytaert, anche se al momento il supporto e la completezza di integrazione con Drupal non possono ancora competere con quella dell’ecosistema di JSON:API senza dover scrivere molto più codice custom.

Guarda il tech talk Headless Drupal

Non hai voglia di leggere tutto il testo? Il video qui sotto è la valida alternativa all'articolo.