Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox



Le framework architectural INDIGO

Compatible Flex 2. Cliquer pour en savoir plus sur les compatibilités.Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par ITERATIF (Olivier Bugalotto), le 24 janvier 2007

En tant que architecte, chef de projet et developpeur, nous sommes toujours à la recherche d'une architecture d'application solide, évolutive et maintenable. L'architecture n-tiers repond a ces besoins quels que soit le langage utilisé meme si pour autant le langage influ sur leurs implémentations.

Une architecture n-tiers comprend généralement une couche de présentation, une couche de services et d'objets métier et une couche d'accès aux données.

Depuis mes debuts en developpement .net en 2000 avec VS.NET, j'ai beaucoup utilisé les designs patterns pour implémenter une architecture n-tiers. Mes applications sont solides et évolutives mais au detriment d'une maintenance trés lourdes a causes des indirections causés par l'emploi abusif des patterns.

En 2004, voila que fait son apparition les conteneurs dits “légers” à travers les projets comme Spring, Avalon ou encore Pico qui utilise un principe simple de la POO: “Ne nous appellez pas, nous vous appellerons” appelé aussi : “le principe d'Hollywood”.

Il faudra attendre en aout 2004 le portage spring (java) en .net avec Spring.net. Ce framework architectural repondait à mes besoins, j'ai donc developpé toutes mes applications en utilisant les bases et principes de la POO et pour gérer les dépendances entre chaque couche le framework Spring.net. Ainsi mes applications etaient enormenent plus facile a maintenir.

Je vais aussi repondre à une question que j'avais posé dans un pécédent billet : Comment utiliser les designs patterns ? Et cette reponse est basée sur ma propre exprience sur le sujet, il est totalement inutile d'apprendre par coeur les 23 principaux patterns, parce qu'ils peuvent se deduire simplement par application des bases et principes de la POO. Il m'est d'ailleurs arriver aprés implémentation d'un code de voir apparaitre un design pattern. Mais si vous ne “transpirez” pas les design patterns (How to use design pattern interview de Erich Gamma membre du celebre Gang Of Four et co-inventeur du framework JUnit) vous ne pourrez pas en comprendre leurs veritables utilités.

Toute la puissance du conteneur léger reside dans sa capacité a resoudre les dependances entre chaque couche de votre architecture et ainsi laisser votre esprit concentré sur les véritables besoins de votre application.

Je vais partager avec vous mon framework Indigo porté en AS3 et qui implémentant un conteneur léger inspiré du framework Spring.net à travers un exemple simple et pédagogique de recherche de films.

Le framework Indigo propose un conteneur léger avec injection de code par accesseurs (Setter Injection) Je vous propose de lire l'article trés connu de la communauté .net à l'adresse suivant : http://www.dotnetguru.org/articles/dossiers/ioc/ioc.htm ainsi que l'excellent article de Martin Fowler : http://www.dotnetguru.org/articles/dossiers/ioc/Fowler/IoC.htm sur les conteneurs légers.

Télécharger le framework INDIGO en beta 1 et les exemples de ce post : INDIGO

Voici l'architecture en couche de l'exemple 1 MovieFinder :

Si l'on regarde de plus pret le code de la classe MovieLister, nous constatons qu'elle est dépendante de la classe SimpleMovieFinder :

public class MovieLister extends EventDispatcher {
      private var _movieFinder:SimpleMovieFinder;
      ...
      public function MovieLister() {
            _movieFinder = new SimpleMovieFinder();
	    _movieFinder.fileName = "movies.txt";
      }
      ...
      public function moviesDirectedBy(director:String):Array {
            searchDirector = director;
	    var allMovies:Array = _movieFinder.findAll();
	    return allMovies.filter(filterMethod);
      }
      ... 
}

Alors la premiere chose à faire et de ne pas dependre de classes concrètes mais plutot d'abstractions, ainsi on pourrai plutot utiliser une interface :

public class MovieLister extends EventDispatcher {
      private var _movieFinder:IMovieFinder;
      ...
      public function MovieLister() {
            _movieFinder = new SimpleMovieFinder();
      }
      ...
      public function moviesDirectedBy(director:String):Array {
            searchDirector = director;
	    var allMovies:Array = _movieFinder.findAll();
	    return allMovies.filter(filterMethod);
      }
      ... 
}

Mais nous restons toujours depend de la classe SimpleMovieFinder, nous pourrions utiliser une fabrique d'objet qui nous retournerez une instance de type IMovieFinder mais nous resterions encore dependant de la fabrique elle-meme.

D'ou l'idée d'un conteneur qui s'occuperait des dépendances entre les classes :

Dans un premier temps, nous imposons un contrat à la classe SimpleMovieFinder à l'aide de l'interface IMovieFinder:

public class SimpleMovieFinder implements IMovieFinder {
      ...
      public function SimpleMovieFinder() {
 
      }
      ...
      public function allMovies():Array {
            return _movies;
      }
      ... 
}

avec IMovieFinder :

public interface IMovieFinder {
      function allMovies():Array;
}

Pour permettre au conteneur léger de construire la dépendance entre les deux classes nous devons definir sur la classe MovieLister un accesseur (Setter) que le conteneur utilisera pour instancier la dépendance :

public class MovieLister extends EventDispatcher {
      private var _movieFinder:IMovieFinder;
 
      public function set movieFinder(value:IMovieFinder):void {
            _movieFinder = value;
      }
 
      // Cette propriete n'est pas utile mais nous l'utiliserons
      // pour verifier que le conteneur a bien fait son travail
      public function get movieFinder():IMovieFinder {
	    return _movieFinder
      }
      ...
      public function MovieLister() {
      }
      ...
      public function moviesDirectedBy(director:String):Array {
            searchDirector = director;
	    var allMovies:Array = _movieFinder.findAll();
	    return allMovies.filter(filterMethod);
      }
      ... 
}

Nous remarquons que notre classe n'est plus dependante d'une classe concrete mais bien d'une abstraction…

Maintenant nous allons construire un conteneur léger et configurer les dépendances à l'aide d'un fichier de configuration.

Le fichier de configuration :

<?xml version="1.0"?>
 <objects>
        <object name="myMovieLister" type="examples.movieFinder.MovieLister">
            <property name="movieFinder" ref="myMovieFinder"/>
        </object>
        <object name="myMovieFinder" type="examples.movieFinder.SimpleMovieFinder"/>
</objects>

Les tags utilisés sont simple :

  • objects est la racine du document XML
  • object indique qu'un nouvel objet doit etre ajouté dans le référentiel du conteneur :
  • l'attribut name de object indique le nom de l'objet au sein du conteneur, c'est avec ce nom que nous pourrons recuperer une instance de l'objet.
  • l'attribut type de object indique la classe qui permet d'instancier l'objet
  • le tag property permet d'indique au conteneur le nom de l'accesseur de l'objet mais surtout sa dependance à l'aide de l'attribut ref

Sur le fichier SearchMovie.mxml nous instancions la classe XmlObjectFactory, un conteneur léger qui utilise le fichier xml de configuration pour connaitre les objets a instancier mais surtout leurs dépendances :

import indigo.objects.factory.xml.XmlObjectFactory;
import indigo.objects.factory.IObjectFactory;
 
private var lister:MovieLister;
 
private function doInit():void {
	// Nous chargeons le fichier de configuration XML
	var url:URLRequest = new URLRequest("config.xml");
	var loader:URLLoader = new URLLoader();
	loader.addEventListener(Event.COMPLETE,completeHandler);
	loader.load(url);
}
 
private function completeHandler(e:Event):void {
	var loader:URLLoader = e.target as URLLoader
	var root:XML = XML(loader.data);
	// Lorsque le fichier est charge nous le passons au constructeur
	// de la classe XmlObjectFactory
	var factory:XmlObjectFactory = new XmlObjectFactory(root);
	// Le conteneur leger charge les objets dans son referentiel
	factory.load();
 
	// La méthode getObject() de XmlObjectFactory permet à l'aide du nom de l'objet
	// d'instancier l'objet correspondant ainsi que ces dependances
	lister = factory.getObject("myMovieLister") as MovieLister;
	Alert.show(lister.movieFinder); // sortie : "[object SimpleMovieFinder]"
}

Bien entendu vous devez indiquer au compilateur d'inclure la classe SimpleMovieFinder :

Et si tout se passe bien, ca devrait marcher…

Continuons avec le conteneur léger avec un exemple 2 ou au lieu d'avoir les films écrient en dur dans la classe, ils soient chargés à partir d'un fichier texte à l'aide d'une classe.

Voici cette classe :

package examples.movieFinder
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
 
	public class ColonDelimitedMovieFinder extends EventDispatcher implements IMovieFinder
	{
		private var _fileName:String;
		private var _movies:Array;
		private var loader:URLLoader;
 
		public function set fileName(value:String):void {
			_fileName = value;
		}
 
		function ColonDelimitedMovieFinder() {
			_movies = [];
		}
 
		public function findAll():Array
		{
			return _movies;
		}
 
		public function load():void {
			if(!loader) {
				loader = new URLLoader();
			}
			loader.addEventListener(Event.COMPLETE,completeHandler);
			loader.load(new URLRequest(_fileName));
		}
 
		private function completeHandler(e:Event):void {
			var loader:URLLoader = e.target as URLLoader;
			loader.removeEventListener(Event.COMPLETE,completeHandler);
 
			_movies = getMovies(loader.data);
 
			dispatchEvent(new Event(Event.COMPLETE));
		}
 
		private function getMovies(src:String):Array {
			var movies:Array = [];
 
			var lines:Array = src.split("\r\n");
			for each(var movie:String in lines) {
				var properties:Array = movie.split(":");
				movies.push({title:properties[0],director:properties[1]});
			}
 
			return movies;
		}
	}
}

Modifions maintenant notre fichier de configuration pour lui ajouter la nouvelle classe :

<?xml version="1.0"?>
 <objects>
        <object name="myMovieLister" type="examples.movieFinder.MovieLister">
            <property name="movieFinder" ref="anotherMovieFinder"/>
        </object>
        <object name="myMovieFinder" type="examples.movieFinder.SimpleMovieFinder"/>
        <object name="anotherMovieFinder" type="examples.movieFinder.ColonDelimitedMovieFinder">
	    <!-- Comme vous pouvez le voir nous pouvons aussi definir une donnee autre qu'un objet a l'aide de l'attribut value -->
            <property name="fileName" value="movies.txt"/>
        </object>
</objects>

Nous avons vu que le conteneur gère les dépendances d'objets mais il peut le faire aussi avec d'autres types comme des strings, numbers, etc… C'est ce que nous allons faire avec le nom du fichier texte a charger.

Comme pour l'exemple précédent il faut penser à inclure la classe ColonDelimitedMovieFinder en arguments du compilateur. Compilons et nous devrions voir apparaitre le message suivant dans la fenetre d'alerte : \[object ColonDelimitedMovieFinder\]

Maintenant avec l'exemple 3, ou cette fois-ci nous avons une interface utilisateur :

Il vous suffit de modifier la reference de l'objet pour la dependance avec l'objet myMovieLister et ceci sans recompiler, simplement en modifiant le fichier de configuration :

Testons :

Test1

puis maintenant changeons de reference :

Testons :

Test2

Tout la puissance réside sur la delegation des dépendances entre couches par un conteneur léger qui s'appui unique sur un principe de la programmation orientée objet vous laissant ainsi les mains libres pour vous concentrez sur votre application sans pour autant connaitre tous les patterns existants pour developper une application solide, evolutive et maintenable. voila maintenant je vous laisse en juger par vous-même avec les exemples que je fournis avec le code source du framework INDIGO.

Dans un prochain billet, je vous montrerai comment diviser chaque couche de votre architecture en des modules (swf) et les charger à l'aide d'un fichier de configuration XML en ajoutant un attribut au tag object :

<?xml version="1.0"?>
 <objects>
        <object name="myMovieLister" type="examples.movieFinder.MovieLister" module="model">
            <property name="movieFinder" ref="myMovieFinder"/>
        </object>
        <object name="myMovieFinder" type="examples.movieFinder.SimpleMovieFinder" module="dao"/>
        <object name="anotherMovieFinder" type="examples.movieFinder.ColonDelimitedMovieFinder">
            <property name="fileName" value="movies.txt"/>
        </object>
</objects>

N'hésiter pas à me contacter pour plus d'informations.

Par ITERATIF - BUGALOTTO Olivier (2007) Vous pouvez retrouver ce tutorial et des commentaires à ce sujet sur mon blog