DGML et Cartographie SI: Au-delà de l’exploration des architectures applicatives
January 5th, 2010Pour ceux qui ne le savent pas encore, Visual Studio 2010 en version Ultimate offre la possibilité d'explorer et d'analyser l'architecture d'un projet. Cette fonctionnalité est accessible via le menu Architecture / Generate Dependency Graph.
Un diagramme (DGML : Directed Graph Markup Language) représente les composants du projet (dll, namespaces, classes) et leurs relations.
Si on se positionne dans un contexte entreprise, j'ai voulu utiliser le DGML pour représenter les applications au sein du SI, et ainsi profiter des outils d'analyse de Visual Studio (Circular reference, hubs .)
En entrée j'ai besoin d'avoir la liste des applications, les relations, le type de relation (WS, DB, File .) et en plus j'ai trouvé intéressant de regrouper les applications par domaine (BackOffice, FrontOffice, Accounting, BI .). Ce qui va me permettre de détecter des anomalies : une application bureautique qui référence une application de production par exemple.
Ci-dessous les entités Application et Relation
public class Application { public string Name { get;set; } public string Domain { get; set; } } public class Relation { public string Source { get; set; } public string Target { get; set; } public string Type { get; set; } }
Pour pouvoir générer du DGML, il faut référencer les DLL suivantes qui se trouvent ~\Program Files\visual Studio 10\Common7\Ide\PrivateAssemblies :
Microsoft.VisualStudio.Progression.Common et Microsoft.VisualStudio.Progression.GraphModel
Après avoir récupérer les liste des applications, domaines, et relations il suffit de construire le graphe comme suit:
//Graph Creation Graph graph = new Graph(); // Create domains and add them to the graph Parallel.ForEach(domains, domain => { Node node = new Node(domain); node.IsGroup = true; graph.Nodes.Add(node); }); //Create application Node, and add the application to the appropriate domain Parallel.ForEach(applications, a => { Node node = new Node(a.Name); graph.Nodes.Add(node); string domain = a.Domain; if (!string.IsNullOrEmpty(domain)) { Node domainNode = graph.Nodes.FirstOrDefault(n => n.Label == domain); if (domainNode != null) { Link link = new Link(graph, domainNode, node); link.SetValue(GraphProperties.IsContainment, true); graph.Links.Add(link); } } }); //having all node, create the relations (links) Parallel.ForEach(relations, r => { Link link = new Link(graph, graph.Nodes.First(n => n.Label == r.Source), graph.Nodes.First(n => n.Label == r.Target)); link.Label = r.Type; graph.Links.Add(link); }); //Write the DGML to file string graphXml = graph.ToXml(); using (StreamWriter writer = new StreamWriter("si.dgml")) { writer.Write(graphXml); writer.Flush(); } }
En ouvrant le fichier dgml dans Visual Studio le résultat est le suivant : Un beau plat de spaghettis qui peut motiver l'urbanisation du SI:
La capture d'écran ci-dessous montre l'interaction d'un domaine FontOffice avec le reste du SI :
La capture d'écran ci-dessous montre l'interaction d'une application avec le reste du SI :
La capture d'écran ci-dessous montre les références cycliques.
L'outil est assez sympa basé sur WPF mais malheureusement on ne peut l'utiliser qu'avec Visual Studio Ultimate et pas d'intégration dans Visio.
[Concours MS] Gagner une Table surface
Juillet 21st, 2009Pour ceux qui veulent embellir leur salon avec une table surface (pas comme celle de Florent Santin). Microsoft lance un concours pour ses partenaires autour de la table surface.
The code name Geneva is no more …
Juillet 15th, 2009Le nom de code “Geneva ” qui regroupe les produits suivant: Geneva Server, Geneva Framework et Windows Cardspace Geneva va être remplacé par le nom officiel des produits:
- Geneva Server passe en Active Directory Federation Services : ADFS (pas de surprise)
- Geneva Framework passe en Windows Identity Foundation (WIF : Pas mal)
- Windows Cardspace Geneva passe en windows cardspace (pas de surprise)
Sun Cloud limité aux employés de Sun
Juillet 8th, 2009Je me suis inscrit y a deux semaines pour avoir un early access au sun cloud et toujours pas d’access code. J’ai demandé donc des informations au support et la réponse est assez claire :
The Sun Cloud is not currently available for external use. The sign up form you filled out gets you onto the early access list, and once we're ready to open the service for early access, you will receive information on how to access the service. But at this time, the service only is available internally to Sun employees.
Re: L’objet, c’est beau
Juin 30th, 2009Matthieu Mezil, artiste de l’objet, est parti pour son article L’objet, c’est beau du besoin suivant:
- Prendre un fichier en entrée, effectuer un traitement sur celui-ci puis sauvegarder les modifications dans un fichier de sortie.
- Par défaut, il propose deux types de traitements : l’inversion des caractères ligne par ligne et la numérotation des lignes.
- De plus, il veut que les traitements soient composables à l’infini et qu’ils puissent être utilisés indépendant de la notion de fichiers.
L’article traite plusieurs problématiques comme le threading, la gestion d’erreurs … Je vais m’intéresser juste à la première partie : l’acte de création du « chef-d’œuvre ».
L’artiste propose d’organiser sa solution en 2 couches UI et Business, déjà ce choix artistique me pose problème … pourquoi deux couches ? Pourquoi pas une seule ? Pourquoi pas 5 ?
Enfaite on connaît tous la réponse, mais on a tendance à concevoir nos applications en n-couche par habitude / respect des bonnes pratiques. Ce reflexe acquis (chez d’autres développeurs c’est même un reflexe inné) peut parfois nuire à la santé de la solution.
Dans notre cas, la problématique posée est assez simple par rapport à la complexité des projets réels, le choix de plusieurs couches dans cet exemple n’est dû aux contrainte de maintanabilité / scalabilité / extensibilité … Mais probablement pour consolider la structure de la solution : J’aurai préféré si Matthieu partait d’une seule couche, un seul centre fort qui encapsule plusieurs sous-centres (centres latents + centres faibles) : UI / interface avec l’infra (manipulation des fichiers) / Traitement des données ) et qu’en avançant dans son analyse il faisait évoluer sa solution en passant les centres latents en centre forts et en éliminant les centres faibles … Par exemple l’application au début se limite à une application client lourd et petite à petit un nouveau centre celui du traitement apparaît et on l’externalise dans une couche à part.
De nos deux couche business / UI Matthieu a fait un zoom X 1000, et on se retrouve en train de discuter d’une interface IStringsTransform ! Moi, j’ai eu le vertige… On est passé d’un level of scale de l’ordre de l’assembly à celui d’une interface ! comme si un architecte te présente la structure des étages de ta futur maison et juste après il enchaine avec l’emplacement du divan dans ton salon.
Autre soucis avec le Zooming, l’artiste fait référence aux designs patterns. Les design patterns c’est bien mais se focalisent sur une partie du système: Avec un zoom réglé au niveau objet, on risque donc de perdre de vue la solution globale.
Ce que j’aurai aimé trouver dans l’article, c’est une explication plus détaillé de l’approche, une analogie avec le monde réel peut être.
Dans notre cas, on a un fichier et des transformations qu’on peut composer séquentiellement. Ca ressemble à mettre une capsule Nespresso dans la machine, demander une transformation (café court / moyen / et long) en appuyant sur la commande adéquate :crazy:. On a donc des commandes qui référencent les transformations et un invoker a qui l’utilisateur associe à la demande une commande, la commande à son tour va s’appuyer sur la transformation pour satisfaire la requête du client:

Pour l'utilisation:
ILineTransformation lineNumberTransformation = new LineNumberTransformation();
TransformCommand lineNumberTransformCommand = new TransformCommand(lineNumberTransformation);
ILineTransformation reverseCharLineTransformation = new ReverseCharLineTransformation();
TransformCommand reverseTransformCommand = new TransformCommand(reverseCharLineTransformation);
TransformationRemote remote = new TransformationRemote();
remote.CurrentCommand = reverseTransformCommand;
var resultWithReverse = remote.Zapping(input);
remote.CurrentCommand = lineNumberTransformCommand;
var resultWithLineNumber = remote.Zapping(input);
ALT.NET : Adaptative Object Model et DDD
Mai 20th, 2009Dans l’approche Domain Driven Design, le cœur de la solution c’est le domaine métier. Du point technique le domaine est en général une assembly centrale qui est référencée par toutes les couches qui gravitent autour du domaine (repository, service, infra, UI, tests unitaires et fonctionnels). Cette dépendance forte est souvent montrée du doigt par certains collègues pro active record et qui pensent que les données c’est 90% du business. Et la question qui pose est : "Souvent" le client est amené à faire évoluer ses process et à mettre à jour son métier, dans ce cas il faut refaire toute l’application à cause de cette dépendance. En général je réponds que normalement le client change rarement de processus et que le cœur du métier est toujours le même, donc l’évolution à apporter dans le modèle n’est pas importante, et en général c’est l’infrastructure qui change (base de données, fichiers, Service web … ) et ca on sait gérer, si l’implémentation des infras sont facilement injectables. Mais admettant que le client revoit ses process et son cœur de métier tout les mois … que faire ? Je suis allé chercher une réponse hier dans la présentation animée par Sebastien Ros au sujet de l’Adaptative Object Model. Et je me suis rendu compte que faire du « DDD adaptable » c’était faisable : En DDD on manipule des entités qui ont des propriétés, des relations avec d’autres entités, des value type, et contient des comportements qui encapsule la logique métier. Il suffit donc de faire une méta description des éléments de base pour monter et manipuler un domaine : un customer par exemple est une instance de meta type entité qui a les propriétés suivantes : FirstName, LastName, Address, et une liste de commande. Son comportement se résume ainsi : s’il a déjà dépensé 500 euros, il a droit à 10% de remise sur sa prochaine commande. Au-delà des objets du domaine, on peut aussi automatiser la création des repositories si une entité est marquée par l’attribut « root agregate » …
Imaginons que c’est faisable, et imaginons qu’une société A a racheté une société B, et imaginons que société A et B on déjà des domaines adaptables (utilisation de l’AOM dans une optique DDD). La mise en relation des systèmes d’information devient simple car il suffit de s’échanger les méta-descriptions respectives de leurs domaines et enfin d’appliquer des règles de transformation. Par exemple :
Pour la société A, un customer possède les propriétés : AddressLine, ZipCode, Country et pour la société B un Customer a la propriété Address. La règle à introduire côté société B est : Address = AddressLine + ZipCode + Country. Toutes ces règles sont au niveau des méta-descriptions et sont facilement échangeable : Ficher XML par exemple. Mieux encore, si l’on met en place un monitoring des fichiers de méta-description on peut automatiquement mettre à jour les règles d’adaptation au sein des sociétés s’interfaçant avec le système.
Vélib, Mappoint, et .NET 3.5
Novembre 6th, 2007Après le démarrage de velib, plusieurs mashup ont consolidé le service Vélib et les services de localisation géographique (la majorité a opté pour Google Map) afin de permettre aux internautes de localiser les stations vélib les plus proches et leurs statuts en temps réel: nombre de vélos, et bornes libres. Un des sites les plus abouti est http://www.unvelovite.com/Velib/
Le souci avec l’utilisation des API de Google Map par exemple, est le javascript inclus dans les pages rendues. Ce javascript exclut les utilisateurs mobiles |-| Or lors des dernières grèves combien d’entre nous ont souhaité avoir accès en fonction de leur position à une carte avec les stations les plus proches et leurs états . Pour ceux qui se séparent pas de leur PC portable et ont une carte d’accès internet 3G+ le problème ne se pose pas :>>.
Bon ! Passons aux choses sérieuses :
L’application, baptisée VelibLightUp, doit pouvoir consommer les services fournis par deux fournisseurs de service : Velib et Mappoint (Si vous vous demandez pourquoi MapPoint et pas GoogleMap la reponse est simple : Vivement Microsoft :>).
Premier constat Mappoint offre via son service web un grand nombre de services et de classes, il y en a tellement qu’on risque de ce perdre, donc afin de simplifier la tâche aux couches applicatives supérieures, une facade de MapPoint est de grande utilité.
Pour le service Vélib et vu qu’il est de type REST (http://en.wikipedia.org/wiki/REST) et afin d’homogénéiser la consommation de ces services, un Velib proxy nous donnera l’illusion d’avoir un web service proxy (classique), ceci va aussi nous permettre de mapper l’XML rendu par le service Velib à des objets qui auront un sens fonctionnel et qui formeront le domaine velib.
Ayant la facade MapPoint et le Proxy Velib, on va quand même pas laisser la couche présentation ou business manipuler ces deux services. Non ! Un Service Agent s’impose, c’est lui qui va consolider les deux fournisseurs de service et offrir aux couches supérieures les méthodes nécessaires.
Basta Blabla voyons voir l’organisation du projet:

Le diagramme de classe du domaine Velib est le suivant, ce domaine n’est que le mapping de l’XML rendu par les services Velib :
+Le listing de toutes les stations. http://www.velib.paris.fr/service/carto
+Pour une station donnée il retourne son état (vélo dispo, bornes libres …) : par exemple pour la station qui a pour id 20020 http://www.velib.paris.fr/service/stationdetails/20020
voir le diagramme: http://www.dotnetguru2.org/media/diagram.jpg
Voyons comment le proxy velib encapsule les appels au service REST :
Par exemple pour récupérer la liste des stations:
/// <summary> /// Return the list of all stations by calling the rest service: /// http://www.velib.paris.fr/service/carto /// </summary> /// <returns>List of Domain Station</returns> public List<Station> GetStationList() { //Opps Linq to XML XDocument xDocument =null; xDocument = XDocument.Load( "http://www.velib.paris.fr/service/carto"); List<Station> stationList = new List<Station>(); // call GetDocumentElementByTagName to // get only the part of the xDocument holding station data var stations = GetDocumentElementByTagName(xDocument,"markers"); // for each XElement in the Stations to build Station Domain Object foreach (XElement element in stations) { //Get the station Id int stationId = Convert.ToInt32( GetAttributeValue(element, "number")); //Get the Station Name string stationName = GetAttributeValue(element, "name"); //Get the station address string stationAddress = GetAttributeValue(element, "address"); //Get the station Full Address string stationFullAddress = GetAttributeValue(element, "fullAddress"); //Get the station latitude string stationLatitude = GetAttributeValue(element, "lat"); //Get the station longitude string stationlongitude = GetAttributeValue(element, "lng"); //Map the station status (open = 1, close = 0) StationStatusEnum stationStatus = (StationStatusEnum) Convert.ToInt32(GetAttributeValue(element, "open")); //Build the station Geographic Coordinates GeographicCoordinates stationCoordinates; stationCoordinates = new GeographicCoordinates( ConvertStringToDouble(stationlongitude), ConvertStringToDouble(stationLatitude)); //Finaly build a station domain object Station station; station= new Station(stationId, stationName, stationAddress, stationFullAddress, stationCoordinates, stationStatus); //Finaly add the station to the list of station stationList.Add(station); } return stationList; }
Pour la méthode GetAttributeValue, qui est appelée plusieurs fois dans la méthode précédente, elle permet de retourner la valeur d’un attribut en fonction de son nom, le code est le suivant:
/// <summary> /// Based on an element and an attribute name return the value of that attribute /// </summary> /// <param name="element">element to fetch in the attribute value</param> /// <param name="attributeName">element name</param> /// <returns></returns> private string GetAttributeValue( XElement element, string attributeName) { var query = from attribute in element.Attributes() where attribute.Name == attributeName select attribute.Value; List<string> attibuteValueList = new List<string>(query); return (attibuteValueList.Count > 0) ? attibuteValueList[0] : string.Empty; }
Passons à Mappoint, le code de la méthode principale GetLocationsMap est le suivant:
/// <summary> /// Based on a set of positions, messages return a /// map that highlights these positins /// and associate a message for each position /// </summary> /// <param name="positionList">List of the positions to place the pushpins</param> /// <param name="messageList">list of messages to print on the map (pushpin)</param> /// <param name="scale">map scale</param> /// <param name="width">Image width</param> /// <param name="height">Image width</param> /// <param name="returnType">Type of the returned map</param> /// <returns></returns> public MapImage GetLocationsMap(List<LatLong> positionList, List<string> messageList, double scale, int width, int height, MapReturnType returnType) { // Map point service used to get the map RenderServiceSoap renderService = new RenderServiceSoap(); //set the user culture CultureInfo culture = new CultureInfo(); culture.Name = "fr"; culture.Lcid = 12; //User info Render Header UserInfoRenderHeader userHeader = new UserInfoRenderHeader(); userHeader.Culture = culture; //Set service properties renderService.Credentials = GetCredential(); renderService.Timeout = 30000; renderService.PreAuthenticate = true; renderService.Credentials = GetCredential(); renderService.UserInfoRenderHeaderValue = userHeader; //Set the view scale ViewByScale[] viewArray = new ViewByScale[1]; viewArray[0] = new ViewByScale(); viewArray[0].CenterPoint = positionList[0]; viewArray[0].MapScale = scale; //For each position set a pushpin Pushpin[] pinArray = new Pushpin[positionList.Count]; for (int i = 0; i < positionList.Count; i++) { pinArray[i] = new Pushpin(); //set the message to associate with the pushpin pinArray[i].Label = messageList[i]; pinArray[i].PinID = i.ToString(); if (i == 0) { //for the first pusshpin it represents the user position //so use a different ICON (RED FLAG) pinArray[i].IconName = "29"; } else { //For other position use the default icon pinArray[i].IconName = "0"; } //set pushpin data source pinArray[i].IconDataSource = "MapPoint.Icons"; LatLong latLong = new LatLong(); pinArray[i].LatLong = positionList[i]; } //Set the map options MapOptions options = new MapOptions(); ImageFormat imageFormat = new ImageFormat(); imageFormat.Height = height; imageFormat.Width = width; options.Format = imageFormat; options.ReturnType = returnType; // set the map specification MapSpecification mapSpecification = new MapSpecification(); mapSpecification.Options = options; mapSpecification.Views = viewArray; mapSpecification.Pushpins = pinArray; mapSpecification.DataSourceName = "MapPoint.EU"; // ouuf finaly call the service MapImage[] imageArray = renderService.GetMap(mapSpecification); // return the first image hoping that we'll get just one :D return imageArray[0]; }
Disons que Map point service et le velib proxy sont finalisés on passe au Service Agent qui va marier les deux services. la méthode GetNearStation prend les paramètres suivants : la rue, le code postal, la ville et pays et retourne une url d’une image (qu’on peut afficher sur un mobile par exemple :D ) cette image contient un drapeau rouge qui représente la position de l’utilisateur et des drapeaux bleus qui correspondent aux stations velib autour de la position de l’utilisateur. En plus, le statut de chaque station est associé respectivement à chaque drapeau. Pour limiter la recherche de l’utilisateur aux stations qui sont les plus proches, on se base sur les coordonnées géographiques de son adresse et on fait varier la latitude et longitude de ~0.03 degrés. Ce qui donne pour 101 avenue des Champs-Elysées, 75008:

Pour voir la carte (taille réelle 800px/800px): http://www.dotnetguru2.org/media/demo.gif
Le code de cette méthode est le suivant:
public string GetNearStation(string streetAddress, string postCode, string city, string country) { // List of Station geo-position List<LatLong> stationPositionList = new List<LatLong>(); //List of station status List<StationStatus> stationStatusList = new List<StationStatus>(); //List of messages associate with a station List<string> stationInfoList = new List<string>(); // Format the address for Map point service string formatedAddress = FormatAddress(streetAddress, postCode,city, country); //Init the map point service MapPointService service = new MapPointService(); //Get the user lat & long LatLong addressLatLong = service.GetAddressLatLong(formatedAddress); //Init the Velib service VelibServiceProxy velibServiceProxy = new VelibServiceProxy(); //Find the station in the same Arrondissement as the user // ~O.OO3 ° var query = from nearStation in velibServiceProxy.GetStationList() where nearStation.Coordinate.Latitude < addressLatLong.Latitude + 0.003 && nearStation.Coordinate.Latitude > addressLatLong.Latitude - 0.003 && nearStation.Coordinate.Longitude < addressLatLong.Longitude + 0.003 && nearStation.Coordinate.Longitude > addressLatLong.Longitude - 0.003 select nearStation; //Set the list from the query List<Station> stationList = query.ToList<Station>(); //Get Station Info stationStatusList = GetStationStatusList(stationList); //Build the messages stationInfoList = StartBuildingStationInfoList(stationList); stationInfoList = EndBuildingStationInfoList(stationInfoList, stationStatusList); //Prepare the list of station lat and long //Convert the list of station to LatLong Mappoint //object stationPositionList = stationList.ConvertAll<LatLong>( new Converter<Station, LatLong>(delegate(Station station) { LatLong latLong = new LatLong(); latLong.Latitude = station.Coordinate.Latitude; latLong.Longitude = station.Coordinate.Longitude; return latLong; })); //Call the GetLocationsByAddress of the mappoint // service facade we the appropriate param MapImage map = service.GetLocationsByAddress( formatedAddress, POSITION_MSG, stationPositionList, stationInfoList, 5000, 800, 800, MapReturnType.ReturnUrl); //Yep! get the img url return map.Url; }
Pour l’utilisation de cette méthode par une page aspx il y pas plus simple. En prenant cette page par exemple: http://www.dotnetguru2.org/media/aspx.jpg
Le code associé au button click est le suivant:
protected void Button1_Click(object sender, EventArgs e) { ServiceFacade service = new ServiceFacade(); string mapUrl = service.GetNearStation(TextBox1.Text, TextBox2.Text, "Paris", "France"); Image1.ImageUrl = mapUrl; Image1.Visible = true; }
Je vais publier le code sur Codeplex, mais d’ici là pour ceux qui veulent avoir le code de la solution (orcas beta 2) ils peuvent me contacter.
1ier post
Septembre 19th, 2006Voilà, on y est mon premier post !:D
Sous l’ampleur de l’émotion, je me limite à inaugurer mon blog et à remercier Sami et Sébastien …