Retour d'expérience - création d'une API avec Drupal pour MK2.com.

Anne-Sophie Picot - @asplamagnifique
Fabien Clément - @GozOo

Par @CommerceGuys by @actualys

Ce que l'on va voir

  • MK2.com
  • Etat de l'art API V2
  • Objectifs V3
  • Réalisation
  • Problèmes et résolutions liées à Drupal (ou non)
  • Aujourd'hui
  • Demain
  • Conclusion

MK2.com

Site développé avec Drupal et Drupal Commerce en 2013 par Commerce Guys.

Présente l'ensemble des salles parisiennes du groupe et permet de réserver en ligne leurs séances.

MK2.com

Les fonctionnalités du site:

  • Salles du groupe MK2
  • Séances de films et d'événements, réservables ou non
  • Parcours d'achat
  • Achat et utilisation de cartes de réservations online/irl
  • Solution de paiement intégrée
  • API V2 limitée pour application mobile tiers hybride.
  • Connexion à un système tiers pour la synchronisation des données métier (salles, films, seances, prix, cartes etc) et de réservation.

Etat de l'art API V2

Mis en place avec plusieurs modules contrib : tels que Services et Views et échange en JSON en se basant sur REST.

  • L'API V2 ne donne pas accès à l'ensemble des données disponibles sur le site
    • pas d'événements
    • pas de recherche
    • pas de contenu lié aux utilisateurs: carte, historique, reservation, données utilisateur).

Etat de l'art API V2

  • L'API V2 ne permet pas d'effectuer de réservation.
  • L'API V2 manque de finesse dans la segmentation des droits d'accès aux données.
  • L'API V2 n'est pas optimisée.
  • L'API V2 produit des contenus non standardisés suivant les différentes ressources.

Requêtes et traitements inutiles

  • À minima une requête pour lister les données souhaitées.
  • Appelle d’une vue par donnée à afficher, à savoir qu’une vue est stockée en base de données. Nous avons donc:
    • chargement de la configuration de la vue
    • chargement de l’objet de la donnée.
  • Génération au format json du résultat par chacune de ces vues.

Requêtes et traitements inutiles

  • Décodage du json en variables PHP pour chacune des données.
  • Envoi de la liste des données en PHP au module services.
  • Encodage en json des données par le module services pour envoi à l’application tiers.
  • Aucun cache.
Schema fonctionnement API V2 MK2.com
Schema 2 fonctionnement API V2 MK2.com

Problème de standardisation

  • Résultat dans des tableaux de tableaux ou dans un tableau unique suivant les requêtes.
  • Pas de consistance dans l'affichage des objets suivant les appels.
  • Pas de règles définies.

Listing des films


[
  [
    {
      "nid": "123",
      "épinglé": "0",
      "title": "Demain",
      ...
    },
    {
      "nid": "456",
      "épinglé": "1",
      "title": "Hier",
      ...
    },
    ...
  ]
]
                        

Affichage d'un film


[
	{
	  "nid": "123",
	  "épinglé": "0",
	  "title": "Demain",
	  ...
	}
]
                        

Listing des séances


[
  [
    {
      "seance_id": "123",
      "seance_date": "06/09/2016 - 00:00",
      "seance_visa": "140040",
      ...
    },
    {
      "seance_id": "124",
      "seance_date": "07/09/2016 - 00:00",
      "seance_visa": "140041",
      ...
    },
    ...
  ]
]
                        

Objectifs V3

Les 3 objectifs que nous nous sommes fixés pour la mise en place de ces nouvelle API sont:

  • La sécurité: l’accès aux différents services doit être sécurisé et administrable facilement.
  • La standardisation: l’ensemble des services doit utiliser une logique et une syntaxe similaire afin d’être utilisable par des interlocuteurs variés.
  • L’évolutivité: l’API mk2 doit être conçue comme un produit permettant d’intégrer les versions antérieures et de faciliter la mise en place des évolutions à venir.

Étude et choix de la solution

  • Le site fournit toujours l’api avec drupal.
  • Création d'une nouvelle version en conservant la v2 fonctionnelle sans conflit.
  • Conserver l’utilisation du module services.
  • Création d’un nouveau service (v3) utilisant des ressources sans lien avec les ressources du service précédent (v2).

Étude et choix de la solution

  • Remplacement de views pour la génération en json par la création d’objets PHP pouvant être sérialisés.
  • Sérialisation seulement lors de la réponse.
  • Toutes les réponses doivent se faire en json dans un format défini et consistant.

Méthode

Ce projet comportait la complexité de devoir être mené en parallèle du développement d’une nouvelle application iOS. Cette coordination de 2 projets a nécessité :

  • La création d’une documentation technique collaborative sur GitHub entre les développeurs Drupal et les développeurs mobiles.
  • Un point quotidien avec l’ensemble des acteurs des deux projets.
  • Un planning des tâches et des priorités mixant les deux projets et mis à jour au quotidien.

Solutions techniques utilisées

Création d'un module contrib Services API Keys Authentication (services_auth_apikeys)

Ce module permet de définir des clés d'authentifications liées à un utilisateur drupal.

  • Les utilisateurs seront alors attachés à un rôle Drupal.
  • Dans l'interface des ressources, il est possible de restreindre l'accès aux ressources par rôles.
  • Ces utilisateurs drupal seront dissociables des utilisateurs connectés et peuvent donc uniquement être utilisés pour savoir quelle application est en train de se connecter au webservice et si elle a le droit d'accéder à l'une ou l'autre des ressources.
Schéma Services API Keys Authentication

Création d'un module contrib Services session Token Authentication (services_session_token_auth)

  • Ce module permet de connecter un utilisateur en se basant sur un couple de clés (tokens) envoyés en en-tête http et de ne pas prendre en compte les éventuelles cookies de session.
  • Lors de sa première connexion via son application, l'utilisateur reçoit un couple de clés qui lui permettra de se connecter ensuite avec son utilisateur sans devoir renvoyer son identifiant et son mot de passe.
  • Le mot de passe de l'utilisateur n'a pas à être stocké dans l'application.

Standardisation des résultats

Les requêtes sont toujours construites directement dans la ressource:


/**
 * Gets an event encoded in JSON.
 *
 * @param int $id
 *   Id of the event.
 * @param string $includes
 *   The objects this object should return.
 *
 * @return
 *   Returns null if the event doesn't exist or the JSON encoded node.
 */
function _events_resource_retrieve($nid, $includes) {
  if ($node = node_load($nid)) {
    $node_wrapper = entity_metadata_wrapper('node', $node);
    $includes = _mk2_webservices3_get_includes_as_array($includes);
    _mk2_webservices3_validate_includes($includes, array('movies'));

    $out = new Mk2Webservices3Event($node_wrapper, $includes);
  }
  else {
    $message = t('The event !nid does not exist!', array('!nid' => $nid));
    services_error($message, 404, array(
        'error' => $message,
        'error_node' => 'events.not_found'
      ));
    return NULL;
  }

  return array('event' => $out);
}
                        

Standardisation des résultats

Le json est généré à partir d'objets (classes)


class Mk2Webservices3Event implements JsonSerializable {

  /**
   * Constructor.
   */
  public function __construct($wrapper, $includes = array(), $data = array()) {
  	// ...
  }

  // Getters/setters
  // ...

  /**
   * function called when encoded with json_encode.
   *
   * @return array
   */
  public function jsonSerialize()
  {
    return get_object_vars($this);
  }
}
                        

Standardisation des erreurs

Utilisation de la méthode services_error() du module services pour générer des erreurs:


$message = t('The event !nid does not exist!', array('!nid' => $nid));
services_error($message, 404, array(
  'error' => $message,
  'error_code' => 'events.not_found'
));
                        

Ce qui affiche:


	{
		"error": "The event 123456 does not exist!",
		"error_code": "events.not_found"
	}
	                        

Standardisation des erreurs

Utilisation d'une méthode générique pour formater les résultats de formulaires en erreur ce qui affichera:


{
	"error": "Field name is required. Birthday date cannot be in future.",
	"error_code": "user.form.error",
	"form_errors" {
		"name": {
			"error": "Field name is required.",
			"error_code": "user.form.name",
		},
		"field_birthday": {
			"error": "Birthday date cannot be in future.",
			"error_code": "user.form.field_birthday",
		}
	}
}
                        

Standardisation des erreurs

Utilisation du hook hook_rest_server_execute_errors_alter() pour surcharger les messages d'erreurs non gérés de manière à ce qu'ils suivent la structure souhaitée.


/**
 * Implements hook_rest_server_execute_errors_alter().
 */
function mk2_webservices3_rest_server_execute_errors_alter(&$error_alter_array, $controller, $arguments) {
  if (!is_array($error_alter_array['body_data'])) {
    $error_alter_array['body_data'] = array(
      'error' => $error_alter_array['body_data'],
      'error_code' => 'generic.error.' . $error_alter_array['code'],
    );
  }
}
                        

Sécurisation des échanges

  • Tous les échanges envoyés et reçus pour et par les webservices doivent désormais se faire par SSL.
  • Accès aux ressources limitées par authentification de clé par application.

Gestion du cache

  • Mise en cache Drupal des résultats
  • Utilisation de cache Varnish
    • Le cache est affiné par authentification d'application.
    • Le cache est affiné par utilisateur pour les ressources utilisateurs.

Problèmes et résolutions liées à Drupal (ou non)

  • Services module: Le module services ne permet pas une utilisation complète REST. Il ne permet pas par exemple les PUSH et est limité dans son approche de méthode/routing.
  • Authentification d'application
    • Aucun module ne permet d'avoir à la fois une authentification de l'application se connectant au service via des clés et une authentification utilisateur.
    • Résolution via la création du module contrib services_auth_apikeys.

Problèmes et résolutions liées à Drupal (ou non)

  • Authentification utilisateurs
    • Ne pas prendre en compte les cookies comme un utilisateur authentifier mais se baser sur une authentification par clés uniquement.
    • Résolution via la création du module contrib services_session_token_auth.

Problèmes et résolutions liées à Drupal (ou non)

  • Erreurs textuelles générées non utilisables par des ws (ex commerce_stripe)
    • Les erreurs sont généralement affichées directement via drupal_set_message() et ne sont pas retournées par les méthodes, ce qui est inutilisable pour des retours d'erreur par WS.

Problèmes et résolutions liées à Drupal (ou non)

  • Erreurs textuelles générées non utilisables par des ws (ex commerce_stripe)
    • Solutions: dans les cas possibles, patch du module pour retourner des messages dans les fonctions qui lèvent l'exception plutôt que d'afficher le message directement. Certains modules ne permettent pas cela, ce qui nécessitait un travail trop important pour le temps prévu.

Aujourd'hui

MK2 sur Allociné

  • Le site Allociné.com exploite les API mk2 depuis le début de l'année 2016.
  • Ses clés d'authentifications et son rôle lui permettent d'accéder uniquement au contenu. Il n'a donc pas accès aux données utilisateur, au processus d'achat, de paiement etc.
  • Toutes les pages salles Mk2 du site Allociné sont servies par les API Mk2.
Capture intégration API MK2 sur Allociné

Application mobile iOS

La version 3 de l’application iOS mk2 exploite plusieurs services des nouvelles API mk2

  • Recherche de contenu.
  • Données utilisateurs.
  • Transactions via différents moyen de paiement (cartes bancaires et cartes de cinéma prépayées).

L'accès à l’ensemble du parcours client (contenu > authentification > paiement) via API permet de garder l’utilisateur dans un environnement 100% natif iOS et donc de proposer une expérience utilisateur fluide.

Capture intégration API MK2 dans application mobile iOS Capture intégration API MK2 dans application mobile iOS Capture intégration API MK2 dans application mobile iOS

Cette nouvelle application est disponible gratuitement sur l’Appstore.

Capture intégration API MK2 dans application mobile iOS

Demain

Sur le moyen-terme les API Drupal mk2 pourront permettre de développer:

  • Des logiciels (affichage d’informations, système de caisses... ).
  • Des outils métiers (CRM, BI…).
  • De nouvelles applications mobiles (Android).
  • De nouveaux business model (affiliation, acquisition de trafic..).

Conclusion

Ce projet comportait de nombreux enjeux:

  • Rétro-compatibilité
  • Sécurité.
  • Délais
  • Évolutivité.

et a pu être réalisé grâce à:

  • Un travail de conception poussé
  • Une solution technique adaptée.
  • Une gestion de projets que l’on pourrait qualifier d’agile

{
  error: "Merci",
  error.code: "generique.de.fin",
}