Commit a2fc30e8 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Amélioration du README

parent 1b08d852
Loading
Loading
Loading
Loading
+126 −84
Original line number Diff line number Diff line
@@ -16,7 +16,8 @@ UnicaenDbImport
   5. [Synchronisation de données distantes avec récupération des clés étrangères](#exemple-5-synchronisation-de-données-distantes-avec-récupération-des-clés-étrangères)
   6. [[Bonus] Appliquer un CRON](#exemple-z-appliquer-un-cron)
 
*NB: Refonte en cours, plus d'information sur l'ancien moteur (peut être encore actuel) d'UnicaenDbImport en [cliquant ici](https://git.unicaen.fr/lib/unicaen/db-import/tree/2ceb3a7e#dans-le-moteur).*
*NB: Refonte en cours, plus d'information sur l'ancien moteur (peut être encore actuel) d'UnicaenDbImport 
en [cliquant ici](https://git.unicaen.fr/lib/unicaen/db-import/tree/2ceb3a7e#dans-le-moteur).*

 
Introduction
@@ -25,6 +26,8 @@ Introduction
Ce module réalise l'import et/ou la synchronisation de données *sources* vers une table d'une base de données 
*destination*. 

L'import et la synchronisation sont 2 mécanismes distincts.

La source peut être :
- soit une base de données (une table ou un "select"), 
- soit une API (web service).
@@ -34,7 +37,6 @@ Principe de l'*import* :
  - Les données obtenues de la source sont insérées dans la table destination.

Principe de la *synchronisation* :

  - Si la source contient un enregistrement qui n'existe pas dans la destination, il est ajouté dans cette dernière.
  - Si la source contient un enregistrement qui existe aussi dans la destination avec les mêmes valeurs de colonnes, 
    rien n'est fait.
@@ -49,6 +51,8 @@ Les données source et les enregistrements destination doivent avoir un identifi
de les rapprocher : on l'appellera "code source" (cf. paramètre de config `source_code_column`). 
**Cet identifiant DOIT être de type chaîne de caractères.**

Afin de faciliter l'adaptation au plus grand nombre de SGBD, UnicaenDbImport s'appuie sur l'_ORM Doctrine 2_.


La différence avec le module UnicaenImport ?
--------------------------------------------
@@ -102,14 +106,15 @@ cp -n vendor/unicaen/db-import/config/unicaen-db-import.local.php.dist config/au
],
```


Utilisation
-----------

Le module possède deux mécanismes distincts :
1. import : réalise un import "brut" des données, autrement dit une copie, d'une source vers une destination;
1. import : réalise un import "brut" des données, autrement dit une copie de données d'une source vers une destination ;
2. synchro : réalise une synchronisation entre une source et une destination en gérant une historisation (created_on, updated_on, deleted_on).

Le module fournit donc une ligne de commande pour lancer :
Le module fournit une ligne de commande pour lancer :

  - un import par son nom :
  
@@ -135,8 +140,8 @@ Le module fournit donc une ligne de commande pour lancer :
    public/index.php run synchro --all
    ```

*NB: L'exécution d'une de ces commandes n'est effectif qu'une fois. Pour importer/synchroniser en permanence (dans) 
la base de données destination, il faut programmer le lancement périodique de cette commande à l'aide de CRON.* 
*NB: Chacune de ces commandes ne réalise l'import ou la synchronisation qu'une seule fois. 
Pour importer/synchroniser en permanence, il faut programmer l'exécution périodique de la commande à l'aide de CRON.* 
Exemple : `*/15 6-19 * * 1-5 root /usr/bin/php /path/to/app/public/index.php run import --all 1> /tmp/zebu-cron.log 2>&1`


@@ -145,13 +150,15 @@ Contraintes

### Identifiant commun

L'identifiant unique commun des enregistrements (`source_code_column`) source et destination doit être de type 
**chaîne de caractères**.
Les données sources et destination doivent posséder un identifiant unique commun (cf. paramètre de config 
`source_code_column`). Cet identifiant doit être de type **chaîne de caractères**.

### Table destination

La *table destination* doit obligatoirement posséder les colonnes d'historique `created_on`, `updated_on` 
et `deleted_on`. Exemple avec PostgreSQL :
La *table destination* doit obligatoirement posséder les colonnes permettant de gérer l'historique des données :
`created_on`, `updated_on` et `deleted_on`. 

Exemple avec PostgreSQL :
```sql
ALTER TABLE TABLE_DESTINATION ADD COLUMN created_on TIMESTAMP(0) WITH TIME ZONE DEFAULT LOCALTIMESTAMP(0) NOT NULL;
ALTER TABLE TABLE_DESTINATION ADD COLUMN updated_on TIMESTAMP(0) WITH TIME ZONE;
@@ -162,51 +169,50 @@ ALTER TABLE TABLE_DESTINATION ADD COLUMN deleted_on TIMESTAMP(0) WITH TIME ZONE;
Fonctionnement
--------------

### Import de données
### Import

Afin de s'adapter au plus grand nombre de SGBD, UnicaenDbImport s'appuie sur l'_ORM Doctrine 2_.
Ainsi nous effectuons le déroulement suivant :
Schéma du déroulement d'un import :

<!--Voir le dossier documentation/ pour toutes modifications-->
![fonctionnement_import.png](documentation/fonctionnement_import.png)

*Schématisation du déroulement d'un import*

1. Suppression des données existantes dans _Destination_
2. Récupération des données de la _Source_
3. Génération des requêtes SQL (insert, update, ...) depuis PHP
4. Exécution des requêtes / Récupération des données vers _Destination_


### Synchronisation de données locales
### Synchronisation standard

L'un des mécanismes d'UnicaenDbImport est d'appliquer un système de synchronisation avec gestion d'une historisation.
Avec une table _Source_ et une table _Destination_ toutes deux dans une base locale, le déroulement est le suivant :
Il s'agit de synchroniser le contenu d'une table destination avec celui d'une table (ou d'un "select") source, 
avec gestion d'historique (date de création, de modification, de suppression).
**Les données sources et destination doivent se trouver dans la même base de données.**

Schéma du déroulement d'une synchro :

<!--Voir le dossier documentation/ pour toutes modifications-->
![fonctionnement_synchro_locale.png](documentation/fonctionnement_synchro_locale.png)

*Schématisation du déroulement d'une synchro (locale)*

1. Création d'une vue différentielle
2. Préparation/détection des actions à appliquer (INSERT, UPDATE, ...)
2. Préparation/détection des actions à appliquer (INSERT, UPDATE, DELETE, UNDELETE)
3. Application des mises à jour (avec historisation : created_on, updated_on, ...)


### Synchronisation de données distantes

Concernant le mécanisme de synchronisation, il est possible de spécifier une _Source_ de type table/select provenant 
d'une base distante, ou de type API (web service).
Dans ce cas, les mécanismes d'[import](#import-de-données) et de [synchro (locale)](#synchronisation-de-données-locales) 
seront réalisés successivement.
Concernant le mécanisme de synchronisation, il est possible de spécifier une _Source_ de type :
- table/select provenant d'une autre base que celle de Destination, 
- API (web service).
Dans ces 2 cas, les mécanismes d'[import](#import-de-données) et de [synchro (locale)](#synchronisation-de-données-locales) 
seront réalisés l'un après l'autre.

Schématisation du déroulement d'une synchro (distant) :

<!--Voir le dossier documentation/ pour toutes modifications-->
![fonctionnement_synchro_distant.png](documentation/fonctionnement_synchro_distant.png)

*Schématisation du déroulement d'une synchro (distant)*

1. Déclenchement du mécanisme d'import de la _Source_ vers une table _Temporaire_
2. Déclenchement du mécanisme de synchro entre la table _Temporaire_ et la table _Destination
2. Déclenchement du mécanisme de synchro entre la table _Temporaire_ et la table _Destination_
3. Suppression de la table _Temporaire_


@@ -214,22 +220,23 @@ Exemples
--------

Pour les exemples suivants, on suppose posséder :
 * 1 Base de donnée **A** locale (celle de notre application) contenant les tables :
   * **UTILISATEUR**(int ID, str CODE, str NOM, str PRENOM, date NAISSANCE)
   * **AUTRE_UTILISATEUR**(str CODE, str NOM, str PRENOM, date NAISSANCE)
   * **COMPOSANTE**(int ID, str CODE, str NOM)
   * **FORMATION**(int ID, str CODE, str NOM, str COMPOSANTE_ID)
   * **PAIN_AU_CHOCOLAT**(int ID, str NOM, str BOULANGERIE)
 * 1 Base de donnée **B** distante (par exemple Apogée) contenant les tables :
   * **UTILISATEUR**(int ID, str CODE, str NOM, str PRENOM, date NAISSANCE)
   * **COMPOSANTE**(int ID, str CODE, str NOM, int DIRECTEUR_ID, str ADRESSE)
   * **FORMATION**(int ID, str CODE, str NOM, str COMPOSANTE_CODE)
   * **CHOCOLATINE**(int ID, str NOM, str BOULANGERIE, int POURCENT_GRAS, int NOTE)
 * 1 Base de donnée **A** (celle de notre application) contenant les tables :
   * `UTILISATEUR`(int ID, str CODE, str NOM, str PRENOM, date NAISSANCE)
   * `AUTRE_UTILISATEUR`(str CODE, str NOM, str PRENOM, date NAISSANCE)
   * `COMPOSANTE`(int ID, str CODE, str NOM)
   * `FORMATION`(int ID, str CODE, str NOM, str COMPOSANTE_ID)
   * `PAIN_AU_CHOCOLAT`(int ID, str NOM, str BOULANGERIE)
 * 1 Base de donnée **B** différente de A (par exemple Apogée) contenant les tables :
   * `UTILISATEUR`(int ID, str CODE, str NOM, str PRENOM, date NAISSANCE)
   * `COMPOSANTE`(int ID, str CODE, str NOM, int DIRECTEUR_ID, str ADRESSE)
   * `FORMATION`(int ID, str CODE, str NOM, str COMPOSANTE_CODE)
   * `CHOCOLATINE`(int ID, str NOM, str BOULANGERIE, int POURCENT_GRAS, int NOTE)
 
 
En outre, on suppose avoir déclaré les configurations _Doctrine_ 'orm_A' et 'orm_B' respectivement pour les bases **A** et **B** ci-dessus (soit dans le fichier [unicaen-db-import.local.php](config/unicaen-db-import.local.php.dist) soit dans un autre fichier de config.).
En outre, on suppose avoir déclaré les configurations Doctrine `orm_A` et `orm_B` respectivement pour les bases **A** 
et **B** ci-dessus (dans le fichier [unicaen-db-import.local.php](config/unicaen-db-import.local.php.dist) par exemple).

**Rappel :** L'ensemble des tables _Source_ et _Destination_ doivent exister au préalable.
**Rappel :** Les tables _Source_ et _Destination_ doivent exister au préalable.

Pour les exemples qui suivent, voici à quoi ressemble le fichier de config `unicaen-db-import.local.php` :

@@ -275,13 +282,16 @@ return [
                'eventmanager' => 'orm_B',
            ],
        ],
    ],
];
```


### Exemple 1 : Import d'une table de données distantes
### Exemple 1 : Import de données provenant d'une autre base

Dans de nombreux cas, on peut souhaiter importer une table de données provenant d'une base distante.
#### Exemple 1.1 : Import d'une table

Dans de nombreux cas, on peut souhaiter importer une table de données provenant d'une autre base que celle de destination.
Ce peut être le cas notamment lorsque l'on souhaite s'assurer de la constante disponibilité des données.

`unicaen-db-import.global.php`
@@ -314,11 +324,7 @@ return [
php public/index.php run import --name "IMPORTATION DES DONNÉES B VERS A"
```


### Exemple 2 : Import d'un select de données distantes

Les bases distantes requêtées sont généralement bien garnies.
Pourtant il est fréquent de ne vouloir importer qu'une partie de ces données.
#### Exemple 1.2 : Import d'un "select"

`unicaen-db-import.global.php`
```php
@@ -351,7 +357,7 @@ php public/index.php run import --name "IMPORTATION PARTIELLE DES DONNÉES B VER
```


### Exemple 3 : Import de données issues d'une API
### Exemple 2 : Import de données obtenues via une API

Dans certains cas, on peut souhaiter importer les données provenant d'une API (web service).

@@ -389,11 +395,10 @@ php public/index.php run import --name "WS_IMPORT_REGIONS"
```


### Exemple 4 : Synchronisation de données locales
### Exemple 3 : Synchronisation de données au sein d'une même base

Dans la pratique, vous ne rencontrerez probablement pas ce cas d'exemple seul (voir [Exemple 5](#exemple-5-synchronisation-de-données-distantes-avec-récupération-des-clés-étrangères)).
Supposez donc posséder 2 tables, l'une d'elle pouvant être qualifiée de "brut"/"en vrac" (d'où proviennent les données) et l'autre "propre" (avec une historisation attendue).
On souhaite alors synchroniser la première table avec la seconde.
Exemple d'un "select" source mettant en forme des données et d'une table destination synchronisée à partir 
de ces données :

`unicaen-db-import.global.php`
```php
@@ -428,13 +433,15 @@ php public/index.php run synchro --name "SYNCHRONISATION LOCALE DE DONNÉES DE A
*NB: Fonctionne également en spécifiant une 'table' au lieu d'un 'select' dans la 'source'.*


### Exemple 5 : Synchronisation de données distantes
### Exemple 4 : Synchronisation de données distantes

#### Exemple 5.1 : Source de type base de données
#### Exemple 4.1 : Source de type base de données

Cet exemple sera probablement l'une des utilisations les plus récurrente d'UnicaenDbImport.
Par exemple, vous possédez une base distante contenant des utilisateurs et une base locale possédant ses propres utilisateurs.
Vous souhaitez alors harmoniser vos utilisateurs présent localement et ceux disponible sur la base distante.

Par exemple, vous avez accès à une base de données contenant des utilisateurs et la base de données de votre application
possédant ses propres utilisateurs.
Vous souhaitez alors synchroniser vos utilisateurs avec ceux disponibles dans l'autre base.

`unicaen-db-import.global.php`
```php
@@ -470,14 +477,14 @@ return [
php public/index.php run synchro --name "SYNCHRONISATION DISTANTE DE DONNÉES DE B VERS A"
```

*NB: Une synchronisation de données distantes fait en réalité appel à la fois au mécanisme d'import et au mécanisme de 
     synchronisation locale (voir [Fonctionnement](#fonctionnement)) ; d'où la présence (facultative) des paramètres 
     `intermediate_table` et `intermediate_table_auto_drop`.*
Une synchronisation de données à partir d'une autre base de données fait en réalité appel à la fois au mécanisme 
d'import et au mécanisme de synchronisation (voir [Fonctionnement](#fonctionnement)) ; d'où la présence
des paramètres `intermediate_table` et `intermediate_table_auto_drop` (facultatifs).

*NB-2: Fonctionne également en spécifiant une 'table' au lieu d'un 'select' dans la 'source'.*
Fonctionne également en spécifiant une 'table' au lieu d'un 'select' dans la 'source'.


#### Exemple 5.2 : Source de type API
#### Exemple 4.2 : Source de type API

`unicaen-db-import.global.php`
```php
@@ -512,16 +519,22 @@ php public/index.php run synchro --name "WS_SYNCHRO_COMMUNES"
```


### Exemple 6 : Synchronisation de données distantes avec récupération des clés étrangères
### Exemple 5 : Synchronisation de données distantes avec récupération des clés étrangères

De nombreux cas d'utilisation suivront cet exemple.
Si vous observez les tables (base locale) déclarées au début de cette section [Exemples](#exemples), vous pourrez constater que les formations possèdent des clés étrangères vers les composantes (via leur ID).

Si vous observez les tables exemples évoquées au début de cette section [Exemples](#exemples), vous pourrez 
constater que les formations possèdent des clés étrangères vers les composantes.

Pour gérer le cas de ces clés étrangères, il vous faudra :
- Déclarer un mécanisme d'import
- Déclarer un mécanisme de synchro
- Créer une vue (conventionnellement nommée SRC_*XXX*) réalisant les jointures nécessaires à la récupération des clés étrangères
- Configurer un mécanisme d'import de la base source vers une table temporaire ;
- Créer une vue source (conventionnellement préfixée par `SRC_`) puisant dans la table temporaire et réalisant les jointures 
  nécessaires à l'alimentation des clés étrangères ;
- Configurer un mécanisme de synchro de la vue source vers la table destination finale.

Voici l'exemple d'une vue `SRC_FORMATION` puisant dans la table temporaire `TMP_FORMATION` et mettant en forme les 
données qui seront la source de la synchronisation vers la table finale `FORMATION` :

`Console de la base de données locale`
```sql
CREATE VIEW SRC_FORMATION AS
    SELECT
@@ -540,7 +553,7 @@ return [
    'import' => [
        'imports' => [
            [
                'name' => "IMPORTATION DE DONNÉES B VERS A",
                'name' => "IMPORTATION PRÉALABLE", // Importation des données externes dans une table temporaire
                'source' => [
                    'name'               => 'TABLE FORMATION DE MA BASE B',
                    'select'             => 'SELECT ID, CODE, NOM, COMPOSANTE_CODE FROM FORMATION',
@@ -557,10 +570,10 @@ return [
        ],
        'synchros' => [
            [
                'name' => "SYNCHRONISATION DE DONNÉES DÉJÀ IMPORTÉE DE B VERS A",   //Autrement dit une synchro locale
                'name' => "SYNCHRONISATION FINALE", // Synchro des données mise en forme vers la table finale
                'source' => [
                    'name'               => 'TABLE SRC_FORMATION DE MA BASE A UTILISANT TMP_FORMATION',
                    'select'             => 'SELECT * FROM SRC_UTILISATEUR',
                    'select'             => 'SELECT * FROM SRC_FORMATION',
                    'connection'         => 'orm_A',
                    'source_code_column' => 'ID',
                ],
@@ -569,7 +582,6 @@ return [
                    'table'              => 'FORMATION',
                    'connection'         => 'orm_A',
                    'source_code_column' => 'ID',

                ],
            ],
        ],
@@ -579,20 +591,24 @@ return [

`Terminal du serveur`
```bash
php public/index.php run import --name "IMPORTATION DE DONNÉES B VERS A"
php public/index.php run synchro --name "SYNCHRONISATION DE DONNÉES DÉJÀ IMPORTÉE DE B VERS A"
php public/index.php run import --name "IMPORTATION PRÉALABLE"
php public/index.php run synchro --name "SYNCHRONISATION FINALE"
```

*NB: Dans cet exemple, la table TMP_XXX doit être préalablement créée. Elle ne sera donc pas supprimée à la fin de la synchronisation. De nouvelles fonctionnalités à venir devraient pouvoir automatiser cette suppression.*
Dans cet exemple, la table `TMP_FORMATION` doit être préalablement créée. Elle ne sera donc pas supprimée à la fin de la 
synchronisation. De nouvelles fonctionnalités à venir devraient pouvoir automatiser cette suppression.


Développement
-------------

Ajouter le support d'une autre plateforme de base de données (pour développeur)
-------------------------------------------------------------------------------
### Supporter une autre plateforme de base de données

Imaginons que l'on veuille ajouter la possibilité d'importer/synchroniser vers la plateforme de base de données
destination MySQL. 

Voici ce qu'il faudra faire :

- Ajouter dans le fichier `config/module.config.php` la config permettant d'associer la bonne classe de "code generator"
  (à créer) à chaque classe de plateforme de base de données MySQL connue de Doctrine :

@@ -605,6 +621,8 @@ return [
            \Doctrine\DBAL\Platforms\MySQL80Platform::class => \UnicaenDbImport\CodeGenerator\MySQL\CodeGenerator::class,
        ],
        //...
    ],
];
```

- Créer un répertoire `src/UnicaenDbImport/CodeGenerator/MySQL` dans lequel on va créer les fichiers/classes 
@@ -684,6 +702,8 @@ class CodeGenerator extends \UnicaenDbImport\CodeGenerator\CodeGenerator
    }
    
    //...

}
```

- Sa factory `CodeGeneratorFactory` doit *obligatoirement* :
@@ -722,13 +742,10 @@ class CodeGeneratorFactory
  - *si besoin* redéfinir les méthodes de la classe mère qui ne génèreraient pas du code SQL valide pour la
    plateforme de base de données (ici, MySQL).

Exemple :

```php
namespace UnicaenDbImport\CodeGenerator\MySQL\Helper;

use Doctrine\DBAL\Platforms\MySqlPlatform;
use UnicaenDbImport\Domain\Operation;

class DiffViewHelper extends \UnicaenDbImport\CodeGenerator\Helper\DiffViewHelper
{
@@ -738,16 +755,41 @@ class DiffViewHelper extends \UnicaenDbImport\CodeGenerator\Helper\DiffViewHelpe
    protected $platform;

    /**
     * {@inheritDoc}
     * @param string $destinationTable
     * @param string $sourceCodeColumn
     * @param array $columns
     * @return string
     */
    protected function generateSQLForUpdateOperationInDestinationTable($destinationTable, $sourceCodeColumn, array $columns)
    {
        // ...
    }

    /**
     * @param string $destinationTable
     * @param string $sourceCodeColumn
     * @param array $columns
     * @return string
     */
    protected function generateViewDeletionSQLSnippet($destinationTable)
    protected function generateSQLForUndeleteOperationInDestinationTable($destinationTable, $sourceCodeColumn, array $columns)
    {
        $name = $this->generateViewName($destinationTable);
        // ...
    }

        return "DROP VIEW IF EXISTS $name";
    /**
     * @param string $destinationTable
     * @param string $sourceCodeColumn
     * @param array $columns
     * @return string
     */
    protected function generateSQLForDeleteOperationInDestinationTable($destinationTable, $sourceCodeColumn, array $columns)
    {
        // ...
    }
  
    //...

}
```