A bord de Salamanca

Aller au contenu | Aller au menu | Aller à la recherche

Alpha 2 : les énumérations

La sortie de la version alpha 2 de Salamanca nous a donné l'occasion de sortir notre équipement pour révision en cale sèche...

Spécification du cycle de vie d'un projet

La principale amélioration apportée par Salamanca alpha 2 étant le support des énumérations dans le modèle entité, nous en avons profité pour revoir et implémenter le cycle de vie d'un projet suivant :

Projet_LifeCycle 

Chaque changement de statut est de la responsabilité d'un des interlocuteurs que nous avons déjà spécifié lors de la modélisation de nos entités :

  • Un client soumet un projet
  • Un commercial prend en charge ou annule un projet soumis
  • Le client détaille le besoin de son projet pris en charge
  • Un consultant prototype un périmètre délimité du besoin
  • Le client clôture le projet après livraison du prototype

Modélisation avec énumération

Pour modéliser le cycle de vie d'un projet, nous avons ajouté l'énumération StatutProjet dans notre modèle entité et défini chaque statut du cycle en tant que valeur, avec comme type sous-jacent une chaîne de 5 caractères. Nous avons alors ajouté l'attribut Statut à l'entité Projet et défini son type comme StatutProjet.

VS_Model_StatutProjet

Pour chaque valeur d'énumération, nous associons à travers le champ Description de ses propriétés la chaîne de caractères "en français" : Soumis, Pris en charge, Détaillée, Prototypé, Clôturé, Annulé.

On retrouve alors dans le code SQL généré l'insertion de ces valeurs en base :

INSERT INTO StatutProjet VALUES('SOUMI', 'Soumis');
INSERT INTO StatutProjet VALUES('ENCHA', 'Pris en charge');
INSERT INTO StatutProjet VALUES('DETAI', 'Détaillé');
INSERT INTO StatutProjet VALUES('PROTO', 'Prototypé');
INSERT INTO StatutProjet VALUES('CLOTR', 'Clôturé');
INSERT INTO StatutProjet VALUES('ANNUL', 'Annulé');

Implémentation de l'écran liste des projets

Afin d'avoir une vue d'ensemble des projets client, un commercial doit pouvoir visualiser la liste des projets et leur statut dés la page d'accueil de son espace.

Nous ajoutons donc dans le projet Eproject.Web :

  • un répertoire Espace qui regroupe les écrans accessibles après ouverture de session
  • une page d'accueil Default.aspx qui affiche une vue d'ensemble selon le rôle utilisateur

Dans la page d'accueil, nous implémentons la liste des projets à travers :

  • un contrôle ASP.NET GridViewaffichant les projets sous forme de tableau
  • un contrôle ASP.NET SqlDataSource récupérant les informations directement avec une requête SQL vers la base de données

Pour accéder à la base de données, nous configurons une chaîne de connexion avec l'utilitaire Enterprise Library Configuration de la même façon que pour notre projet de tests unitaires. Le résultat placé dans le fichier web.config du projet web est le suivant :

<dataConfiguration defaultDatabase="eProject_ABord" />
<connectionStrings>
  <add name="eProject_ABord" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=eProject_ABord;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>

Pour alimenter la liste, nous ajoutons des données de test dans la base eProject_ABord avec le script SQL :

Declare @client_id uniqueidentifier=newid();

INSERT INTO Interlocuteurs (Id, Nom, Email) VALUES (@client_id, 'Client test', 'client@test.com');
INSERT INTO Clients (Id, Societe) VALUES (@client_id, 'Société test');

INSERT INTO Projets (Client_Id, Nom, Statut_Code) VALUES (@client_id, 'Application DA VINCI', 'SOUMI');
INSERT INTO Projets (Client_Id, Nom, Statut_Code) VALUES (@client_id, 'Gestion de stocks', 'SOUMI');
INSERT INTO Projets (Client_Id, Nom, Statut_Code) VALUES (@client_id, 'Projet SIGMAT', 'ENCHA');
INSERT INTO Projets (Client_Id, Nom, Statut_Code) VALUES (@client_id, 'Plateforme YaKa', 'DETAI');
INSERT INTO Projets (Client_Id, Nom, Statut_Code) VALUES (@client_id, 'Fiche PATROL', 'PROTO');
INSERT INTO Projets (Client_Id, Nom, Statut_Code) VALUES (@client_id, 'MOBILAT', 'CLOTR');
INSERT INTO Projets (Client_Id, Nom, Statut_Code) VALUES (@client_id, 'Projet SPAMMER', 'ANNUL');

Dans un fichier de ressource nommé Sql.resx ajouté au dossier ASP.NET App_GlobalResources, nous définissons la requête SQL ProjectSelectList :

SELECT p.Id, p.Client_Id, p.Nom, c.Societe, sp.Description
FROM Projets p, Clients c, StatutProjet sp
WHERE  p.Client_Id=c.Id AND p.Statut_Code=sp.Code

Dans la page web, nous ajoutons le code ASP.NET ci-dessous pour afficher la liste issue de la requête :

<asp:SqlDataSource ID="ProjetSqlDataSource" runat="server" 
ConnectionString="<%$ ConnectionStrings:eProject_ABord %>" ProviderName="<%$ConnectionStrings:eProject_ABord.ProviderName %>"
SelectCommand="<%$ Resources:Sql, ProjetSelectList %>">
</asp:SqlDataSource>
<asp:GridView ID="ProjetGridView" runat="server" DataSourceID="ProjetSqlDataSource" DataKeyNames="ID" Width="400px">
    <Columns>
        <asp:BoundField DataField="Id" HeaderText="Id" ReadOnly="True" SortExpression="ID" Visible="False" />
        <asp:BoundField DataField="Nom" HeaderText="<%$ Resources:SR, ProjetNomLabel %>" />
        <asp:BoundField DataField="Societe" HeaderText="<%$ Resources:SR, ClientSocieteLabel %>" />
        <asp:BoundField DataField="Description" HeaderText="<%$ Resources:SR, ProjetStatutLabel %>" />
    </Columns>
</asp:GridView>

Pour obtenir un "look & feel" homogène dans notre application, nous avons ajouté :

  • la page maître Site.Master liée à une feuille de style CSS placée dans le thème Default du répertoire ASP.NET App_Themes
  • un fichier d'apparence grid.skin également placé dans le thème Default définissant le style par défaut d'un contrôle GridView
  • l'option styleSheetTheme="Default" à la directive pages du fichier web.config

La page Default.aspx est transformée en formulaire de contenu web en ajoutant :

  • la directive MasterPageFile="~/Site.master"
  • le code ASP.NET dans un contrôle Content associé à la région pour le contenu (ContentPlaceHolder) définie dans la page maître

Nous obtenons ainsi la liste des projets avec leur client et leur statut associés sous la forme suivante :

 Ecran Liste des projets client

Mise à jour des références Salamanca

Le refactoring des bibliothèques réalisé dans la mise à jour alpha 2 de Salamanca nous a permis lors de cette révision de réduire le nombre de références dans notre projet.

Par exemple, dans le projet Eproject.Model, nous avions les 4 références suivantes :

  • Salamanca.Common
  • Salamanca.DataAccess
  • Salamanca.DataAccess.EnterpriseLibrary
  • Salamanca.DataRules

Nous n'avons maintenant plus que 2 références :

  • Salamanca
  • Salamanca.EnterpriseLibrary.4

Ce passage en "révision" de notre équipée nous a donc permis d'enrichir notre modèle graphique des entités avec la description du cycle de vie d'un projet ainsi que que de l'illustrer simplement à travers l'implémentation d'un écran web accédant directement à la base de données.

Il est bientôt temps de repartir à l'aventure pour mettre en oeuvre des interactions entre l'application et l'utilisateur à travers des activités métier...

Contrôle des papiers...

En quittant notre dernière escale, nos hôtes nous ont demander de présenter des informations validées sur notre équipée. Heureusement, nous avons dans Salamanca une bibliothèque qui permet de vérifier aisément la saisie de données : Data Rules !

Pour rendre un système d'information utile et efficace, il ne suffit pas de l'alimenter en données, il faut aussi en assurer la cohérence.

La première exigence qui en découle est de valider les informations fournies si possible dés leur saisie et avant leur sauvegarde dans la base de données.

Dans le modèle entité dessiné avec Salamanca, on peut associer à chaque attribut des règles de validation prédéfinies en fonction de spécifications métier telles que :

  • valeur par défaut ; par exemple, le statut du projet est "Nouveau" par défaut
  • autorisation de la valeur null ; par exemple, un projet doit avoir un nom
  • longueur maximum ; par exemple, le nom du projet ne dépasse pas 32 caractères
Ces propriétés sur les attributs d'une entité constituent le premier niveau de règles métier. Elles sont implémentées non seulement dans la base de données sous forme de contraintes, mais aussi dans la couche objet sous forme d'attributs (au sens .NET) liés aux propriétés de l'objet. Par exemple :
[NotNullRule]
[StringLengthRule(32)]
public string Nom
{
...
}

Profitons d'avoir configuré notre projet de tests pour valider que ces règles métier sont vérifiées lors de la sauvegarde d'un projet.

Pour faciliter l'exécution de notre série de tests, nous initialisons chacun avec des données valides pour une instance de Projet défini pour la classe de test :

[TestInitialize()]
public void MyTestInitialize()
{
Client c = new Client(DataMappers);
c.Nom = "Jean Dupond";
Projet = new Projet(c, DataMappers);
Projet.Nom = "Mon projet";
}

Pour tester que le nom saisi dépasse la longueur maximale, nous ajoutons un test avec "un nom de projet vraiment trop long" et nous vérifions que

  • la méthode Validate(string propertyName) d'un objet DomainEntity renvoit FAUX
  • qu'une BrokenRulesException est levée lorsqu'on sauvegarde une instance non valide
[TestMethod]
[ExpectedException(typeof(BrokenRulesException))]
public void CannotValidateNomNull()
{
    Assert.IsTrue(Projet.Validate());
    Projet.Nom = null;
    Assert.IsFalse(Projet.Validate("Nom"));
    Projet.Save();
}

Le code complet de notre classe se résume à la validation d'une série de 3 tests :

  • On doit pouvoir sauvegardé une instance de Projet avec un nom valide
  • On ne doit pas pouvoir sauvegardé une instance de Projet avec un nom vide (NULL)
  • On ne doit pas pouvoir sauvegardé une instance de Projet avec un nom trop long (> 32 caractères)
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Salamanca.DataRules;
using NourY.Eproject.Model;

namespace NourY.Eproject.Tests.Model
{
    [TestClass]
    public class ProjetTest : UnitTestBase, IProjetContainer
    {
        [TestMethod]
        [DeploymentItem("..\\eProject_ABord_Tests.mdf")]
        public void CanSaveAndValidateProjet()
        {
            Assert.IsTrue(Projet.Validate());
            Projet.Save();
        }

        [TestMethod]
        [ExpectedException(typeof(BrokenRulesException))]
        public void CannotValidateNomNull()
        {
            Assert.IsTrue(Projet.Validate());
            Projet.Nom = null;
            Assert.IsFalse(Projet.Validate("Nom"));
            Projet.Save();
        }

        [TestMethod]
        [ExpectedException(typeof(BrokenRulesException))]
        public void CannotValidateNomTooLong()
        {
            Assert.IsTrue(Projet.Validate());
            Projet.Nom = "Un nom de projet vraiment trop long";
            Assert.IsFalse(Projet.Validate("Nom"));
            Projet.Save();
        }

        [TestInitialize()]
        public void MyTestInitialize()
        {
            Client c = new Client(DataMappers);
            c.Nom = "Jean Dupond";
            Projet = new Projet(c, DataMappers);
            Projet.Nom = "Mon projet";
        }

        public Projet Projet
        {
            get { return _Projet; }
            set { _Projet = value; }
        }

        private Projet _Projet;
    }
}

Les règles de validation sur les attributs des entités s'avère donc non seulement aisées à mettre en place mais également implémentées de manière concise dans la couche objet. Pour valider des champs avec des règles avancées tels qu'une adresse email, un numéro de téléphone ou un code postal, nous pouvons exploité la puissance des expressions régulières à travers des règles de type RegexRule.

Cette exploration "forcée" de la bibliothèque DataRule entre deux escales nous ouvre déjà un horizon bien dégagé du côté de la consolidation des données de nos applications métier. A suivre avec leur utilisation dans le cadre d'activités métier...

Escale #2 : Base de données

Pourquoi avoir spécifié, modélisé et généré des entités métier ?

Si nous sommes à bord de Salamanca, c'est car nous voulons informatiser (et améliorer) l'un de nos processus de travail. L'objectif est de permettre aux acteurs de ce processus de consulter, saisir, évaluer, récupérer ou exploiter les ressources et informations alimentant ce processus dans le cadre d'un enchaînement d'activités.

C'est d'ailleurs la base de la définition d'une application métier :

"manipuler des données spécifiques à une organisation ou un secteur d'activité".

Nous sommes donc en accord avec la ligne de produits ciblée par l'usine Salamanca (ça tombe bien !). Pour cela, un moyen de stocker l'ensemble de ces informations nous est nécessaire.

Comme Salamanca comprend une bibliothèque ORM (mais pas seulement !), l'implémentation du mapping entre notre modèle d'entités métier et le modèle physique des données est pour nous, humble développeur "métier", transparent (même s'il est toujours conseillé d'avoir une compréhension haut niveau des concepts associés).

Nous nous attarderons donc lors de cette escale uniquement sur l'organisation que nous avons mis en place pour créer (et re-créer) rapidement notre base de données.

Organisation

Puisque nous disposons du code SQL généré pour la création des tables et des procédures stockées CRUD, autant en profiter. Néanmoins, leur emplacement dans le projet Model et leur liaison au fichier modèle (.tt) assurant leur génération ne permettent pas de les dinstinguer facilement dans notre solution Visual Studio. C'est pourquoi nous avons ajouté le projet Eproject.Sql de type Projet de base de données lors de la création de notre solution (tout s'explique !).

Ainsi, chaque développeur dispose de sa propre base de données de développement à partir d'un schéma placé dans le gestionnaire de versions (conformément aux bonnes pratiques) et "bien rangé" dans l'environnement de travail :

  • VS_SolutionExplorer_Sqlles scripts de création : dans Create Scripts
  • les scripts de mise à jour : dans Change Scripts

Pour cela, nous ajoutons dans les propriétés du projet Eproject.Model un évènement après génération (Post-build) afin de copier les fichiers SQL générés dans leur répertoire cible.

De cette façon, les scripts servant à la création de la base restent synchronisés après chaque mise à jour du modèle, puis compilation du code généré.

Remarque : la première fois, pensez à ajouter manuellement les fichiers copiés (clic droit sur le répertoire, puis "Ajouter un élément existant...").

Automatisation

Pour automatiser la création de notre base de données, nous avons besoin :

  • d'un script TRANSACT-SQL pour effacer (si nécessaire) et créer la base
  • d'exécuter l'utilitaire sqlcmd en ligne de commande ou dans un batch (.cmd)

Nous pourrions pousser l'automatisation de la même façon que pour la copie des fichiers SQL en ajoutant un évènement après génération du projet Eproject.Model. Mais cela efface les données saisies en cours de développement à chaque génération, donc nous préférons nous placer dans le répertoire Create Scripts et exécuter la commande ci-dessous (dans un batch) uniquement lors d'un changement dans notre schéma :

C:\eProject\src\ABordDeSalamanca\Eproject.Sql\Create Scripts>sqlcmd.exe -E -X -S .\SQLEXPRESS -v DatabaseName=eProject_ABord -i Create.sql

Comme nous utilisons une base de données SQL Server 2008 Express, notre exemple de script TRANSACT-SQL contient le code suivant :

USE master
GO

IF EXISTS (SELECT * FROM sys.databases WHERE name=N'$(DatabaseName)')
BEGIN
ALTER DATABASE $(DatabaseName)
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE
DROP DATABASE $(DatabaseName)
END
GO

CREATE DATABASE $(DatabaseName)
GO

USE $(DatabaseName)
GO

:reset
:r Tables.sql
:r StoredProcedures.sql
GO

Voilà une façon rapide de disposer à tout moment d'une base de données synchronisée avec notre modèle d'entités métier. Et nous verrons lors de notre prochaine escale comment la mise en place de notre projet de tests unitaires en bénéficie...

Escale #1 : Entités métier

Entités

Nous voilà arrivés au pays des "entités". Mais qui sont-elles ?

Pour mieux intégrer cette notion d'entité, nous nous référons à sa définition dans le cadre du modéle entité-relation développé par Dr Peter Chen :

"une chose qui peut être identifiée distinctement ayant une signification pour une organisation".

Spécification

Dans le cadre du service que nous développons, eProject, nous voulons mettre en relation les entités suivantes :

  • un projet métier : vue d'ensemble, besoin détaillé et périmètre d'un prototype
  • 3 interlocuteurs : client, commercial et consultant

Lors de la définition de notre besoin, nous avons également précisé les règles d'association à travers les spécifications suivantes :

  • Pour consulter NourY Solutions sur mes besoins métier, je veux pouvoir soumettre mon projet ainsi que mes projets ultérieurs en tant qu'interlocuteur client
  • Pour piloter la relation clientèle, je prend en charge le suivi de l'avancement des projets de chacun de mes clients en tant qu'interlocuteur commercial
  • Pour produire un prototype à partir de l'analyse d'un besoin, je veux pouvoir estimer la charge de travail sur la base de chaque projet en tant qu'interlocuteur consultant

Modélisation

Modèle eProject

Une fois les concepts digérés et notre besoin spécifié, Salamanca nous permet de rapidement modéliser le socle de notre métier représenté dans le diagramme ci-dessus.

Pour cela, nous ouvrons le modèle SDML du projet Eproject.Model de la solution Visual Studio créée lors de notre départ.

VS_Model_Toolbox

La palette d'éléments mise à disposition dans la boite à outils est d'une aide précieuse pour implémenter notre modèle de 5 entités simplement par glisser-déposer et définition des propriétés des associations :

  • 1 composition entre Client et Projet
  • 2 associations bidirectionnelles entre Commercial et Client, entre Consultant et Projet 
  • 3 héritages : Client, Commercial et Consultant sont tous des Interlocuteurs du Projet

Nous pouvons maintenant définir les attributs soit par glisser-déposer de l'élément "Attribute" à partir de la boîte à outils, soit par clic droit sur l'entité, puis "Ajouter un nouveau Entity Attribute" à partir du menu contextuel.

Le nom et le type (string par défaut) de l'attribut peuvent être définis directement de manière graphique alors que la fenêtre de "Propriétés" permet également de spécifier des règles validées automatiquement grâce à l'utilisation de l'une des 4 bibliothèques de Salamanca : Data Rules

La facilité d'intégration de règles métier est l'une des grandes forces de Salamanca, aussi nous lui dédierons bientôt une escapade de découverte avant la prochaine escale !

Génération

Une fois les attributs et les règles associées définis pour chaque entité, il nous reste qu'à générer le code correspondant par un simple clic sur l'icône "Transformer tous les modèles" présente dans la barre d'outils de la fenêtre "Explorateur de solutions".

Code généré

A la fin de la génération, nous obtenons l'ensemble du code de la couche d'accès aux données basé sur la bibliothèque générique de Salamanca dédié à cet usage : Data Access. Cette couche est constituée :

  • du code SQL de création des tables associées aux entités
  • des procédures stockées pour les opérations CRUD sur chaque entité
  • des interfaces et classes de mapping entre entités et tables (Data Mappers)
  • des ressources liant procédures stockées et méthodes CRUD implémentées dans les Data Mappers
  • des interfaces et classes des entités du domaine métier

L'avantage évident de cette formule "Modélisation + Génération" est de ne plus avoir à taper du code redondant. Pour preuve, je vous invite à satisfaire votre curiosité et éditer les fichiers générés : on retrouve le même air dans chaque classe avec quelques variations autour du thème...

Mais le réel instant de bonheur du point de vue du développeur se savoure lors de l'implémentation de demandes d'évolutions telles que l'ajout d'attributs, ou mieux, lors de la modification de relations. Personnellement, j'ai par le passé consommé beaucoup de temps dans la maintenance du code SQL et objet suite aux évolutions inhérentes à tout projet métier.

Je vous invite donc dés maintenant à faire l'expérience avec le modèle métier de votre projet en cours avant notre prochaine escale : l'automatisation de la mise en place de la base de données.