web-dev-qa-db-fra.com

Pourquoi utiliser @Singleton sur l'objet Scala dans Play Framework?

J'utilise Play! Framework pour Scala depuis près d'un an maintenant. J'utilise actuellement la version 2.5.x .

Je suis conscient de l'évolution des contrôleurs dans Play et de la façon dont les développeurs ont été contraints de quitter les routes statiques object.

Je suis également au courant de l'utilisation de Guice en jeu.

Si vous téléchargez activateur et exécutez:

activator new my-test-app play-scala

Activator produira un projet de modèle pour vous. Ma question concerne spécifiquement ce fichier de ce modèle.

my-test-app/app/services/Counter.scala

package services

import Java.util.concurrent.atomic.AtomicInteger
import javax.inject._

/**
 * This trait demonstrates how to create a component that is injected
 * into a controller. The trait represents a counter that returns a
 * incremented number each time it is called.
 */
trait Counter {
  def nextCount(): Int
}

/**
 * This class is a concrete implementation of the [[Counter]] trait.
 * It is configured for Guice dependency injection in the [[Module]]
 * class.
 *
 * This class has a `Singleton` annotation because we need to make
 * sure we only use one counter per application. Without this
 * annotation we would get a new instance every time a [[Counter]] is
 * injected.
 */
@Singleton
class AtomicCounter extends Counter {  
  private val atomicCounter = new AtomicInteger()
  override def nextCount(): Int = atomicCounter.getAndIncrement()
}

Vous pouvez également voir son utilisation dans le fichier this :

my-test-app/app/controllers/CountController.scala

package controllers

import javax.inject._
import play.api._
import play.api.mvc._
import services.Counter

/**
 * This controller demonstrates how to use dependency injection to
 * bind a component into a controller class. The class creates an
 * `Action` that shows an incrementing count to users. The [[Counter]]
 * object is injected by the Guice dependency injection system.
 */
@Singleton
class CountController @Inject() (counter: Counter) extends Controller {

  /**
   * Create an action that responds with the [[Counter]]'s current
   * count. The result is plain text. This `Action` is mapped to
   * `GET /count` requests by an entry in the `routes` config file.
   */
  def count = Action { Ok(counter.nextCount().toString) }

}

Cela signifie que chaque contrôleur qui a le constructeur de @Inject() (counter: Counter) recevra la même instance de Counter.

Ma question est donc:

Pourquoi utiliser @Singleton Puis @Inject Dans un contrôleur, alors que pour cet exemple, vous pouvez simplement utiliser un objet Scala?
C'est beaucoup moins de code.

Exemple:

my-test-app/app/services/Counter.scala

package services

trait ACounter {
  def nextCount: Int
}

object Counter with ACounter {
  private val atomicCounter = new AtomicInteger()
  def nextCount(): Int = atomicCounter.getAndIncrement()
}

Utilisez-le comme ceci:

my-test-app/app/controllers/CountController.scala

package controllers

import javax.inject._
import play.api._
import play.api.mvc._

import services.{Counter, ACounter}

/**
 * This controller demonstrates how to use dependency injection to
 * bind a component into a controller class. The class creates an
 * `Action` that shows an incrementing count to users. The [[Counter]]
 * object is injected by the Guice dependency injection system.
 */
@Singleton
class CountController extends Controller {
  //depend on abstractions
  val counter: ACounter = Counter

  def count = Action { Ok(counter.nextCount().toString) }

}

Quelle est la différence? L'injection est-elle préférée et pourquoi?

28
Rhys Bradbury

L'injection est-elle le moyen préféré? Généralement oui

Quelques avantages de l'utilisation de l'injection de dépendance:

  1. Découpler le contrôleur de l'implémentation concrète de Counter.
    • Si vous deviez utiliser un object, vous devrez changer votre contrôleur pour pointer vers l'implémentation différente. EG Counter2.nextCount().toString
  2. Vous pouvez varier l'implémentation lors des tests à l'aide des liaisons personnalisées Guice
    • Disons qu'à l'intérieur de Counter vous effectuez un appel WS. Cela pourrait entraîner des difficultés de test unitaire. Si vous utilisez l'injection de dépendances avec Guice, vous pouvez remplacer la liaison entre Counter et AtomicCounter pour pointer vers une version hors ligne de Counter que vous avez écrite spécifiquement pour vos tests. Voir ici pour plus d'informations sur l'utilisation des tests Guice for Play.

Voir également les motivations que Play avait pour migrer vers DI.

Je dis généralement parce que j'ai vu l'injection de dépendances se tromper en utilisant Spring et d'autres frameworks Java. Je dirais que vous devriez utiliser votre propre jugement mais pécher par rapport à l'utilisation de DI pour Play.

19
nattyddubbs

Peut-être parce que l'objet singleton de Scala ne peut pas avoir de paramètres? Par exemple, si vous avez une classe de service sur laquelle un DAO est injecté et que vous souhaitez utiliser le service dans le contrôleur, vous devez l'injecter. Le moyen le plus simple (IMO) est DI avec Guice ... Aussi, vous pouvez avoir vos dépendances en un seul endroit (module) etc ...

7
insan-e

Je ne suis pas sûr, si je comprends votre question, mais l'injection est préférable car:

  • les différentes parties de votre application sont moins couplées
  • il est plus facile de remplacer votre dépendance par une classe différente fournissant les mêmes fonctionnalités (au cas où vous auriez besoin de le faire à l'avenir) - vous devrez changer quelques lignes de code et ne pas rechercher toutes les occurrences de votre objet
  • c'est plus simple à tester (surtout quand on a besoin de se moquer de quelque chose)

En bref: D de SOLID principes: "Dépendre des abstractions. Ne pas dépendre des concrétions".

4
michaJlS