Pulahdus Drupal 8 & Commerce 2 -migraatioon

Viimeiset kolme viikkoa ovat olleet melkoinen syväsukellus Drupal 8 -migraatiojärjestelmään ja siitä tarjoamme teille tässä pienen pulahduksen. Varsin antoisa kokemus, näin jälkikäteen katsottuna, mutta välillä melkein meinasi usko loppua. Vain melkein.

D8:ssa on hyvä tuki erilaisille rajapinnoille ja loppujen lopuksi paljon erilaisia tapoja tehdä onnistunut migraatio jostain järjestelmästä tai vaikkapa tietokannasta toiseen. Valitettavasti dokumentaatio laahaa pahasti jäljessä. Paras apu onkin oma apu, ja on samantien syytä opetella käyttämään riittävää debuggausjärjestelmää. Lisäksi kaikki vastaantulevat kivet kannattaa kääntää. Migrate Plus esimerkiksi toi paljon vipuvartta, kunhan sen tuomiin muutoksiin tutustui erikseen ja kunnolla.

Koska emme päästä itseämme helpolla, niin ihka ensimmäinen migraatiomme oli tuotetietojen siirtäminen CodeMaster-järjestelmästä vastaluomaamme Drupalin Commerce 2 -verkkokauppaan. Käytännössä siis luoda vastaavat tuotteet ja niiden variaatiot migraation avulla Drupaliin. CodeMasteriin pääsee tutustumaan täällä. Se tarjoaa varsin kelvollisen REST-rajapinnan json-optioineen.

Koska kaikki palikat olivat jo käden ulottuvilla, niin eikun hommiin. Parin sekoilun jälkeen kävi ilmeiseksi, että D8 vaatii tuotteen variaation luomista ennen tuotetta. Makes sense. Jos tuotteella ei ole variaatiota käden ulottuvilla ajoissa, Drupal omatoimisesti luo sille sellaisen. Saattaa hetken hämätä pientä kulkijaa siinä kohtaa kun tauluun ilmestyy yhtä monta niogwhyapirghhepbhslfn-variaatiota kuin mitä on tuotteita. Tuotteen variaatiot niputetaan tuotteen variations-kenttään (array) ja vastaavasti variaation target_id-kenttään asetetaan tuotteen product_id. Tämä viimeisin asetetaan paikalleen postSave-funktiossa siinä kohtaa kun tuotetta luodaan esimerkiksi migraation aikana. Se on muuten myös erittäin hyvä kohta pistää vaikkapa pieni printti elämän ja kehitystyön helpottamiseksi, kunhan sitten muistaa siivota jälkensä:

foreach ($this->variations as $item) {
  $variation = $item->entity;

  if ($variation->product_id->isEmpty()) {
    drush_print_r("We are seeing empty product_id that is now filled with:");
    drush_print_r($this->id());

    $variation->product_id = $this->id();
    $variation->save();
  }
}

Luontevin tapa toteuttaa tuotteiden ja variaatioiden migraatio paikasta toiseen oli luoda molemmille entiteeteille oma .yml-tiedostonsa. Drupal 8:ssa yml-configuraatioiden käyttäminen on kätevää lukuunottamatta configuraatioiden muuttelua. Ne saa kyllä poistettua kun uninstalloi luomansa migraation (esim. containerin ulkopuolelta: docker-compose exec --user 82 php drush @dev pm-uninstall migrate_products -y) ja sitten vaikka ceditillä putsaa migraatiot, mutta se ei ole kovin nopeaa. Siksi yml-filen alkuun kannattaa laittaa riippuvuus:

dependencies:
  module:
    - migrate_products
    - node
  enforced:
    module:
      - migrate_products #deletes the migrate configuration when the module is disabled

Molemmille migraatioille annetaan luonnollisesti oma id:nsä ja ne voidaan pakata samaan grouppiin:

id: migrate_products_json_product
label: ''
migration_tags: null
migration_group: migrate_products

Meidän tapauksessamme sisään tuli tavaraa mainitun REST-rajapinnan yli ja basic autentikaatiota käyttäen. Toista tuntia saimme kulumaan siihenkin, että arvailimme jälkimmäisen syntaksia, jota emme onnistuneet löytämään:

source:
  plugin: url
  # Specifies the http fetcher plugin:
  data_fetcher_plugin: http
    # Specifies the JSON parser plugin.
  data_parser_plugin: json
  authentication:
    plugin: basic
    username: ''
    password: ''
  headers:
    Accept: 'application/json; charset=utf-8'
    Content-Type: 'application/json'
  # One or more URLs from which to fetch the source data.
  urls: https://codemaster-api.liiba.laaba

Noin se toimii. Tämän jälkeen sitten tulikin isompia ongelmia. Koska kyseessä tosiaan on ensimmäinen D8-migraatiomme, niin voimme toistaiseksi vetää johtopäätöksiä vain sen pohjalta. Suhtaudu siis pienellä varauksella ja muista lisäksi, että D8:n migraatioimplementaatio elää jatkuvasti.

Tuotteen ja sen variaatioiden välille tulee luoda yllä mainitut kytkökset ja mielellään migraation aikana. Alunperin lähestyimme operaatiota migration_lookup-prosessipluginin kautta, joka vaikutti loogiselta valinnalta. Kuvion oli määrä mennä suunnilleen siten, että koska tuotteiden migraatio riippuu variaatioiden migraatiosta, niin tuotteen yml:ssä määrätään seuraavaa:

destination:
  plugin: entity:commerce_product
process:
  type:
    plugin: default_value
    default_value: default # This default is actually the type of the product in this case

# Fills in variation details created in product variation migration.
  variations:
    -
      plugin: migration_lookup
      migration: migrate_products_json_product_variation
      source: product_id

Tämä olisi ehkä voinut onnistua hieman erilaisella setupilla. Nyt kuitenkin variaation migraatiossa (tässä id 'migrate_products_json_product_variation') json-parserille pitää antaa tietty identifier. Migrate Plussan isä Mike Ryan avaa XML/JSON-parsereita esim. täällä.
"Under ids, we specify which of the source fields retrieved above represent our unique identifier for the item" eli parserille pitää antaa uniikki id, joka meidän tapauksessamme - ja varmasti varsin usein - on variaation id. Tämä kuitenkin sattuu olemaan samalla se id, jota migration_lookup defaulttina käyttää mäpätessään kahden eri migraation tietoja toisiinsa. Se ei meidän suunnitelmiimme sopinut, sillä olisimme halunneet asettaa aivan eri variaatiokentän migraatiotaulukoiden avaimeksi. 
Saimme toteutettua 1:1-migraation (yksi variaatio per tuote) kohtalaisen helposti migration_lookup-plugarilla ja tosiaan jossain toisessa setupissa 1:n-migraationkin saanee onnistumaan. Tässä se olisi vaatinut sen verran räätälöintiä, ja migraatiosta uhkasi tulla turhan kompleksinen, että aloimme pohtimaan muita lähestymistapoja. 

Lyhyestä virsi kaunis, joten todettakoon ratkaisuksi löytyneen Entity Lookup -pluginin Migration Plus -paketista. Jälleen olisimme arvostaneet D8 migraation dokumentaatiota edes sen verran, että tuo kätevä kikkula olisi löynyt aiemmin, mutta hyvä näinkin. Se ei nimittäin lopulta vaatinut kuin pienen twiikin (ja aika paljon debuggausta), ennen kuin saimme suhteellisen nätisti toimivan tuotemigraation toimimaan.

Melkein pahoittelen, etten päässytkään hyödyntämään Drupal 8:n migration service -systeemiä muuten kuin debuggauksessa. Kyseisten palvelujen luominen näyttää olevan yhtä kätevää ja paljon siistimpää kuin mitä Drupal 7:n hookit tapasivat olla. Esim. jokin POST_ROW_SAVE-eventtiin reagoiva palvelufunktio antaa paljon hyödyllistä tieto siitä mitä migraatiossa on ajon aikana meneillään.

Toivottavasti näistä tiedonmurusista on jollekin hyötyä. Kattavan selonselonteon kirjoittaminen vaatisi useamman tunnin lisää ja oikeita migraatioratkaisuja on varmaan yhtä monta kuin migraatioitakin. Suunnittelemme jonkinlaisia On Demand -kursseja D8 & Commerce 2 -migraatioista, joten niistäkin voi jo kysellä osoitteesta info@drontti.fi.