Refonte de l'espace client
Bouygues Telecom
sbachelier
carlogren
Présentation
- Contexte du projet
- Rework
Contexte du projet
La motivation
- Meilleure expérience client
- Vue 360 : Multi-device / Omnicanal
- Simplicité
- Performance
- Transformation profonde du SI
- Simplification / Modernisation : WOA
- Réduction des coûts
L'existant
-
Espace Client COnvergeant (ECCO pour les intimes)
-
Architecture Client / Serveurs "classique" SOA
-
PHP / Zend
-
Web Services SOAP/XML
-
Choix de Backbone
- Flexibilité / Maîtrise du code
- Ecosystème important
- Annonce d'Angular 2
Organisation
Méthode Agile
SCRUM - Sprint 2 semaines
- 1 Team Leader
- 1 Arch. Technique
- 1 Développeur
- 1 Team Leader
- 1 PO (MOA)
- 1 Arch. Technique
- 1 Resp. Etudes
- 2 Développeurs
- 1 Intégrateur
- 1 Team Leader
- 1 PO (MOA)
- 1 Arch. Technique
- 1 Resp. Etude
- 3 Développeurs
- 1 Intégrateur
Janvier (POC)
Avril
Juin (Shift tech.)
Dream Team
- 1 Team Leader
- 1 PO (MOA)
- 1 Arch. Technique
- 1 Resp. Etude
- 3 Développeurs
- 1 Intégrateur
- 1 Analyste / Tests
Octobre (MEP)
Rework
- Janvier 2015 : Lancement
- Juillet 2015 : Shift
- Octobre 2015 : MEP 1.0
Lancement de la refonte
- Création d'un POC
- Une première version avec Backbone et Thorax
- Authentification via OAuth (JWT)
- Dashboard
- Synthèse mono ligne
- Navigation
- Responsive (Desktop, Tablette, Mobile)
Shift technique
Juillet 2015
Socle technique
Backbone.Marionette
Require.js
Workflow avancé avec Grunt
Tests unitaires avec Karma, Mocha, Chai, Sinon
Correspond à l'arrivée de nouvelles ressources
Contraintes
Plus que 3 mois pour la refonte avant MEP
Démo fonctionnelle en fin de sprint
Construction des ressources en // de l'application
Cohabitation avec certaines parties de l'espace client
Congés d'été
Besoins fonctionnels
Intégration de marketing ciblé (Revive Ad Server)
Sécurité (HTTPS, SSO)
Intégration et interconnexion avec SalesForce
Exploitation de la nouvelle API REST
Cohabitation du nouvel espace client avec l'existant
Monitoring (Kibana)
Analytics (XiTi)
Un peu de technique :)
Configuration
-
Cinq environnements principaux
Dev, Recette, Intégration, Pre-prod, Prod
Gestion des URLs différentes suivant les environnements
-
i18n : Une seule langue (FR)
Volonté de séparer tous les champs textes
Gestion effectuée par la MOA
Utilisation du format YAML
Automatisation avec des tâches Grunt
Boot Loader
S'exécute avant le lancement de l'application
-
Un certain nombre de vérifications sont effectuées
Authentification
Test du profil client (redirection)
Participation à la beta
Initialisation de certaines données
Authentification
-
Utilisation d'un SSO (Single Sign ON)
-
OAuth 2
-
JWT (JSON Web Token)
-
HTTPS !!!
-
Token généré et signé par le serveur
-
Décrypté côté client :
-
Librairie jsrasign
-
Contient un certain nombre de claims, dont la date d'expiration
-
Extraction des informations utilisateur
-
Authentification
Récupération du token (URL ou sessionStorage)
Validation du token au lancement de l'application
-
Redirection vers la mire d'authentification si besoin
token non présent
token invalide
token expiré
Authentification
-
Authentification des requêtes API
-
utilisation de l'access token renvoyé par l'OAuth
-
envoyé via un header HTTP
-
-
Détection des status code 401
-
redirect vers OAuth
-
API
REST ~ HATEOAS : attribut _links
GET /resource
{
"id": 1234,
"firstname": "Jean",
"lastname": "Dupont"
}
GET /resource
{
"id": 1234,
"firstname": "Jean",
"lastname": "Dupont",
"_links": {
"factures": { "href": "...." },
"lignes": { "href": "...." },
"adressesFacturation": { "href": "...." }
}
}
API
-
Avantages
API auto-découvrante
Un seul point d'entrée : la ressource personne
-
Inconvénients
Beaucoup d'appels
Un appel par ressource
API
-
GET uniquement
Choix délibéré d'implémentation
-
POST/PUT/DELETE
Opérations réalisées sur l'espace client
API
-
Avantages
Cycle de livraison plus rapide
Impact réduit sur la sécurité
Suit le cycle de livraison des ressources
-
Inconvénients
Rediriger l'utilisateur vers l'espace client actuel
Trouver une solution pour l'expérience utilisateur
Templates
-
Utilisation de Handlebars
Compilation des templates avec Grunt
Utilisation du runtime dans le navigateur
Nombreux helpers métiers
Génération d'un seul fichier templates.js
Renderer
Customisation du Marionette.Renderer
-
Motivation :
utilisation de require.js
découplage vues / templates :
Utilisation d'une référence
// Vue avant
Mn.ItemView.extend({
template: templates['offre-ligne']
});
// Vue avant
Mn.ItemView.extend({
template: templates['offre-ligne']
});
// Vue après
Mn.ItemView.extend({
template: 'offre-ligne'
});
Renderer
Implémentation simplifiée
var templates = require('templates');
Marionette.Renderer.render = function (name, data) {
return this.renderString(name, data);
};
Marionette.Renderer.renderString = function (name, data) {
if (!templates[name]) {
throw new Error('template [' + name + '] not found.');
}
return templates[name](data);
};
var templates = require('templates');
Marionette.Renderer.render = function (name, data) {
return this.renderString(name, data);
};
Bus Event
Centralisation de la distribution d'évènements
Meilleur découplage des différents composants/vues
-
Choix de Postal.js
Présence de channel
Wildcard (*, #) sur les topics
Utilisation d'une "enveloppe"
Bus Event
Cas d'utilisation : Backbone.history.navigate
handleLinkClick: function (e) {
var $target = $(e.currentTarget);
bus.publish('navigate', $target.attr('href'));
}
Marionette.AppRouter.extend({
initialize: function () {
this.subscriptions = [
bus.subscribe('navigate', _.bind(this.handleNavigate, this))
];
},
handleNavigate: function (path) {
// business logic ...
this.navigate(path, {trigger: true});
}
});
Monitoring
-
Génération de log depuis la SPA
Fonctionnel : Affichage des vues
Technique : Récupération des données
Stockage dans un Elastic Search
Visualisation avec Kibana
Monitoring
Cohabitation ECCO / A360
Intégration d'ECCO dans A360
-
Sous la forme :
d'un lien vers une autre page
ouverture d'une fenêtre (popup)
redirection vers une route interne
ouverture d'un bloc
Cohabitation ECCO / A360
Les liens vers l'espace client actuel (ECCO)
Cohabitation ECCO / A360
Page interne => IFrame
IFRAME
Cohabitation ECCO / A360
1
2
Injection d'un bloc HTML
=> IFrame
Cohabitation ECCO / A360
-
Problématique :
-
A360 est utilisé par différents profils
-
client
-
conseiller de clientèle (CDC)
-
conseiller de vente (CDV)
-
-
-
Problématique :
-
A360 est utilisé par différents profils
client
conseiller de clientèle (CDC)
conseiller de vente (CDV)
-
Chargement :
application web (client)
iframe dans salesforce (CDC)
webview dans application métier (CDV)
-
Cohabitation ECCO / A360
-
Lien vers ECCO dépend du profil de la personne
le lien
des paramètres à passer (query)
-
comportement d'affichage
iframe
bloc
window
target (blank, self)
-
ECCO :
~60 parcours utilisateurs à intégrer
Cohabitation ECCO / A360
-
Solution retenue
paramétrisation suivant le profil
paramétrisation des comportements
-
Utilisation de fichiers de configuration
gestion des paramètres
configuration des liens
configuration des routes internes
Paramétrisation du lien
type:
iframe:
query: display=frame
target: iframe
Définition des types:
- lien interne / externe
- query d'un lien
- target d'un élément A
- attribut DOM injecté
type:
iframe:
query: display=frame
target: iframe
newpage:
target: _blank
dataTarget: a360
type:
iframe:
query: display=frame
target: iframe
newpage:
target: _blank
dataTarget: a360
self:
target: _self
dataTarget: a360
type:
iframe:
query: display=frame
target: iframe
newpage:
target: _blank
dataTarget: a360
self:
target: _self
dataTarget: a360
window:
query: display=frame
dataTarget: window
type:
iframe:
query: display=frame
target: iframe
newpage:
target: _blank
dataTarget: a360
self:
target: _self
dataTarget: a360
window:
query: display=frame
dataTarget: window
widget:
query: display=frame
dataTarget: widget
Paramétrisation du profil
user:
client:
type: iframe
Comportement par défaut pour chaque profil
user:
client:
type: iframe
cdc:
type: window
query: login=:login
user:
client:
type: iframe
cdc:
type: window
query: login=:login
cdv:
type: iframe
query: login=:login
Exemple de surcharge
reinitialisercodeparental:
changementMDP:
reinitialisercodeparental:
query: ctype=p&no_reference=:contractId
reinitialisercodeparental:
query: ctype=p&no_reference=:contractId
user:
client:
changementMDP:
user:
cdc:
query: type=interne
changementMDP:
user:
client:
type: self
query: CallbackURL=${url.a360}
changementMDP:
user:
cdc:
query: type=interne
client:
type: self
query: CallbackURL=${url.a360}
reinitialisercodeparental:
query: ctype=p&no_reference=:contractId
user:
client:
type: widget
Mise en place des liens
-
~60 liens à mettre en place
interne (router)
externe
3 types de profils principaux
soit ~180 boutons ?
=> il doit y avoir une solution plus simple et plus "propre"
Mn.Behaviors
Utilisation d'un behaviour
<a {...}
data-link-type="external"
>
Changement mot de passe
</a>
<a {...}
data-link-type="external"
data-link="changementMDP"
>
Changement mot de passe
</a>
Mise en place du lien dans le template
Utilisation d'un behaviour
<a {...}
data-link-type="external"
data-link="changementMDP"
>
Changement mot de passe
</a>
Manipulation de la balise A par le behaviour
<a {...}
data-link-type="external"
data-link="changementMDP"
href="<link>?ctype=p&display=frame&ctype=p&no_reference=1234"
target="_self"
>
Changement mot de passe
</a>
Utilisation d'un behaviour
Marionette.ItemView.extend({
behaviors: {
},
});
Marionette.ItemView.extend({
behaviors: {
EccoButton: {
behaviorClass: behaviors['ecco-button']
}
},
});
Marionette.ItemView.extend({
behaviors: {
EccoButton: {
behaviorClass: behaviors['ecco-button']
}
},
eccoData: function() {
var contrat = this.options.contract;
return {
contractId: contract.id
};
}
});
Mise en place du behaviour
Utilisation d'un behaviour
<a
{...}
data-link-type="external"
>
Réinitialiser code parental
</a>
<a
{...}
data-link-type="external"
data-link="reinitialisercodeparental"
>
Réinitialiser code parental
</a>
<a
{...}
data-link-type="external"
data-link="reinitialisercodeparental"
data-widget-target="reinitcode-widget"
>
Réinitialiser code parental
</a>
<div data-widget="reinitcode-widget"></div>
IFrame
Injection d'une IFrame au travers d'une Mn.ItemView
-
Ajout des écouteurs sur les évènements :
onload
onresize
onmessage (et postMessage)
Non supporté par la propriété "events" d'une vue
Cohabitation ECCO / A360
"Sticky bar"
Menu contextuel commun à tous les sites BouyguesTelecom
=> Réécriture du lien une fois la barre chargée
Sticky Bar
Injection d'un script pour l'affichage du menu
Nécessité de modifier le lien
-
Problème
comment s'assurer que l'HTML est bien injecté pour pouvoir modifier le lien
-
Solution
MutationObserver
MEP 1.0 (Beta)
Début octobre
-
Ouverture progressive
CDC / CDV
3% des clients
Fin prévisionnelle de la beta en janvier 2016
-
Amélioration des performances
attente fortement réduite sur A360
pré-chargement < 0.5s (A360) contre ~2s (ECCO)
moins de données échangées
navigation beaucoup plus fluide
Merci :)
ByTel
By stephane bachelier
ByTel
- 2,114