Concepts clés
Vue d'ensemble
Un OAV désigne l'ensemble des écrans utilisés par les professionnels de l'assurance pour vendre un produit.
Le développement d'un OAV à l'aide de l'OIP repose sur le principe convention over configuration : le développeur décrit ce qu'il veut dans des fichiers de configuration, et l'outillage génère le code qui le réalise.
Quatre notions structurent un OAV :
Notion | Question à laquelle elle répond |
|---|---|
Entité | Quelles données l'OAV manipule-t-il ? |
Vue | Quels écrans l'OAV présente-t-il ? |
Transition | Comment passe-t-on d'un écran à l'autre ? |
Tâche | Quels traitements longs l'OAV déclenche-t-il ? |
Les briques logicielles qui les mettent en œuvre sont décrites dans la section « Architecture ».
Modèle de données
Structure générale
Un modèle de données OIP repose sur une structure entité–relation organisée de manière arborescente. L'entité racine est toujours Project.
Toute entité autre que la racine est portée par une entité parente : elle n'existe pas indépendamment de l'arbre du projet.
Définition d'une entité
Une entité est définie par un ensemble de propriétés. Chaque propriété est décrite par :
- un type (e.g. chaîne de caractères) ;
- un ou plusieurs validateurs (e.g. longueur égale à 14 caractères) ;
- une règle RGPD (e.g. purge automatique).
Types disponibles
Type | Variante tableau |
|---|---|
boolean | boolean[] |
date | date[] |
number | number[] |
object | object[] |
string | string[] |
Une propriété peut également porter une énumération (liste fermée de valeurs), une valeur par défaut, ou être déclarée en lecture seule.
Validateurs
Un validateur est une règle évaluée à chaque écriture de la propriété.
Plusieurs validateurs peuvent être combinés sur une même propriété ; ils sont alors évalués en conjonction.
Règles RGPD
Deux mécanismes sont disponibles :
- le chiffrement au repos, qui s'applique à des propriétés désignées d'une entité ;
- la purge automatique, qui s'applique à un type d'entité et supprime l'intégralité de l'arbre associé après une période d'inactivité.
Exemple de modèle simplifié
Ce modèle permet de représenter :
- un projet OAV (Project) contenant des compagnies (Company) et des bénéficiaires (Grantee) ;
- des compagnies associées à des comptes bancaires (BankAccount) ;
- des bénéficiaires associés à des adresses (Address).
erDiagram
Project {
string externalId
}
Company {
string siret
}
Grantee {
string firstName
string lastName
}
BankAccount {
string iban
}
Address {
string country
}
Project }o--o{ Company : has
Project }o--o{ Grantee : has
Company }o--o{ BankAccount : has
Grantee }o--o{ Address : hasDéclaration du modèle
Le modèle se déclare au format OpenAPI. Les entités sont des schémas, les relations des références ($ref) entre schémas.
openapi: 3.1.2
info:
title: MyOAV
version: 4.0.0
components:
schemas:
Project:
type: object
x-fasst-entity-category: OIP_ENTITY_ROOT
description: Root entity containing all other entities
required: ['externalId']
properties:
externalId:
description: External project identifier
type: string
companies:
type: array
items:
$ref: '#/components/schemas/Company'
grantees:
type: array
items:
$ref: '#/components/schemas/Grantee'
Company:
type: object
x-fasst-entity-category: OIP_ENTITY
properties:
siret:
type: string
exactLength: 14
bankAccounts:
type: array
items:
$ref: '#/components/schemas/BankAccount'Génération du code
Le modèle n'est pas interprété à l'exécution : il est compilé.
Pour une entité Company, les fonctions suivantes sont générées :
Fonction | Rôle |
|---|---|
createCompany | Crée une instance et la rattache à son parent. |
getCompany | Récupère une entité, ou null si le critère est ambigu ou non satisfait. |
listCompany | Récupère la liste des entités correspondant au critère. |
updateCompany | Persiste les modifications locales. |
deleteCompany | Supprime l'entité. |
setCompanySiret / getCompanySiret | Écrit / lit la propriété siret. |
Manipulation des données
// Create an entity `Project`
let project = await createProject();
// Create and link an entity `Company` to the entity `project`
let company = await createCompany(project);
// Set property `siret` of the entity `company`
company = setCompanySiret('12345678901234', company);
// Set property `externalId` of the entity `project`
project = setProjectExternalId('aab2d285-0efa-48b6-a222-deeb3c35f258', project);
// Update the entity `company`
company = await updateCompany(company);
// Update the entity `project`
project = await updateProject(project);
// Search for an entity `Company` given property `siret`
company = await getCompany(project, { siret: '12345678901234' });
// Search for an entity `Project` given property `externalId`
project = await getProject({ externalId: 'aab2d285-0efa-48b6-a222-deeb3c35f258' });
// Delete the entity `company`
await deleteCompany(company);
// Delete the entity `project` and all its descendants
await deleteProject(project, { withChildren: true });- Les accesseurs sont curryfiés et retournent une nouvelle entité ; ils ne modifient pas l'entité passée en argument et n'écrivent rien en base ;
- seul l'appel à update<Entity> déclenche la persistance ;
- les validateurs sont appliqués au moment de l'écriture de la propriété, et non à la persistance : une valeur invalide lève une erreur dès l'appel au setter.
Cohérence des données
Un système de transactions permett de garantir la cohérence des données.
Une transaction assemble plusieurs opérations d'écriture en une seule opération « tout ou rien ».
let project;
const transactionId = await transaction();
project = await createProject();
project = setProjectExternalId('1', project);
project = await updateProject(project, { transactionId });
// Here, project's external id is 1
await rollbackTransaction(transactionId);
// Here, project's external id is nullLe modèle transactionnel de l'OIP présente trois particularités :
- il n'y a pas de commit. Chaque écriture est appliquée immédiatement et est visible aussitôt. L'identifiant de transaction ne fait que marquer les écritures ;
- l'annulation est un rejeu inverse. rollbackTransaction relit les états enregistrés sous cet identifiant, du plus récent au plus ancien, et inverse chaque opération : une création devient une suppression, une mise à jour restaure la valeur précédente ;
- l'annulation est elle-même tracée, sous la forme d'une nouvelle transaction, ce qui préserve l'auditabilité.
Vues et transitions
Vues
Une vue représente un écran du parcours. Elle possède :
- un identifiant (viewId), utilisé par le moteur de navigation ;
- une route (pathname), exposée au navigateur ;
- un répertoire (directory), contenant le composant React et ses resolvers GraphQL.
Transitions
Une transition décrit comment quitter une vue. Elle couvre deux directions : next et prev.
Chaque direction se compose d'une liste ordonnée de destinations candidates.
export default {
next: {
hooks: {
// Runs once, before evaluating the handlers; its result is exposed as `ctx.setup`
before: async (ctx) => {
const project = await $Project(ctx.projectId);
return { isCompany: getProjectCustomerType(project) === 'COMPANY' };
}
},
transitions: [
{ viewId: VIEW_IDS.COMPANY_SIRET, handler: (ctx) => ctx.setup.isCompany },
// No handler: always satisfied, acts as a fallback
{ viewId: VIEW_IDS.CUSTOMER_IDENTITY }
]
}
};Le moteur applique une règle simple : la première destination satisfaite l'emporte.
sequenceDiagram
participant View
participant Orchestrator
participant Transitions
View->>Orchestrator: onNext()
Orchestrator->>Transitions: `before` hook
Transitions-->>Orchestrator: ctx.setup
Orchestrator->>Transitions: handlers, in declared order
Transitions-->>Orchestrator: first satisfied destination
Orchestrator->>Transitions: `after` hook
Orchestrator->>View: resolved viewId, then navigationSi aucune destination n'est satisfaite, la résolution échoue explicitement.
Navigation
// Resolve the next view, then navigate
onNext();
// Resolve the previous view, then navigate
onPrev();
// Resolve the next view according to the given args, then navigate
onNext({ transitionData: { foo: 'bar' } });
// Resolve the previous view according to the given args, then navigate
onPrev({ transitionData: { bar: 'foo' } });
// Resolve the next view, then navigate and attach given args to the next view's context
onNext({ resolvedViewData: { foo: 'bar' } });
// Resolve the previous view, then navigate and attach given args to the previous view's context
onPrev({ resolvedViewData: { foo: 'bar' } });Les arguments transitionData et resolvedViewData ne jouent pas le même rôle :
| Rôle | Consommé par | Durée de vie |
|---|---|---|---|
transitionData | Entrée de la décision. | Les handler et les hooks, via le contexte de transition. | Le temps de la résolution. |
resolvedViewData | Sortie transportée vers la vue résolue. | La vue de destination, via le contexte de l'OAV. | Persisté avec le Process ; survit à un rechargement. |
Process
L'état courant de l'OAV est porté par un objet Process, accessible depuis n'importe quel composant ou resolver :
Champ | Description |
|---|---|
projectId | Identifiant du projet courant. |
viewId | Identifiant de la vue active. |
viewData | Métadonnées fournies lors de la dernière transition (resolvedViewData). |
sessionId | Identifiant de la session de navigation. |
Le Process est mis à jour par le moteur à chaque transition, de manière transparente.
Opérations asynchrones
Tâches asynchrones
Des tâches asynchrones peuvent être exécutées et suivies (e.g. envoi d'un e-mail).
Une tâche peut être déclenchée à la demande ou programmée périodiquement (cron).
Le serveur exécute chaque tâche dans un processus dédié : une tâche longue ou en échec ne bloque donc pas l'application. La tâche communique sa progression par messages.
const handler = async () => {
try {
const summary = await doSomething();
process.send?.({ action: 'complete', metadata: summary });
process.exit(0);
} catch (error) {
process.send?.({ action: 'cancel', error: error.message });
process.exit(1);
}
};
if (process.send) process.on('message', handler);Une tâche traverse trois états : executing, puis completed ou cancelled.
Côté client, des fonctions permettent de déclencher la tâche et s'abonner à ses notifications :
// Start an async task `SendEmail`
onRunTask({
taskName: 'SendEmail',
metadata: { foo: 'bar' }
});
// Subscribe to the task notifications
const { data, error } = useSubscription(TaskSubscription, {
taskName: 'SendEmail'
});
// data: { taskId, taskName, projectId, state, metadata, error }Enchaînement de tâches asynchrones
Des enchaînements (workflows) de tâches asynchrones peuvent également être exécutées (e.g. procédure de signature électronique).
Un workflow est un graphe orienté acyclique de tâches. Une tâche ne démarre que lorsque toutes ses tâches parentes sont terminées, et hérite automatiquement de leurs métadonnées.
flowchart LR
A[GenerateDocument] --> C[SendToSignature]
B[FetchCustomerData] --> C
C --> D[NotifyAdvisor]// Start an async task workflow `SendToSignature`
onRunTaskWorkflow({
workflowName: 'SendToSignature',
metadata: { foo: 'bar' }
});
// Subscribe to the tasks workflow' notifications
const { data, error } = useSubscription(TaskWorkflowSubscription, {
workflowName: 'SendToSignature'
});
// data: { taskId, taskName, workflowName, projectId, state, metadata, error }L'abonnement à un workflow notifie chaque changement d'état de chaque tâche du workflow : le champ taskName permet d'identifier l'étape concernée.
Glossaire
Terme | Définition |
|---|---|
OAV | Outil d'Aide à la Vente, aussi appelé parcours de vente. |
Entité | Objet représentant une donnée métier ou technique, identifié et décrit par un dictionnaire clé/valeur. |
Propriété | Donnée élémentaire d'une entité, typée et validée. |
Relation | Lien de parenté entre deux entités, constitutif de l'arbre du projet. |
Vue | Écran du parcours, identifié par un viewId et exposé sur une route. |
Transition | Règle de passage d'une vue à une autre, évaluée côté serveur. |
Process | État courant de l'OAV : projet, vue active, métadonnées de vue, session. |
Décrochage | Phase d'entrée dans l'OAV : résolution du contexte client, du projet et de la première vue. |
Tâche | Traitement asynchrone exécuté hors du cycle de requête. |
Workflow de tâches | Enchaînement de tâches organisé en graphe orienté acyclique. |
Transaction | Marqueur regroupant des écritures, permettant leur annulation groupée. |
Trame projet | Arbre complet des entités. |
Résumé
- Un OAV se décrit par quatre notions : les entités, les vues, les transitions et les tâches ;
- le modèle de données est déclaratif et arborescent : il est compilé en code, jamais interprété à l'exécution ;
- les écritures sont immédiates, historisées et annulables par transaction ;
- la navigation est résolue côté serveur, à partir de gardes évalués dans l'ordre de déclaration ;
- les traitements longs sont exécutés hors du cycle de requête et suivis par souscription.