Scala et injection de dépendances
En surfant un peu, j'ai trouvé l'article suivant:
http://jonasboner.com/2008/10/06/rea...ection-di.html
Celui-ci explique que Scala possède au niveau du langage tout le nécessaire pour faire de l'injection de dépendances - donc sans requérir l'utilisation d'un framework spécialisé.
Je ne vais pas répéter l'ensemble de l'article ici, mais au final, toute la configuration se fait dans un object Scala "ordinaire":
Code:
1 2 3 4 5 6 7 8
|
object ComponentRegistry extends
UserServiceComponent with
UserRepositoryComponent
{
val userRepository = new UserRepository
val userService = new UserService
} |
Et quand à l'utilisation elle se fait le plus simplement du monde, sans avoir à se préoccuper du "câblage" des composants:
Code:
1 2 3
|
val userService = ComponentRegistry.userService
val user = userService.authenticate(..) |
Un argument fort qui est cité est que l'ensemble est statiquement typé. Ce qui permet au compilateur de détecter les incohérences de type - et réduit les surprises au déploiement.
Que pensez-vous de ce genre de technique? Est-ce que l'argument de la vérification des types donne vraiment un avantage concurrentiel à Scala sur ce point? Est-ce que cela pourrait motiver votre adoption de ce langage?
- Sylvain
Contruction monadique des dépendances
Voici une technique "d'injection de dépendances" que j'utilise dans mes projets Scala. Je la trouve personnellement plus fonctionnelle.
Tout abord, définissons le trait Functor:
Code:
1 2 3 4
|
trait Functor[F[_]] {
def map[A, B](m: F[A])(f: A => B): F[B]
} |
puis la monad Free comme suit
Code:
1 2 3 4 5 6 7 8 9 10 11 12
|
trait Free[F[+_], A] {
def map[B](f: A => B)(implicit F: Functor[F]) = flatMap(a => Done(f(a)))
def flatMap[B](f: A => Free[F, B])(implicit F: Functor[F]) = this match {
case Done(a) => f(a)
case More(m) => More(F.map(m)(_ flatMap f))
}
}
case class Done[F[+_], A](v: A) extends Free[F, A]
case class More[F[+_], A](f: F[Free[F, A]]) extends Free[F, A] |
Définissons maintenant nos dépendances:
Code:
1 2 3 4
|
trait Dependency[+A]
case class GetUserRepository[A](f: UserRepository => A ) extends Dependency[A]
case class GetUserService[A](f: UserService => A ) extends Dependency[A] |
Nous pouvons maintenant définir un functor pour le trait Dependency:
Code:
1 2 3 4 5 6 7
|
new Functor[Dependency]{
def map[A, B](d: Dependency[A])(f: A => B) = d match {
case GetUserRepository(g) => GetUserRepository(x => f(g(x)))
case GetUserService(g) => GetUserService(x => f(g(x)))
}
} |
Définissons maintenant l'objet Provider qui nous permet de construire "nos dépendances"
Code:
1 2 3 4 5 6 7 8
|
object Provider {
def getUserRepository: Free[Dependency, UserRepository] =
More(GetUserRepository(rep => Done(rep)))
def getUserService: Free[Dependency, UserService] =
More(GetUserService(ser => Done(ser)))
} |
Nous pouvons maintenant construire nos dépendances à la demande monadiquement comme cela par exemple:
Code:
1 2 3 4 5 6 7
|
import Provider._
for {
repo <- getUserRepository
service <- getUserService
} yield ma_fonction(repo, service) |
avec ma_fonction la fonction qui a besoin des dépendances
Maintenant passons à l'injection à proprement parlé. Définisson le trait Environment comme suit
Code:
1 2 3 4 5
|
trait Environment {
val repository: UserRepository
val service: UserService
} |
Avec par exemple cette config de dev:
Code:
1 2 3 4 5
|
object DEV extends Environment {
lazy val repository = new UserRepositoryDeDev
lazy val service = new UserServiceDeDev
} |
Voici maintenant le fameux "injecteur"
Code:
1 2 3 4 5 6 7
|
@tailrec
def resolve[A](needed: Free[Dependency, A])(implicit env: Environment): A = needed match {
case Done(a) => a
case More(GetUserRepository(f)) => resolve(f(env.repo))
case More(GetUserService(f)) => resolve(f(env.service))
} |
En reprenant notre exemple de tout à l'heure, nous avons maintenant
Code:
1 2 3 4 5 6 7 8 9
|
import Provider._
val action = for {
repo <- getUserRepository
service <- getUserService
} yield ma_fonction(repo, service)
resolve(action) // l'injection se passe à ce niveau |
C'est assez dense, mais l'injection de dépendance est un bien grand mot qui peut se résumer à ''Passer un objet en paramètre d'une fonction". Si l'idée vous plait, je pourrais également faire un tutoriel (beaucoup) plus explicatif.
Que pensez-vous de cette technique ?
Des points à améliorer ?