SSIS intégré à une architecture .net (1) : Exploiter une assembly dans un data-flow

Cet article est le premier d’une série d’articles traitant de la manière d’exploiter les possibilités de SQL Server Integration Services (SSIS) dans une architecture applicative .Net plus globale.

Petit rappel des faits :

SQL Server Integration Services 2012 (SSIS) permet de réaliser efficacement des imports / exports de données avec transformation, dédoublonnage, nettoyage de la donnée. Ces imports de données peuvent être rapidement développés et sont aisément industrialisables par configuration (Utilisation de paramètres d’exécution, mise en place de logs, gestion des erreurs graphiquement).

Traditionnellement, pour les imports de fichiers ou les traitements de base à base, SSIS est utilisé de bout en bout :

  • Lecture depuis la source de données
  • transformations en mémoire
  • alimentation de tables cibles (destination de données)

La logique des transformations et de l’alimentation des tables cibles se trouvent implémentée dans SSIS.

Ces caractéristiques sont pleinement satisfaisantes lors d’un développement centré sur SQL Server, par exemple l’alimentation d’un DataWarehouse ou la synchronisation de 2 bases de données. Les paramètres d’exécution et les traces sont stockés directement dans une instance SQL Server (pour la version 2012 de SSIS, on peut les trouver sur d’autres supports avec les versions précédentes du produit, ce qui ne change rien à l’article), les rapports d’exécution également.

L’architecture de la solution est homogène.

Le problème :

Dans le cadre d’un développement complet d’un système d’information, il arrive que l’on soit amené à développer des fonctionnalités d’insertion de données dans la couche applicative redondants avec des packages SSIS d’import de données.

La logique de validation des données et les éventuels traitements métier sont alors codés 2 fois ce qui engendre des risques de divergence fonctionnelle d’une part et de divergence technique d’autre part (formats de trace différents, gestions des erreurs incompatibles…)

L’objectif de cet article est de proposer une solution d’import de données en utilisant SSIS pour l’aspect lecture des sources et chargement des données, et la couche métier pour l’aspect validation de règle business. L’objectif étant de tendre vers un développement réellement piloté par un domaine (DDD), même si SSIS est utilisé.

Le contexte

Mon application de démo permet de gérer un référentiel de parution de presse. Chaque jour, je reçois un fichier contenant les parutions prévues à 1 semaine qu’il faut intégrer dans mon référentiel. Des parutions peuvent également être créées manuellement depuis une application Web. Les mêmes règles de validation doivent être respectées qu’il s’agisse de l’import de fichier ou de la saisie manuelle.

La partie technique

Je dispose d’une assembly implémentant mes structures de données et les règles business conduisant à leur validation. Cette assembly constitue mon domaine et contient uniquement la classe suivante :

namespace ParutionDomain
 {
  public class Parution
  {
    public int Numero { get; set; }
    public string Libelle { get; set; }
    public string Codification { get; set; }
    public string Codebarre { get; set; }

 #region BusinessRules
    public bool IsValid()
    {
       return TestBarCodeValid();
    }
    private bool TestBarCodeValid()
    {
       string barCodeRule =string.Format("{0}{1}", Codebarre.Substring(0,5), Codification);
       return Codebarre.StartsWith(barCodeRule);
    }
 #endregion
 }
}

Seule une méthode de validation est implémentée ici, mais on peut sans difficulté imaginer qu’il ne s’agit pas de ma seule règle de validation de parutions entrantes. Cette assembly est utilisée dans mon application Web pour la validation des saisies utilisateur.

Je saurais re-créer les mêmes règles avec les composants SSIS, à base de derived column et conditional split. Pour mon exemple, dans l’objectif de ne pas recoder la logique de mes règles différemment et de centraliser leur mise en place, je choisis d’utiliser l’assembly définie ci-dessus dans un composant de script dans le data-flow qui lit le fichier.

Je vais donc créer un Package SSIS chargé de l’import quotidien de mes fichiers de parutions.

Ce fichier a pour structure :

La codification sur 4 caractères, le libellé de la parution sur 20 caractères, le numéro de parution sur 6 caractères puis le codebarre sur 14 caractères.

Ce qui nous donne par exemple ce contenu :

0051SUP. FIGARO TV VE   00000093789005100003
0745SUP. FRANCE FOOT    00000093789074500001
0041SUP.FIGARO MAGAZI.VE00000093789004100004
0044SUP.FIGARO MADAME VE00000093789004400005
0106EQUIPE              13011803780010601004

Ce package contient un data-flow chargé de la lecture du fichier à largeur de colonne fixe. Il débute par une source de type fichier plat.

Elle est connectée à un composant de script. Ce composant de script marque « invalide » les lignes ne respectant pas les règles business définies dans le domaine. Je souhaite disposer en sortie de ce composant des colonnes d’entrée et d’une colonne additionnelle pour stocker cette information.

Ajout de colonne dans le composant Script

Ajout de colonne dans le composant Script

Pour pouvoir utiliser l’assembly, celle-ci doit être référencée par le composant de script. Pour que cette référence soit permise, il faut que mon assembly soit signée et hébergée dans le GAC. J’ai signé mon assembly, compilée pour le framework 4.0 et je l’ai chargée dans le GAC.

C:> gacutil – i ParutionDomain.dll

Attention pour le framework .net 4 et 4.5, la gestion du GAC a changé comme le résume cet article :

http://rajmittal.blogspot.fr/2013/04/multiple-gac-net-framework-40.html

Attention néanmoins, si je dois modifier ma dll, il faudra penser à mettre à jour la version hébergée dans le GAC, car c’est bien cette assembly qui est chargée par SSIS à l’exécution, même si ma référence semble porter sur l’instance de l’assembly dans le système de fichier.

Je peux ainsi la référencer dans la transformation du data flow :

Ajout de référence dans le composant Script

Ajout de référence dans le composant Script

J’override ensuite la méthode Input0_ProcessInputRow, de manière à affecter la colonne Valide que j’ai créée.

Je construis donc un objet du domaine à partir des colonnes de mon flux SSIS et j’affecte à la colonne Valide la valeur retournée par la méthode IsValide du Domaine.

#region Namespaces
using System;
using ParutionDomain;
#endregion
/// <summary>
/// This is the class to which to add your code.  Do not change the name, attributes, or parent
/// of this class.
/// </summary>
[Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute]
public class ScriptMain : UserComponent
{
/// <param name="row">The row that is currently passing through the component</param>
   public override void Input0_ProcessInputRow(Input0Buffer row)
   {
      Parution parution = new Parution();
      try
      {
         parution = new Parution()
         {
            Codebarre = row.Codebarre,
            Codification = row.Codifitre,
            Libelle = row.Libelle,
            Numero = int.Parse(row.Numero)
         };
      }
      catch (Exception)
      {
         row.Valide = false;
      }
      row.Valide = parution.IsValid();
   }
}

J’ai ensuite dans mon data-flow un conditionnal split pour n’insérer dans la table que les objets valides.

Finalement, le dataflow contient une destination vers la table Parution de ma base de données dans laquelle ne sont intégrées que les lignes valides au regard du domaine.

Exécution du package

Exécution du package

Conclusion

En utilisant les composants script et en embarquant mes règles de validation du domaine dans une assembly, je peux faire converger mon architecture de manière à ne pas développer 2 fois dans des technologies différentes les mêmes comportements.

Néanmoins, je dis régulièrement lors des formations que j’anime de n’utiliser les composants de scripts que pour des traitements complexes et non réalisables avec les composants SSIS existants pour atteindre les meilleures performances possibles avec le produit. Le composant de script traite en effet, dans ce cas, en ligne à ligne, alors que je pourrais n’utiliser que des composants synchrones et ensemblistes pour réaliser le même travail.

Suivant, les traitements réalisés, on pourra constater ou non un écart de performance. Il s’agit simplement d’arbitrer entre performance et meilleure maintenabilité de nos développements mêlant à la fois applicatifs .Net et intégration de données.