Beeldscherm met code
Blog

Data migratie in Drupal 8

Complexe data migraties met minimaal maatwerk

Een datamigratie is vaak een Epic, wanneer er veel en diverse content moet worden verhuisd. Vaak is dit het uitgelezen moment om de 'oude' content op te schonen en de website te vereenvoudigen. Maar als je te maken hebt met kennisartikelen, TV uitzendingen of recepten, dan wil je zoveel mogelijk (of alles) kunnen migreren. De nieuwe content structuur, tagging en het zorgen dat nieuwe URL's goed voor Google indexeerbaar blijven hoort daarbij. Voor wat betreft de technische migratie ligt er veel werk in handen van ontwikkelaars. En daar helpt Drupal 8 je in mee.

Een van de vele verbeteringen in Drupal 8 is de out-of-the-box ondersteuning voor het migreren van data. Deze basis API maakt het eenvoudig om data te migreren van oudere Drupal versies naar Drupal 8. Ook voor maatwerk migraties biedt de module uitkomst. Doordat de module een aantal belangrijke standaard zaken afhandelt, kun je als developer met minimaal maatwerk complexe migraties in enkele stappen uitvoeren. 

Stap 1: Benodigde modules

De core migrate module biedt ondersteuning voor migraties van oudere Drupal versies naar Drupal 8, maar voor maatwerk migraties van externe systemen zijn extra contrib modules nodig. Download en/of installeer de onderstaande core/contrib modules:

  • Migrate (core module)

  • Migrate Plus (contrib module): Biedt verschillende verbeteringen, zoals ondersteuning voor XML of JSON brondata, en is vereist voor de Migrate Tools module.

  • Migrate Tools (contrib module): Biedt oa verschillende drush commando’s om de migraties uit te kunnen voeren.

Stap 2: Basisconfiguratie

Het migreren van data gebeurt op entiteit niveau. Elke migratie moet data bevatten voor 1 entiteit type, zoals bijv. node, taxonomy terms etc. Zorg, indien mogelijk, dat de brondata al is opgesplitst in verschillende entiteiten, dit scheelt een hoop moeite bij het verwerken van de data.

Elke migratie kan worden aangemaakt via een YAML bestand. Deze dient te worden geplaatst in de /config/install directory van een maatwerk module. Het is aan te raden om een aparte module te maken voor de migratie, maar dit is niet verplicht. Een basis voorbeeld van een YAML bestand is het onderstaande.

id: node_article
source:
  plugin: node_article
process:
  title: name
destination:
  plugin: entity:node
 

Het bestand configureert 4 belangrijke zaken die minimaal benodigd zijn voor een migratie:

  1. ID: Een unieke identifier voor de migratie. Andere migraties kunnen op basis van deze identifier afhankelijkheden of mappings specificeren.
  2. Source: Elke migratie wordt uitgevoerd via een migrate source plugin. De source plugin is verantwoordelijk voor het ophalen van de brondata en biedt enkele mogelijkheden om de data aan te passen voor, of tijdens de import, etc. In dit deel van het YAML bestand specificeer je de plugin ID van de migrate source plugin. Dit kan een bestaande class zijn, maar vaak zul je een base class extenden om meer controle te hebben over de data.
  3. Process: In het process onderdeel wordt de mapping van de brondata naar de velden in Drupal gespecificeerd. Voor elke mapping regel zijn er verschillende mogelijkheden, zoals het mappen van brondata, het instellen van vaste waardes voor bepaalde velden, maar ook complexe mappings van brondata ID’s naar reeds geïmporteerde inhoud voor referenties tussen verschillende entiteiten. Hier komen straks enkele voorbeelden van.
  4. Destination: In de meeste gevallen zal de data worden gemigreerd naar entiteiten, bijv. nodes. Hier is een standaard destination plugin voor. Voor complexe use-cases is het echter ook mogelijk om eigen destination plugins te schrijven.

Stap 3: Source plugins

Het ophalen en verwerken van de data gebeurt via annotated source plugins. De core Migrate module biedt ondersteuning voor array’s of database tabellen als bron, maar de Migrate Plus module biedt daarnaast ook de mogelijkheid om data op te halen van een URL. In de configuratie YAML file kan configuratie worden meegegeven aan de source plugin, zo kan bijv. aan de URL plugin een data_parser worden meegegeven (JSON/XML). 

  plugin: url
  data_fetcher_plugin: http
  data_parser_plugin: xml
  urls: http://sourcefiles.dev/articles.xml

Nadat de data is opgehaald zijn er verschillende mogelijkheden om de data verder aan te passen tijdens de migratie. Dit kan bijv door het implementeren van de prepareRow(Row $row) method in de migrate source plugin class. Elke rij uit de brondata komt door deze functie heen. Hier is het bijv. mogelijk om bestaande data aan te passen of samen te voegen voor import. Daarnaast zijn er verschillende events die door de migrate module worden getriggerd. Door een event subscriber te maken kun je hier op in haken.

Voor de use-case in het huidige project dienden verschillende bronbestanden te worden samengevoegd tot 1 entiteit. Dit was 1 van de redenen waarom we hebben gekozen om gebruik te maken van de EmbeddedDataSource base class. Deze base class verwacht een standaard array als brondata. Door een aparte service te maken die deze array op te bouwen uit de verschillende bronbestanden, is het samenvoegen van de data direct netjes gescheiden van het ophalen en verwerken ervan.

Stap 4: Data mapping

De data mapping is waar de data uit de bronbestanden wordt gekoppeld aan de velden die beschikbaar zijn in het CMS. Dit gebeurt via het process onderdeel in de configuratie YAML file. Hier volgen enkele voorbeelden van veelgebruikte mappings.

process:
  title: name

Dit is de meest basis vorm van mapping. Het veld source_name uit de brondata wordt geïmporteerd in het veld title van de Drupal entiteit.

process:
  type:
   plugin: default_value
   default_value: article

Hier wordt een vaste waarde ‘article’ geïmporteerd als type van de entiteit. In dit geval het node type. Elke mapping kan gebruik maken van plugins voor verschillende use-cases. Hier komen zo nog enkele voorbeelden van. Het is ook mogelijk om eigen plugins te maken die de data op veldniveau te bewerken voor import.

process:
 uid:
   plugin: default_value
   default_value: 1

Hier wordt een vaste waarde ‘1’ geïmporteerd als gebruikers ID van de entiteit. De speciale admin user wordt in dit geval de eigenaar van de inhoud.

process:
 body/value: source_description
 body/format:
   plugin: default_value
   default_value: filtered_html

Het body veld in Drupal bestaat uit een value en een format. Hier kan optioneel nog een summary veld bij. Via een slash kan heel specifiek de brondata worden gemapped op een element in het Drupal veld. In dit geval wordt voor het format zelfs een vaste waarde toegekend, terwijl de value wordt aangeleverd door de brondata.

process:
 field_category:
   plugin: entity_lookup
   source: source_category

De entity_lookup plugin maakt het mogelijk om een specifieke entiteit op te zoeken op basis van naam. Dit kan bijv een taxonomy term zijn, maar ook een node. De plugin leest automatisch uit welke entiteit types geconfigureerd zijn voor het veld.

process:
 field_tag:
   plugin: entity_generate
   source: source_tag

De entity_generate plugin doet hetzelfde als de entity_lookup plugin, met de toevoeging dat de entity_generate plugin nieuwe entiteiten aanmaken als deze nog niet bestaat. Deze kan dus nieuwe taxonomy terms aanmaken als dit nodig is.

process:
 field_author:
   plugin: migration
   migration: author
   no_stub: true
   source: source_authorid

De migration process plugin maakt het mogelijk om een referentie te leggen naar eerder geïmporteerde entiteiten. De plugin ontvangt het unieke externe ID van de geimporteerde inhoud, en zoekt in de mapping tabel de interne Drupal ID op om de referentie te kunnen leggen.

process:
 field_paragraphs:
   -
     plugin: migration
     migration: paragraphs
     no_stub: true
     source: source_component_ids
   -
     plugin: iterator
     process:
       target_id: '0'
       target_revision_id: '1'

Dit gedeelte is vrij complex. Het is namelijk ook mogelijk om meerdere plugins achter elkaar los te laten op de data. Vanuit het oude CMS was de data in dit geval al opgesplitst in verschillende paragraphs (componenten in het oude CMS) per artikel. Paragraphs velden slaan naast de target ID ook de target_revision_id op.  De componenten zijn geïmporteerd via een aparte migratie. De migration process plugin haalt op basis van het externe bron component ID’s de target ID’s en target revision ID’s in Drupal op. Via een extra iterator plugin kunnen deze waardes worden gemapped.

process:
 path/pathauto:
   plugin: default_value
   default_value: 0
 path/alias: source_slug

Hier een tweede voorbeeld van een veel voorkomende situatie waar je moet weten dat de data als array moet worden aangeleverd in het YAML configuratie bestand.

process:
 moderation_state: moderation_state
 status: status

Het laatste voorbeeld laat zien dat de moderation_state moet worden aangeleverd wanneer de content moderation module is geïnstalleerd. Zonder de moderation_state wordt de status niet goed opgeslagen en zal de default moderation_state voorrang krijgen op het status veld. De content moderation module verandert de status: 1 waarde dan weer naar status: 0.

Stap 5: Migratie starten

Momenteel is het alleen mogelijk om een migratie te starten via drush. Dit kan via het commando:

drush mi --all --update --feedback=”100 items”

De opties zijn:

  • --all: Dit betekent dat alle migraties worden uitgevoerd. Hierbij wordt rekening gehouden met onderlinge afhankelijkheden. In plaats van --all kan ook een specifieke migratie naam worden aangegeven.
  • --update: Wanneer de migratie vaker wordt gedraaid, kun je reeds geimporteerde inhoud bijwerken via deze optie. De is handig voor periodieke imports, maar ook tijdens het ontwikkelen van een eenmalige import.
  • --feedback: Migraties kunnen soms erg veel tijd kosten. Het is dan niet altijd duidelijk of de migratie nog loopt. Door de feedback optie mee te geven zal drush elke 100 items een bericht sturen. Zo is goed te zien of de import nog bezig is. Dit aantal kan uiteraard naar keuze worden aangepast (naar bijv 500 of 1000 items).

De config

dependencies:
  enforced:
    module:
      - mymodule_migrations
id: node_article
migration_tags: null
migration_group: nodes
label: 'Node: Article'
source:
  plugin: node_article
  key: id
process:
  type:
    plugin: default_value
    default_value: article
  title: source_name
  uid:
    plugin: default_value
    default_value: 1
  body/value: source_description
  body/format:
    plugin: default_value
    default_value: filtered_html
  path/pathauto:
    plugin: default_value
    default_value: 0
  path/alias: source_slug
  moderation_state: source_moderation_state
  status: source_status
  created: source_created
  changed: source_changed
destination:
  plugin: 'entity:node'
migration_dependencies:
  required:
    - node_author

  • Dependencies: Dit zijn de verschillende afhankelijkheden voor het draaien van de migratie. Het is verstandig om hier de module op te nemen dit de migratie definieert.
  • Migration groups: Het is mogelijk om verschillende migraties te groeperen. Dit geeft extra mogelijkheden om migraties op een logische manier in te delen. Zo kun je bijv eenvoudig een deel van de data migratie uitvoeren.
  • Migration dependencies: Hier kan worden aangegeven van welke andere migraties de huidige migratie afhankelijk is. In de bovenstaande voorbeelden is de article migratie bijv afhankelijk van de author migratie.

Drush commando’s

drush ms

Hiermee kan de status van alle migraties  in 1 keer worden opgevraagd. Welke lopen er nog, hoeveel items zijn er geïmporteerd, hoeveel items moeten er nog, etc.

drush mrs node_article

Soms loopt het migratie script vast. Dit kan tijdens het ontwikkelen zijn door een PHP error oid. In dat geval blijft de migratie onterecht hangen in de status Importing. Deze migratie kan dan ook niet meer worden gestart. In dat geval zal het bovenstaande commando de status van een specifieke migratie resetten. Het is helaas niet mogelijk om alle migratie statussen in 1 keer te resetten.

drush mr --all
drush mr node_article

Het mr commando kan worden gebruikt om een migratie terug te draaien. De geïmporteerde data wordt verwijderd uit het systeem. Dit kan handig zijn tijdens het testen van de migraties.

De voorbeeld bestanden met code behorende bij dit artikel kun je hier downloaden. 

 

Dit artikel is geschreven door Sean Blommaert die interim bij LimoenGroen aan complexe datamigraties en website ontwikkeling werkt. Wil je meer weten over dit soort data migraties? Stuur ons een bericht op hallo@limoengroen.nl of kom eens langs bij ons in Amsterdam. De koffie is altijd vers!