Ail. Un injecteur de dépendances pour Ruby.

Si vous n'avez jamais entendu parler du pattern 'Dependency Injection' ni d'IoC (Inversion of Control) vous trouverez plus d'informations à ce sujet dans le chapitre 'Injection de dépendances'

Ail est du type setter injector du fait que Ruby se prête à ce genre d'implémentation, un des problèmes de ce choix est que que les dépendances d'une ressource ne peuvent être injectées que lorsque un objet représentant une ressource est créé.
Dans Ail les dépendances d'un objet peuvent être utilisées dès la fonction 'initialize'.

Avoir injecté une dépendance ne signifie pas nécessairement que l'objet correspondant à la ressource ait été créé, mais que cette ressource est accessible, Ail permet une évaluation paresseuse des ressources.

Ail est un container léger : L'utilisation de ses fontionnalités de base n'entraine pas d'overhead significatif et peut souvent faire économiser des ressources.
Il est souple : Son API peut être utilisée de différentes manières sans impliquer un cadre srict de développement, les classes implémentant les ressources n'ont pas besoin de connaître cette API, l'utilisation de Ail permet de réduire de manière significative le code de ces classes et des classes non conçues pour Ail peuvent être utilisées comme ressources.
Il est extensible : C'est un outil de base sur lequel viendront se greffer des fonctionnalités de style AOP.

La différence avec d'autres injecteurs de dépendances peut être trouvée dans son API simple qui permet de définir de manière descriptive et concise les dépendances envers les resources.
Il comprend également des méthodes permettant de visualiser le graphe de dépendance des ressources utilisées dans une application.


1 Introduction.

Le terme Ressource correspond à ce ce l'on appelle souvent 'service' ou 'composant', je préfère utiliser le terme 'ressource' parce qu'il est plus général.
Une ressource peut être considérée comme un objet, la relation 'ressource -> un objet désiré' est d'ailleurs vraie quand cet objet existe, le reste du temps c'est plutôt un objet permettant d'accéder à cet objet de notre désir.

1.1 A quoi ça sert?

Les logiciels non triviaux comprennent un certain nombre de composants que nous voulons souvent découpler de manière à pouvoir connecter ces composants sans rien changer à leur cablage interne et ainsi pouvoir les réutiliser plus facilement dans d'autres logiciels.

L'exemple suivant est un peu fabriqué, vos logiciels sont sans doute un peu plus complexes et surtout, je l'espère, plus utiles. Je vous demanderai d'essayer d'extrapoler plutôt que de vous présenter un exemple complexe.
Vous avez écrit une class 'calc' (calculateur) et vous voulez l'utiliser dans un script que nous appelerons 'main'.

class Calc
  require 'mylogger.rb'
  def initialize
    @logger = Logger.new('calc.log')
  end
  def add(a, b)
    a.to_i + b.to_i
  end
  def divide(a, b)
    a.to_i / b.to_i
  rescue => e
    @logger.log("divide #{a} #{b} failed: #{e.message} #{e.class}")
    nil
  end
  # snip substract, multiply, fact etc.
end


Cette classe n'est pas si naïve qu'on pourrait le penser, l'auteur a bien pensé que la première personne qui l'utilisera essaira de diviser quelque chose par zero et il a raison de logger cette erreur pour un examen subséquent. D'ailleurs cette solution doit marcher.
La classe Calc doit cependant connaître le nom de la classe du logger et celui du fichier 'mylogger.rb' où elle est définie, ce qui est un couplage fort entre la classe Calc et la classe Logger.
Vous devez avoir une classe Logger définie sous ce nom dans le fichier mylogger.rb pour faire marcher le calculateur.

Il est probable que ce calculateur puisse être intéressant pour une application qui a d'autres composants qui aiment logger les erreurs sur un même support. Peut-être que a et b que l'on doit additionner ou diviser ont été obtenus via un GUI, des sockets sécurisées ou trouvés dans une database, toutes choses qui aussi logger leurs problèmes que vous pouvez vouloir voir apparaître sur un même fichier.

Il y a une solution simple, c'est de créer une instance de la class Logger et de la donner en paramêtre à Calc en l'instanciant.
La methode initialize de Calc serait :

  def initialize(logger)
    @logger = logger
  end

Ca a l'air mieux et ça doit marcher.
C'est là qu'il va vous falloir extrapoler, les instanciations de classes demandent parfois déjà plusieurs arguments et il va falloir en ajouter pour chaque composant avec lesquels elles sont connectées.
Le nombre d'arguments positionels à une méthode peut ne pas être imposé par le langage, il doit l'être par l'attention que l'on porte au confort des programmeurs.
Le problème ne tient pas seulement au nombre d'arguments mais aussi à l'ordre dans lequel il faut les fournir. il faudrait parfois fournir beaucoup d'arguments à new() et dans le bon ordre lorsque cette classe fait appel a de nombreux composants.

Il y a aussi un autre problème :
Dans l'exemple, un objet de la class Calc fait appel a un objet de la classe Logger, cela demande que l'objet de la classe Logger ait été instancié avant celui de la classe Calc.
Mettons que le logger veuille numéroter ses messages, il fera naturellement appel à l'objet de la classe Calc qu'il aura reçu en argument, ce qui demande que l'objet de la classe Calc ait été instancié avant celui de la classe Logger.

Aïe!

Evidemment, le logger pourrait instancier son propre objet Calc, mais dans le cas d'objets volumineux, c'est préjudiciable à l'espace d'objets.
Il est également possible de trouver un mécanisme compliqué pour communiquer à chaque objet la référence de l'autre. cela augmente le couplage entre les classes de ces objets.

Des personnes ont trouvé une solution très simple de résoudre ce problème en disant qu'il s'agit là d'une mauvaise conception ou d'une mauvaise techique de programmation et se passent de cette possibilité. Il n'ont pas tort, ces dépendances croisées impliquent dans le développement d'un composant une certaine connaissance du fonctionnement d'autres composants et elles sont assez généralement à éviter.

Le graphe de dépendances peut être représenté par ce schéma :
graphe de dépendance A ---> B signifie : A est requis par B

Le problème survient lorsque le graphe comporte des cycles.

Ail ne propose pas de limiter les possibilités de conception ni de pallier ses éventuels défauts mais d'être un outil pour implémenter un design, il aide donc aussi à implémenter de manière simple ce genre d'architecture.
il le fait en allègeant le code des classes qu'il décharge de la gestion des dépendences.

Plus de raisons de trouver une utilité à un injecteur de dépendances apparaîtront dans les chapitres suivant de cette introduction.

1.2 La solution Ail.

Ail résoud ces problèmes en extrayant la logique des dépendances entre objets des définitions des classes de ces objets et en offrant une possibilité de décrire ces dépendances.
Le graphe de dépendance peut être cyclique ou non, connexe ou non, le code des classes peut être écrit en parfaite méconnaissance du nom des classes dont elles dépendent, elles ont simplement à indiquer le symbole qui leur servira de référence à cette resource.

Dans l'exemple suivant, nous avons ajouté une nouvelle ressource correspondant à l'objet IO permettant d'accéder au fichier log.

Voici à quoi pourrait ressembler une application utilisant le calculateur. et le logger. Mais il existe plusieurs manières d'utiliser Ail comme 'framework', dans le chapitre suivant vous trouverez d'autres façons sans doute plus habiles de l'utiliser..

Son graphe de dépendances peut être représenté ainsi : graphe de dépendance

L'application est decoupée en plusieurs sources. (Vous pouvez trouver ces sources dans la directory ./samples de la distribution).

calc.rb
# file calc.rb
class Calc
  attr_writer :logger
  def add(a, b)
    a.to_i + b.to_i
  end
  def divide(a, b)
    a.to_i / b.to_i
  rescue => e
    @logger.log("divide #{a} #{b} failed: #{e.message} #{e.class}")
    nil
  end
  # snip substract, multiply, fact etc.
end

attr_writer :logger

Cela permet à Ail de pouvoir renseigner la variable d'instance @logger sans avoir recours à un mauvais hack.
On peut remarquer que la classe se passe maintenant de methode initialize.

mylogger.rb
# file mylogger.rb
class Logger
  attr_writer :file, :calc
  def initialize
    @num = 0
    if block_given?
      yield(self)
      log "Logger initialized."
    end
  end
  def log(str)
    @num = @calc.add(@num, 1)
    @file.puts "#{@num} : #{str}"
  end
end

Les attr_writer correspondent aux ressources nécessaires au logger, notons que le la ressource IO correspondant au fichier log a pour symbole :file.
Le logger a besoin des dépendances :calc et :file dans sa fonction initialize de manière à écrire un message sur le fichier log.

yield(self)

Cela permet è Ail de renseigner @logger et @file avant la fin de la fonction initialize ce qui serait le cas sans ce 'yield'.

(Je vous suggère d'utiliser plutôt le logger de la librairie standard. :-)

app.rb
# file app.rb
class App
  attr_writer :calc
  def run
    puts "1 + 1 = #{@calc.add(1, 1)}"
    puts "1 / 0 = #{@calc.divide(1, 0)}"
  end
end

cette classe se passe de commentaires...

inject.rb
# file inject.rb
module Inject

require 'rubrix/ail.rb'
include Ail
require 'app.rb'
require 'calc.rb'
require 'mylogger.rb'

  def Inject.init
    resources = Resources.new( :resources, :app ) { |resources|
      resources.register(:calc, Singleton|Defer,
                        :logger
                        ) { Calc.new() }
      resources.register(:logger, Defaults,
                        [:logFile, :file], :calc
                        ) { |p| Logger.new(&p) }
      resources.register(:logFile, NoDefer
                        ) { File.new('mylog.log', 'a') }
      resources.register(:app, Defaults, :calc ) { App.new() }
    } # end of Resources.new block
  end

end
Il s'agit de la description des resources pour l'injecteur.
Nous allons reprendre les methodes utilisées une à une.

Il débute par des 'require', comme il concentre les dépendances entre les resources, cela peut sembler un bon endroit pour les mettre.

La methode init est chargée de créer l'injecteur et d'enregistrer les différentes ressources. Cette fonction retourne une instance de la classe Resource que l'on peut considérer comme l'injecteur de dépendances.

    resources = Resources.new( :resources, :app ) { |resources|
        ...
    } # end of Resources.new block
    
L'instanciation de la classe Resources produit un container de ressources, On peut indiquer quelques arguments, tous optionels et on devrait fournir un block destiné à y définir les resources. Ces ressources sont définies de manière descriptive, l'ordre dans lequel elles sont définies n'ayant aucune importance.
Le premier argument est le symbole associé au container, s'il n'est pas précisé, le symbole qui sera associé est :anonymous.
Le second argument est le symbole de la ressource qui est considéré comme la racine du graphe de dépendance, si elle n'est pas précisée, le graphe n'aura pas de racine.
Ce block de description des resources prend un argument qui est une référence à ce container.

Bien que l'on puisse mettre à peu près ce que l'on veut dans ce block, sa fonction la plus importante est d'enregistrer des ressources.
      resources.register(:calc, Singleton|Defer,
                        :logger
                        ) { Calc.new() }
La methode Resources#register définit une resource.

:calc
C'est le symbole identifiant la ressource.

Le second argument est un 'ou' entre des options, il est aussi possible d'indiquer 'Defaults' si on désire les options par défaut.
Singleton
Il s'agit d'une constante précisant qu'une seule instance de la ressource doit être créée et être partagée entre les ressources l'utilisant. Pour l'instant vous n'avez d'ailleurs que ce choix... et ce sera le défaut.
Defer indique que l'instanciation de la ressource doit être retardée le plus possible. Elle ne sera instanciée que lorsqu'un message lui sera envoyé. Ici aussi c'est le défaut.
Il est ainsi possible de faire tourner une application sans instancier une partie de ses composants qui ne servent que rarement, par exemple des objets servant à gérer des erreurs qui n'arrivent qu'exceptionellement.

:logger
Après les arguments décrits ci-dessus vient la liste des symboles des ressources auxquelles fait appel la ressource que l'on enregistre, :calc ne dépends que de :logger.

{ Calc.new() }
Il s'agit du block retournant l'objet correspondant à la ressource, ce block sera appelé lorsque l'objet devra être instancié. Par la suite, nous l'appelerons block d'instantiation.

      resources.register(:logger, Defaults,
                        [:logFile, :file], :calc
                        ) { |p| Logger.new(&p) }
Les mêmes remarques s'appliquent. Defaults est synonyme de Singleton | Defer.

[:logFile, :file], :calc
:logger a besoin de 2 ressources.
La ressource :logFile est indiquée sous forme d'un tableau de 2 symboles, le premier est le symbole identifiant la ressource, le second (:file) le symbole sous lequel cette ressource (:logfile) est connue par la ressource que l'on enregistre (ici :logger).

{ |p| Logger.new(&p) }
Ici, le block d'instanciation de la ressource utilise l'argument que Ail fournit à ce block. Cet argument est un block servant à injecter ses dépendances à la ressource.
ce block peut être appelé par yield(self) dans la methode initialize de l'objet que l'on instancie, Cela permet d'utiliser les ressources desquelles dépend l'objet dans la fonction initialize.
Lorsque cet argument du block d'instanciation n'est pas utilisé, les dépendances de la ressource sont injectées juste après que le block d'instanciation est exécuté.

  resources.register(:logFile, NoDefer
                 ) { File.new('mylog.log', 'a') }
Cette ressource correspond à un objet IO, cela veut dire qu'un objet de n'importe quelle classe peut être utilisé comme ressource sans pour cela que cette classe ait été définie en fonction de Ail, même nil peut être une ressource.

NoDefer
L'instanciation de la ressource ne sera pas retardée jusqu'a ce qu'un message lui soit envoyé, l'instanciation se fera dès que Resources#open sera appelé (Resources#open sera abordé un peu plus bas).
Dans ce cas, peut-être parce qu'on ne veut pas attendre qu'une erreur se produise avant de se rendre compte que l'on ne peut pas ouvrir le fichier log.

main.rb, le script.
# file main.rb
require ARGV[0]
#
resources = Inject.init
root = resources.open
root.run
# or 
#Inject.init.open.run
require ARGV[0]
Le fichier définissant l'injecteur peut être paramêtré sur la ligne de commande.
ici ce serait : 'ruby main.rb inject.rb'

resources = Inject.init
C'est l'appel de la fonction init de l'injecteur qui instancie un injecteur de dépendance.

root = resources.open
Ressources#open instancie toutes les resources qui sont définies avec NoDefer et retourne une instance de la ressource donnée de argument ou à défaut celle définie comme racine du graphe de dépendances ou nil. Que cette resource ait été définie comme Defer ou NoDefer, c'est l'instance de la ressource (telle que renvoyée par le block d'instantiation) qui est renvoyée.

root.run
C'est l'appel de la methode 'run' de la ressource 'root' (ici :app).

1.3 Variantes.

Il y a plusieurs façons d'utiliser Ail dans une application. Dans l'exemple du chapitre précédant, main.rb était superflu (oui le reste aussi : ruby -e 'puts(1 + 1)' est bien plus rapide).

1.3.1 Plus simple.

Une Variante pourrait être celle-ci :

# file inject2.rb
require 'rubrix/ail.rb'
include Ail
require 'app.rb'
require 'calc.rb'
require 'mylogger.rb'

resources = Resources.new( :resources, :app ) { |resources|
  resources.register(:calc, Singleton|Defer,
                      :logger
                    ) { Calc.new() }
  resources.register(:logger, Defaults,
                      [:logFile, :file], :calc
                    ) { |p| Logger.new(&p) }
  resources.register(:logFile, NoDefer
                    ) { File.new('mylog.log', 'a') }
  resources.register(:app, Defaults, :calc ) { App.new }
} # end of Resources.new block

resources.open.run
qui pourrait être exécuté à partir de la ligne de commande :
ruby inject2.rb
Cette utilisation est simple mais a le désavantage d'être un peu rigide.

1.3.2 Plus souple.

Une possibilité plus souple serait de paramétrer l'injecteur de manière à appeler le script désiré.

#!/usr/bin/ruby
# file inject3.rb
module Inject

require 'rubrix/ail.rb'
include Ail
require 'app.rb'
require 'calc.rb'
require 'mylogger.rb'

  def Inject.init
    resources = Resources.new( :resources, :app ) { |resources|
      resources.register(:calc, Singleton|Defer,
                        :logger
                        ) { Calc.new() }
      resources.register(:logger, Defaults,
                        [:logFile, :file], :calc
                        ) { |p| Logger.new(&p) }
      resources.register(:logFile, NoDefer
                        ) { File.new('mylog.log', 'a') }
      resources.register(:app, Defaults, :calc ) { App.new() }
    } # end of Resources.new block
  end

end

require ARGV.shift
Et de faire ce script main3.rb
# file main3.rb
Inject.init.open.run
qui pourrait être appelé par :
./inject3.rb main3.rb
L'avantage est de pouvoir appeler d'autres scripts qui utilisent le même injecteur.
Par exemple pour construire un graphe visualisable des dépendances en utilisant l'API de AilGraph.
# file graphO3.rb
require 'rubrix/ailgraph.rb'
include AilGraph
resources = Inject.init
graph = Graph.new( resources )
graph.make_graph('nodes', 'edges', 'mkgraph' )

1.3.3 La meilleure solution ?

C'est du moins celle que je préfère. Elle peut ajouter une flexibilité supplémentaire en inversant une fois de plus le contrôle.
L'injecteur de dépendance ne gère plus que les dépendances entre les objets, la nature exacte des classes de ces objets lui est retiré, cela se fait en plaçant les 'require' dans le script appelé.

#!/usr/bin/ruby
# file inject4.rb
module Inject

require 'rubrix/ail.rb'
include Ail

  def Inject.init
    resources = Resources.new( :resources, :app ) { |resources|
      resources.register(:calc, Singleton|Defer,
                        :logger
                        ) { Calc.new() }
      resources.register(:logger, Defaults,
                        [:logFile, :file], :calc
                        ) { |p| Logger.new(&p) }
      resources.register(:logFile, NoDefer
                        ) { File.new('mylog.log', 'a') }
      resources.register(:app, Defaults, :calc ) { App.new() }
    } # end of Resources.new block
  end

end

require ARGV.shift
Et de faire ce script main4.rb
# file main4.rb
require 'app.rb'
require 'calc.rb'
require 'mylogger.rb'
Inject.init.open.run
Et peut-être celui-ci aussi :
# file graphO4.rb
require 'rubrix/ailgraph.rb'
include AilGraph
resources = Inject.init
graph = Graph.new( resources )
graph.make_graph('nodes', 'edges', 'mkgraph' )
Qui est en fait le même que celui de l'exemple précédant. Du fait que les classes n'ont pas besoin d'être instanciées, les 'require' n'ont aucune utilité.

Nous approchons d'un parfait partage de la responsabilités et d'un découplage maximal des resources.

Les classes permettant d'instancier les objets définissent le boulot que ces objets font sans avoir besoin de connaître la nature des resources auxquelles elles font appel.

L'injecteur de dépendances gère les dépendances entre des objets qu'il ne connaît que de nom.

Le script main choisit les resources à utiliser, c'est dans ce script qui ne peut faire que quelques lignes que se concentre les décisions importantes dans la confection d'un projet.
Dans une phase de test, c'est dans ce script que vous pouvez changer "require 'stub_logger.rb'" par "require 'logger.rb'" quand le logger semble opérationnel, cela sans avoir à changer les dépendances entre les resources ni les ressources elles-mêmes.
En changeant une ligne de code vous pouvez revenir à un état précédant parce que le logger fait inexpliquablement planter votre classe, cela sans affecter en rien les développeurs à qui il est utile et qui utilisent 'mock_calc.rb' à la place de 'calc.rb' que vous écrivez.

Pour les applications formées d'un grand nombre de composants, les 'requires' peuvent être aussi être mis dans un source spécial.
Il y a encore beaucoup de moyens d'utiliser Ail pour construire un framework qui vous convienne.

1.4 Alternatives.

Pour le moment Ail ne fait guère plus de 400 lignes de Ruby, il n'offre que les fonctionalités de base d'un injecteur de dépendances ainsi que des possibilités de visualisation de graphes de dépendances.

Vous pourrez trouver des références à d'autres injecteurs de dépendances sur cette page du site de Jim Weirich.
Needle de Jamis Buck a l'avantage de proposer beaucoup de fonctionalités et d'être bien documenté.

2 API.

2.1 Module Ail.

The Resources class is defined in this module.
Some methods can only be used in the block given to Resources.new, some others cannot be used in this block, this doesn't mean they must not appear in an instantiation block (defined inside thé Resource.new block) but that they must not be called before the initialize lethod of the Resources object is terminated.

2.1.1 Class methods.

 Resources.new([name,[root]] { |resources| block }  -> resources
Creates a resources container.
Returns the created resources container.

name must be a symbol, nil or false when specified.
When it is a symbol, it can used to identify the container otherwise :anonymous is used as a name.

root must be a symbol, nil or false when specified.
When it is a symbol, it is a name of a resource which will be considered as the root resource of the container.

If the optional block is given, it will be passed the resource container as an argument.

Exceptions:

2.1.2 Important instance methods.

 register(name, [options, [*dependencies]]) { |pr| block }  -> name
Add a resource to the container.
This method can only be used in a Resources.new block. It is also the only important Ail method which can be used in a Resource.new block.
Returns the resource name symbol.

name identifies the resource, it must be a symbol.
It must be an unique resource identifier within the resource container.

options must be a Fixnum.
It should be defined by a 'or' of these values : or by Ail::Defaults which means Ail::Singleton | Ail::Defer

dependencies is a list of resources the resource being registered depends on. An item of this list has one of the following format :

block : often referred as instantiation block in this document.
It receive as argument a proc aka injection block which may be passed to the new method of the class to instantiate. this block can be used by the initialize method to retrieve it's dependencies by :
yield(self)
(As far as possible, the initialize method should use the block_given? method before retrieving and using it's dependencies).

Exceptions:

 instantiate([name])
Instantiate the resource identified by name when it is not already instantiated.
When name is not specified, the root resource name as defined in the new method is used.
Returns the resource instance.

Exceptions:

 open([name])
Instantiate the resource identified by name and all the resources registered as NoDefer when they are not already instantiated.
When name is not specified, the root resource name as defined in the new method is used.
Returns the instance of the resource identified by name.

Exceptions:

 open_res([name])
Behaves mostly as open, the difference is that only the resources defined as NoDefer which the resource 'name' depends on (directly or not) are instantiated.

2.1.3 Other instance methods.

You can easily survive without using them.

each_connected_to(name) { |sym, dep| block }  -> resources 
Executes block for each edge of the container subgraph of resources which the resource identified by name depends on.
sym is the name of a resource, dep is an object which responds to rsym (the name of a resource which sym depends on) and usym (the symbol by which sym reference rsym).
Can be called from the block given to new().
Exceptions : NoBlockGivenError, MissingResourceError.
each_depend { |sym, dep| block }  -> resources 
Executes block for each edge of the container graph.
sym is the name of a resource, dep is an object which responds to rsym (the name of a resource which sym depends on) and usym (the symbol by which sym reference the resource which it depends on).
Can be called from the block given to new().
Exceptions : NoBlockGivenError.
each_key { |sym| block }  -> resources 
Executes block for each resource, passing it the resource name symbol.
Can be called from the block given to new().
empty?  -> true or false
True or false depending of the number or resource registered.
Can be called from the block given to new().
has_key?(sym)  -> true or false
True when the container includes a resource identified by sym.
Alias : key?, include?, member?
Can be called from the block given to new().
instance(sym)  -> an object
Returns the resource instance when the resource is instantiated, nil otherwise.
Cannot be called from the block given to new().
Exceptions : NotInitializedError, MissingResourceError.
instantiated?(sym)  -> true or false
true or false whether the resource is instantiated or not.
Cannot be called from the block given to new().
Exceptions : NotInitializedError, MissingResourceError.
keys  -> an array
return the array of the resource name symbols.
Can be called from the block given to new().
 name  -> a symbol
Returns the symbol which identifies the Resources instance.
Can be called from the block given to new().
 root  -> a symbol
Returns the root symbol.
Can be called from the block given to new().
size  -> a Fixnum
Number or resource registered.
Alias : length
Can be called from the block given to new().

2.2 Module AilGraph.

The Graph class of his module provides methods which produce files in the format expected by Graphdot01.rb.
The graphdot methods produce files in the dot format which may be used by to produce visual graphs.
Graphs which appear in this documentation are made using these methods.

2.2.1 Class Graph.

If you use a Graph class object as an Ail resource, you can inject it a Resource object.

new([resources = nil])
Returns a Graph object.
make_graph(nodefn, edgefn, scriptfn = nil)  -> nil
It creates files which may be used to produce a visual graph of the whole resources container.
The three arguments are output file names.
nodefn is the nodes filename.
edgefn is the edges filename.
scriptfn when specified is a script filename. make_graph will create this script which invoke graphdot01.rb to produce a dot file.

Example :
resources = Resources.new( ... ) {...}
graph = Graph.new(resources)
graph.make_graph('nodes.txt', 'edges.txt', 'script.sh')
Then, to make the graph from the command line :
chmod +x script.sh
./script.sh > graph.dot
dot -Tpng -o graph.png graph.dot
make_connected(name ,nodefn, edgefn, scriptfn = nil)  -> nil
Produce a visual subgraph of the resource identified by name and all its dependencies.

2.2.2 Example.

This script produce a subgraph when used with the dependency injector of chapter 'Ail solution.'
# file graphO3.rb
require 'rubrix/ailgraph.rb'
include AilGraph
resources = Inject.init
graph = Graph.new( resources )
graph.make_graph('nodes', 'edges', 'mkgraph' )
It may be called from the command line :
./graph03.rb inject.rb logger
./mkgraph > logger.dot
dot -Tpng -o logger.png logger.dot
to produce this png file : graphe de dépendance de logger

3 Injection de dépendances.

3.1 Introduction.

Bien que le concept d'injection de dépendances ne soit pas compliqué, il n'est pas très facile à expliquer et une métaphore pourra m'y aider.

Beaucoup de plats cuisinés nécessitent de l'ail dans leur préparation mais on n'a jamais vu de plats s'assaisonner d'ail eux même, ni de gousse d'ail s'émincer elle-même pour venir garnir un plat. Cela nécessite une tierce personne qui connait les ingrédients nécessaire à la recette et le moment de les utiliser.
Le plat et l'ail ainsi que les éventuelles préparations intermédiaires (aïoli) peuvent être vus comme des ressources correspondant à des composants logiciels dont l'injecteur de dépendances serait le cuisinier.
Certains cuisiniers piquent d'ail des rotis, si vous en croisez un, vous pouvez lui dire que c'est un setter injector.

L'injecteur de dépendances est une tierce partie qui concentre la gestion des dépendances en en déchargeant les autres composants. Bien entendu, ces composants doivent savoir quelles ressources ils utilisent lorsqu'ils les utilisent mais sans connaître leur nature exacte, la ressource utilisée doit simplement répondre aux messages qui lui sont envoyés.
Les classes n'ont pas à s'occuper du cablage entre les ressources, (il a été dit que cela prend 30% du code), de leur recherche et de leur création au moment opportun.

Bien sûr, à un certain moment il faut indiquer qui a besoin de qui, mais ce cablage peut être fait en dehors des composants, ce qui a un certain nombre d'intérêts.

3.2 Liens.

Le terme 'Dependency Injection' a, je crois, été employé la première fois par Martin Fowler, son article en anglais est une base difficilement contournable. Les exemples sont en Java et la 'Setter Injection' y est plus lourde qu'en Ruby.

Une très bonne introduction sur la 'Dependency Injection', en anglais avec des exemples en Ruby. sur le site de Jim Weirich.
Sur le même site ces transparents avec des exemples en Java et Ruby vous ferons comprendre l'interêt de ce 'pattern'.
Les autres articles du site sont également très intéressants.

Injection de dépendances, article en français avec des exemples en Java.

4 Informations légales.

Ail is copyrighted : Copyright (c) 2006 Patrick Davalan. All rights reserved.
Ail is free software, it may be used, modified and distributed under the same terms as Ruby.
See the file COPYING in the Ail distribution.

Ail is provided "as is" without any warranty, including, but not limited to, any warranty you might dream of.

5 Download, install and use.

Download the latest version in the Download page

Unzip the tarball with tar xzvf ail-version.tar.gz, it ceates the ail-version directory, then read the README file in that directory.

In order to not pollute your Ruby libraries directory , Ail as well as some other projects of my own installs itself into the subdirectory 'rubrix', before using it, you should write somewhere in your code :
require 'rubrix/ail.rb'
and if you want to visualize dependencies graphs :
require 'rubrix/ailgraph.rb'

6 Bugs and deficiencies.

Send bug reports to : almazz at wanadoo dot fr

Ail, its installation procedures and test suite are designed to work on any platform where Ruby works, but they have only been tested on a Debian GNU/Linux system.
Ail had been developped using Ruby 1.8, but it may work on previous versions of Ruby.

As intantiation of a resource may be triggered by a message sent to it, One have to know that the initialize method of the object may be called before the message is handled by the newly created resource.
This may be a problem when the dependencies graph is cyclic.
The a method of a class A object may trigger the instantiation of class B object by calling it's b method. If the initialize method of the class B object retrieves it's dependencies using 'yield(self)' and calls the a method of the A class object, this may be unexpected by the A class.
This is the simplest case, there may be indirections.
The best solution is to avoid cyclic dependencies, but if you need it, avoid to retrieve dependencies in resources initialize methods in a cycle, but if you need it be careful.

7 Remerciements.

La conception de Ail est basée sur DIM de Jim Weirich, Le matzdi_setter.rb de Yukihiro 'Matz' Matzumoto. m'a aussi aidé.

La documentation des logiciels Copland et Needle de Jamis Buck m'a aussi montré tout l'avantage que l'on pouvait tirer de l'injection de dépendances et inspirera probablement les développements ulterieurs de Ail.

Mes remerciements vont aussi à Ruby lui-même pour avoir excité mon imagination.

keywords : dependency injection, inversion of control, DI, IoC, découplage, pattern, modèle, injection de dépendances, inversion de contrôle, logiciel,ail
1 Introduction.
1.1 A quoi ça sert?
1.2 La solution Ail.
1.3 Variantes.
1.4 Alternatives.
2 API.
2.1 Module Ail.
2.2 Module AilGraph.
3 Injection de dépendances.
3.1 Introduction.
3.2 Liens.
4 Informations légales.
5 Download, install and use.
6 Bugs and deficiencies.
7 Remerciements.