Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Decoupled Drupal Days 2018: Drupal Unhitched

Decoupled Drupal Days 2018: Drupal Unhitched

The CMS in Decoupled Architectures

dirtystylus

August 17, 2018
Tweet

More Decks by dirtystylus

Other Decks in Technology

Transcript

  1. Descriptive, Not Prescriptive I’m not telling you how to do

    this stuff. But I hope all of this helps.
  2. 3 Things 1. What is a decoupled CMS? 2. Decoupled

    Scenarios 3. Decoupling Drupal
  3. What is a decoupled CMS? •An architecture for websites and

    applications where the CMS is not used to render the user-facing site or application.
  4. CMS (Drupal 8) + Touchscreen Interactive (React) + JS Website

    (React) Smithsonian National Museum of the American Indian nmai.si.edu/americans
  5. CMS (Drupal 8) + CMS-rendered Website + Digital Signage (React)

    Mann Center for the Performing Arts manncenter.org
  6. CMS (Drupal 7) + CMS-rendered Website + Touch Wall (Cinder/C++)

    + 2 Touchscreen Interactives (AngularJS) + iOS / Android Native Applications Smithsonian National Air and Space Museum airandspace.si.edu
  7. Cons •Larger tech stack (Drupal, JS, multiple environments) •Maintenance •For

    websites you lose built-in functionality of CMS: menus/routing/accessibility/SEO
  8. Start with Content •Getting content ready to travel •NO Presentation

    specific names •Decouple your mind •Content in many forms/faces
  9. { "nid": [ { "value": 1 } ], "uuid": [

    { "value": "3036ea2d-7502-4c64-8bc6-2e9ee3a0f897" } ], "vid": [ { "value": 1 } ], "langcode": [ { "value": "en" } ], "type": [ { "target_id": "book", "target_type": "node_type", "target_uuid": "a3c32842-68b2-46fd-b70e-d6ed9e71ee2d" } ], "revision_timestamp": [ { "value": "2018-04-23T18:10:59+00:00", "format": "Y-m-d\\TH:i:sP" } ], "revision_uid": [ { "target_id": 1, "target_type": "user", "target_uuid": "a7484c09-d013-402e-b070-46c0d84a7b28", "url": "/user/1" } ], "revision_log": [], "status": [ { "value": true } ], "title": [ { "value": "The Fifth Season" } ], "uid": [ { "target_id": 1, "target_type": "user", "target_uuid": "a7484c09-d013-402e-b070-46c0d84a7b28", "url": "/user/1" } ], "created": [ { "value": "2018-04-23T18:10:29+00:00", "format": "Y-m-d\\TH:i:sP" } ], "changed": [ { "value": "2018-04-23T18:10:59+00:00", "format": "Y-m-d\\TH:i:sP" } ], "promote": [ { "value": true } ], "sticky": [ { "value": false } ], "default_langcode": [ { "value": true } ], "revision_translation_affected": [ { "value": true } ], "path": [ { "alias": null, "pid": null, "langcode": "en" } ], "body": [ { "value": "<p>Part one of the Broken Earth trilogy.</p>\r\n", "format": "basic_html", "processed": "<p>Part one of the Broken Earth trilogy.</p>", "summary": "" } ] }
  10. { "nid": [ { "value": 1 } ], "type": [

    { "target_id": "book", "target_type": "node_type", "target_uuid": "a3c32842-68b2-46fd-b70e-d6ed9e71ee2d" } ], "title": [ { "value": "The Fifth Season" } ], "body": [ { "value": "<p>Part one of the Broken Earth trilogy.</p>\r\n", "format": "basic_html", "processed": "<p>Part one of the Broken Earth trilogy.</p>", "summary": "" } ] }
  11. [ { "title": "Kim Stanley Robinson", "body": "<p>Author of the

    <em>Mars</em> trilogy and <em>New York 2140</em>.</p>\r\n", "field_books": [ "2" ] }, { "title": "N. K. Jemisin", "body": "<p>Hugo Award-winning author of the Broken Earth trilogy.</p>\r\n", "field_books": [ "1" ] } ]
  12. … $a_query = \Drupal::entityQuery('node'); $a_query->condition('status', 1); $a_query->condition('type', 'event'); $entity_ids =

    $a_query->execute(); $events = Node::loadMultiple($entity_ids); foreach ($events as $event) { $item['id'] = (int) $event->nid->value; $date = $event->field_event_date->value; $item['date'] = $date; $item['title'] = $event->title->value; $item['display_title'] = $event->field_display_title->value; $image_data = MediaAssetService::getMediaPathSingle($event, 'field_featured_image'); $item['image'] = urldecode($image_data['image']); $item['image_relative'] = urldecode($image_data['image_relative']); $data[] = $item; } $response = new JsonResponse(['data' => $data]); return $response; …
  13. { "data": [ { "type": "node--author", "id": "88223a76-278d-4fc6-9945-0bdb56cc78d3", "attributes": {

    "nid": 2, "body": { "value": "<p>Winner of the Hugo Award.</p>\r\n", "format": "basic_html", "processed": "<p>Winner of the Hugo Award.</p>", "summary": "" }, "field_display_name": { "value": "N. K. Jemisin", "format": "basic_html", "processed": "N. K. Jemisin" } }, "relationships": { "field_books": { "data": [ { "type": "node--book", "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f" } ], }, } ], }
  14. { "attributes": { "nid": 2, "body": { "value": "<p>Winner of

    the Hugo Award.</p>\r\n", "format": "basic_html", "processed": "\<p>Winner of the Hugo Award.</p>", "summary": "" }, "field_display_name": { "value": "N. K. Jemisin", "format": "basic_html", "processed": "N. K. Jemisin" } }, }
  15. { "relationships": { "field_books": { "data": [ { "type": "node--book",

    "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f" } ], }, }
  16. { "relationships": { "field_books": { "data": [ { "type": "node--book",

    "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f" } ], }, }
  17. { "relationships": { "field_books": { "data": [ { "type": "node--book",

    "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f" } ], }, }
  18. { "included": [ { "type": "node--book", "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f", "attributes": {

    "nid": 1, "title": "The Fifth Season", "body": { "value": "<p>Part one of the Broken Earth trilogy.</p>\r\n", "format": "basic_html", "processed": "<p>Part one of the Broken Earth trilogy.</p>", "summary": "" } }, } }
  19. { "included": [ { "type": "node--book", "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f", "attributes": {

    "nid": 1, "title": "The Fifth Season", "body": { "value": "<p>Part one of the Broken Earth trilogy.</p>\r\n", "format": "basic_html", "processed": "<p>Part one of the Broken Earth trilogy.</p>", "summary": "" } }, } }
  20. { "included": [ { "type": "node--book", "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f", "attributes": {

    "nid": 1, "title": "The Fifth Season", "body": { "value": "<p>Part one of the Broken Earth trilogy.</p>\r\n", "format": "basic_html", "processed": "<p>Part one of the Broken Earth trilogy.</p>", "summary": "" } }, } }
  21. { "included": [ { "type": "node--book", "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f", "attributes": {

    "nid": 1, "title": "The Fifth Season", "body": { "value": "<p>Part one of the Broken Earth trilogy.</p>\r\n", "format": "basic_html", "processed": "<p>Part one of the Broken Earth trilogy.</p>", "summary": "" } }, } }
  22. Installation Note •Download the module (or use drush dl graphql)

    •Run composer require webonyx/graphql-php at root level (error otherwise)
  23. Installation Note •Download the module (or use drush dl graphql)

    •Run composer require webonyx/graphql-php at root level (error otherwise) •Enable the module
  24. { "data": { "nodeQuery": { "entities": [ { "entityId": "1",

    "entityLabel": "The Fifth Season" }, { "entityId": "2", "entityLabel": "New York 2140" }, { "entityId": "3", "entityLabel": "N. K. Jemisin" }, { "entityId": "4", "entityLabel": "Kim Stanley Robinson" } ] } } }
  25. Node Query w/ Types (Author) query { nodeQuery { entities

    { ...on NodeAuthor { entityId title body { value } fieldDisplayName { value } fieldBooks { entity { entityId title } } } } } }
  26. Node Query w/ Types (Author) query { nodeQuery { entities

    { ...on NodeAuthor { entityId title body { value } fieldDisplayName { value } fieldBooks { entity { entityId title } } } } } }
  27. Node Query w/ Types (Author) query { nodeQuery { entities

    { ...on NodeAuthor { entityId title body { value } fieldDisplayName { value } fieldBooks { entity { entityId title } } } } } }
  28. Node Query w/ Types (Author) query { nodeQuery { entities

    { ...on NodeAuthor { entityId title body { value } fieldDisplayName { value } fieldBooks { entity { entityId title } } } } } }
  29. Node Query w/ Types (Author) query { nodeQuery { entities

    { ...on NodeAuthor { entityId title body { value } fieldDisplayName { value } fieldBooks { entity { entityId title } } } } } }
  30. { "data": { "nodeQuery": { "entities": [ {}, {}, {

    "entityId": "3", "title": "N. K. Jemisin", "body": { "value": "<p>Hugo Award-winning author of the Broken Earth trilogy.</p>\r\n" }, "fieldDisplayName": { "value": "N. K. Jemisin" }, "fieldBooks": [ { "entity": { "entityId": "1", "title": "The Fifth Season" } } ] }, … ] } } }
  31. { "data": { "nodeQuery": { "entities": [ {}, {}, {

    "entityId": "3", "title": "N. K. Jemisin", "body": { "value": "<p>Hugo Award-winning author of the Broken Earth trilogy.</p>\r\n" }, "fieldDisplayName": { "value": "N. K. Jemisin" }, "fieldBooks": [ { "entity": { "entityId": "1", "title": "The Fifth Season" } } ] }, … ] } } }
  32. { "data": { "nodeQuery": { "entities": [ {}, {}, {

    "entityId": "3", "title": "N. K. Jemisin", "body": { "value": "<p>Hugo Award-winning author of the Broken Earth trilogy.</p>\r\n" }, "fieldDisplayName": { "value": "N. K. Jemisin" }, "fieldBooks": [ { "entity": { "entityId": "1", "title": "The Fifth Season" } } ] }, … ] } } }
  33. query { nodeQuery { entities { ...on NodeAuthor { entityId

    title body { value } fieldDisplayName { value } fieldBooks { entity { entityId title } } } ...on NodeBook { entityId title body { value } } } } }
  34. query { nodeQuery { entities { ...on NodeAuthor { entityId

    title body { value } fieldDisplayName { value } fieldBooks { entity { entityId title } } } ...on NodeBook { entityId title body { value } } } } }
  35. query { nodeQuery { entities { ...on NodeAuthor { entityId

    title body { value } fieldDisplayName { value } fieldBooks { entity { entityId title } } } ...on NodeBook { entityId title body { value } } } } }
  36. query { nodeQuery { entities { ...on NodeAuthor { entityId

    title body { value } fieldDisplayName { value } fieldBooks { entity { entityId title } } } ...on NodeBook { entityId title body { value } } } } }
  37. Filter by Node Type query { nodeQuery (filter: {conditions: [{

    field: "type" value: "author" operator: EQUAL }] } ) { … } }
  38. Filter by Node Type query { nodeQuery (filter: {conditions: [{

    field: "type" value: "author" operator: EQUAL }] } ) { … } }
  39. Filter by Node Type query { nodeQuery (filter: {conditions: [{

    field: "type" value: "author" operator: EQUAL }] } ) { … } }
  40. Filter by Node Type query { nodeQuery (filter: {conditions: [{

    field: "type" value: "author" operator: EQUAL }] } ) { … } }
  41. query { nodeQuery (filter: {conditions: [{ field: "type" value: "author"

    operator: EQUAL }] } ) { entities { ...on NodeAuthor { entityId title body { value } fieldBooks { entity { title body { value } } } } } } }
  42. { "data": { "nodeQuery": { "entities": [ { "entityId": "3",

    "title": "N. K. Jemisin", "body": { "value": "<p>Hugo Award-winning author of the Broken Earth trilogy.</p>\r\n" }, "fieldBooks": [ { "entity": { "title": "The Fifth Season", "body": { "value": "<p>Part one of the Broken Earth trilogy.</p>\r\n" } } } ] },
  43. Aliases and Multiple Queries Take a simple Image Media Bundle

    entities { entityLabel ... on NodeHotspot { fieldName, title, fieldThumbnailImage { entity { ... on MediaImage { fieldMediaImage { url width height alt title } } } }, } }
  44. entities { entityLabel ... on NodeHotspot { fieldName, title, fieldThumbnailImage

    { entity { ... on MediaImage { fieldMediaImage { url width height alt title } } } }, } }
  45. entities { entityLabel ... on NodeHotspot { fieldName, title, fieldThumbnailImage

    { entity { ... on MediaImage { fieldMediaImage { url width height alt title } } } }, } }
  46. entities { entityLabel ... on NodeHotspot { fieldName, title, fieldThumbnailImage

    { entity { ... on MediaImage { fieldMediaImage { url width height alt title } } } }, } }
  47. entities { entityLabel ... on NodeHotspot { fieldName, title, fieldThumbnailImage

    { entity { ... on MediaImage { fieldMediaImage { url width height alt title } } } }, } }
  48. entities { entityLabel ... on NodeHotspot { fieldName, title, fieldThumbnailImage

    { entity { ... on MediaImage { fieldMediaImage { url width height alt title } } } }, } }
  49. entities { entityLabel ... on NodeHotspot { fieldName, title, fieldThumbnailImage

    { entity { ... on MediaImage { fieldMediaImage { url width height alt title } } } }, } }
  50. This gives us the source image. What about derivatives? fieldThumbnailImage

    { entity { ... on MediaImage { fieldMediaImage { derivative(style: THUMBNAIL) { url width height } url width height alt title } } }
  51. This gives us the source image. What about derivatives? fieldThumbnailImage

    { entity { ... on MediaImage { fieldMediaImage { derivative(style: THUMBNAIL) { url width height } url width height alt title } } }
  52. This gives us the source image. What about derivatives? fieldThumbnailImage

    { entity { ... on MediaImage { fieldMediaImage { derivative(style: THUMBNAIL) { url width height } url width height alt title } } }
  53. This gives us the source image. What about derivatives? fieldThumbnailImage

    { entity { ... on MediaImage { fieldMediaImage { derivative(style: THUMBNAIL) { url width height } url width height alt title } } }
  54. What if we need multiple derivatives? Enter aliases. An alias

    allows you to run multiple queries for the same field and map them to different names.
  55. fieldThumbnailImage { entity { ... on MediaImage { fieldMediaImage {

    thumbnail:derivative(style: THUMBNAIL) { url width height } large:derivative(style: LARGE) { url width height } url width height alt title } } }
  56. fieldThumbnailImage { entity { ... on MediaImage { fieldMediaImage {

    thumbnail:derivative(style: THUMBNAIL) { url width height } large:derivative(style: LARGE) { url width height } url width height alt title } } }
  57. fieldThumbnailImage { entity { ... on MediaImage { fieldMediaImage {

    thumbnail:derivative(style: THUMBNAIL) { url width height } large:derivative(style: LARGE) { url width height } url width height alt title } } }
  58. fieldThumbnailImage { entity { ... on MediaImage { fieldMediaImage {

    thumbnail:derivative(style: THUMBNAIL) { url width height } large:derivative(style: LARGE) { url width height } url width height alt title } } }
  59. We’ve tried: •Polling for changes (are we there yet?) •NodeJS

    (get a server in the middle, maybe Socket I/O)
  60. Enter Progressive Web Applications •CMS to Application communication is a

    use case •PWAs have Service Workers and Push Notifications
  61. Web Push API •Gives web applications the ability to receive

    messages pushed to them from a server •Requires a subscription to be activated, but once allowed, the subscription is saved by the browser
  62. Web Push API •Gives web applications the ability to receive

    messages pushed to them from a server •Requires a subscription to be activated, but once allowed, the subscription is saved by the browser •You can pass “messages”, but fundamentally you are passing data for your app or website to interpret as you see fit
  63. Web Push API •Gives web applications the ability to receive

    messages pushed to them from a server •Requires a subscription to be activated, but once allowed, the subscription is saved by the browser •You can pass “messages”, but fundamentally you are passing data for your app or website to interpret as you see fit •Browsers handle pushing the data through Service Workers, so a third party is not required
  64. Fine Print •Limited browser support: ✅ Chrome, ✅ Firefox, "

    Safari (coming soon?) •Spec is considered experimental, so it may change in the future
  65. Fine Print •Limited browser support: ✅ Chrome, ✅ Firefox, "

    Safari (coming soon?) •Spec is considered experimental, so it may change in the future •Great for kiosks, but in the wild, you can’t guarantee that a user will subscribe
  66. Fine Print •Limited browser support: ✅ Chrome, ✅ Firefox, "

    Safari (coming soon?) •Spec is considered experimental, so it may change in the future •Great for kiosks, but in the wild, you can’t guarantee that a user will subscribe •Doesn’t help with non-web-based projects (Cinder/ C++, Unity)
  67. Takeaways •Start with your Content, and make that portable •There’s

    many different ways to expose your content •You can do this!