8

I'm looking at the scaly code example from play-mailer: https://github.com/playframework/play-mailer

It goes basically like this:

class MyComponent @Inject() (mailerClient: MailerClient) {
   ...
}

simple enough and it compiles without compliant

Then I try to "call" it however and there doesn't appear to be a way to satisfy the compiler OR get a working instance of mailerClient.

object AnObject {
  val mailer = new MyComponent
  def sendEmail = mailer.doStuff
}

[info] Compiling 1 Scala source to ...
[error] /SomeOne/SomePath/SomeFile.scala:30: not enough arguments for constructor MyComponent: (mailerClient: play.api.libs.mailer.MailerClient) MyComponent.
[error] Unspecified value parameter mailerClient.
[error]   val mailer = new MyComponent
[error]                ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed

I though I might have gotten close thanks to this:

How does @Inject in Scala work

Which indicated that the following syntax might work by removing the @Inject from the constructor and placing it on a field.

@Inject var mailerClient: MailerClient = null

However the moment we try to run anything that needs that reference we still get null.

I'm reading everything I can find on @Inject

( [warning] [rant] I'm NOT a fan of compiler magic like this for this exact reason -- voodoo magic is wonderful until it stops working then no one seems to have any idea of how to fix it. [/rant] [/warning] )

but what I really want to know is how to use it properly, safely and effectively.

Community
  • 1
  • 1
Techmag
  • 1,383
  • 13
  • 24
  • FYI - this may help with those looking just to get email working... https://github.com/playframework/play-mailer/blob/master/src/test/scala/play/api/libs/mailer/MailerPluginSpec.scala – Techmag Jul 10 '15 at 15:56

4 Answers4

12

Since you closed your issue on the original GitHub repo, I don't know if this answer is still necessary but since you don't fully understand the use of a DI framework and I find it incredibly important to learn this skill, I'll try to explain it here and list some benefits.

First off, the way you are instantiating your instance doesn't give the DI framework a chance to inject your dependencies. Since new is a language keyword, DI can't interfere and the dependencies you need for your class can't be injected. How it is done is through constructor or field injection. I'll mainly focus on constructor injection because that is "standard" in the scala world.

If you specify a constructor argument with the @Injected annotation, you are basically telling the DI framework to resolve this dependency from the container. The DI framework goes and looks for an entry of that object inside its container. If it doesn't exists, it will create it (and resolve its dependencies in the process) and if it's annotated with @Singleton also save this instance for future use. Most DI frameworks require you to specify a starting class in most cases but because you are using Play! Framework this is not necessary. When you want to use a particular module inside your controller you can do this:

import javax.inject.Inject

import play.api.mvc.Controller

class Test @Inject() (val dependency: FooClass) extends Controller {
  ...
}

In this case FooClass is the class name of the dependency you want to inject into your controller. Let's say FooClass has Play's Application as a dependency this will be injected, because Play provides a couple pre-bonded presets like Application but also ActorSystem.

This is possible because Play! Framework uses DependencyInjectedRoutes. If you were to create an Actor outside of an Controller you would need to specify that inside a module class but that is explained in this link and this link.

There is also a concept of using Traits inside your controller and then later on wiring together the traits with the implementation classes but I think that is a bit too complicated for now.

If you want some benefits and succes stories to this method of writing applications, here is a good resource: https://softwareengineering.stackexchange.com/a/19204/164366

If you want something to read on this concept:

I hope this clears things up! If you have question, please do ask!

Community
  • 1
  • 1
Martijn
  • 2,268
  • 3
  • 25
  • 51
  • 1
    I completely understand the purpose and requirement for DI --THAT was not the question -- I've read most of the Guice docs - including everything to do with the `@Inject` process (and all it's related options) and yet I still can not get access to an `@Injected` Object. I think Play has some magic it does to make Controllers have injectable constructors. Something in the docs hinted at the zero argument constructor being averrable and the `@Injected` one being calling by it (or perhaps vice versa). – Techmag Jul 29 '15 at 13:03
  • Perhaps `@Injected` objects are only available "inside" an Object -- but even if that were true then why would every attempt at using field injection result in a null pointer at runtime? I'm booting (and binding) up Guice in Global.scala and I've traced it to ensure that it is in fact loading as expected. So the problem (and my original question ) still stand. All I want to know is the missing piece to get access to `@Injected` Objects... – Techmag Jul 29 '15 at 13:05
  • 2
    This dosn't answer the question, I have the same problem. What is the point in using DI in a class constructor if I have to pass in the object when I want to use the class anyway – Mark Mar 08 '16 at 17:50
  • @Ir1sh You don't. That's the whole point. You define the instances beforehand and Guice wires them up when necessary. You still use the "I need to use the `new` keyword for everything" mind-set. Guice can't intercept that, you need to start all the way up the chain with your module class and go from there. In Play's case, you module class is already done for you so you can start defining dependencies in your Controllers (which are defined in your module class by Play automagically). – Martijn Mar 08 '16 at 21:15
  • @Ir1sh Read this (https://www.playframework.com/documentation/2.4.x/ScalaDependencyInjection#Providing-custom-bindings) if you want to understand the basic functionality of a Module. Most of the plugins (play-mailer included) already provide such a module class so it can be wired up by Guice. – Martijn Mar 08 '16 at 21:21
  • Thanks, I have read that, it dosn't explain why I cant do class MyComponent @inject(ws: WSClient) class bClass extends MyComponent – Mark Mar 08 '16 at 22:16
  • 1
    @Ir1sh Sorry for my witty comment. I'll explain. What you want can be achieved with this: `MyComponent @Inject (ws: WSClient)` and `class bClass @Inject (ws: WSClient) extends MyComponent(ws)` – Martijn Mar 12 '16 at 13:48
0

this is not a scala Issues, but a DI one. You should read some guice documentation.

In Play 2.4.x, you need to use dependency injection (https://www.playframework.com/documentation/2.4.x/ScalaDependencyInjection) to achieve your goal.

Your AnObject should be:

@Singleton class AnObject @Inject()(mailer:MyComponent){
 def sendEmail = mailer.doStuff
}
aparo
  • 84
  • 2
  • 1
    Hmmm: Guice aims to make development and debugging easier and faster, not harder and slower. In that vein, Guice steers clear of surprises and magic. ??? – Techmag Jul 10 '15 at 15:17
  • Factories and Singletons are looking really really good right now. – Techmag Jul 10 '15 at 15:22
  • 1
    Still doesn't solve the original question - we've just move it down to another class - how do I call (use) this AnObject as now the compiler wants a mailer parameter for AnObject. Show me how to use it from an object please -- which I realize is the antithesis of what Guice is trying to do -- but at least I'll be able to take it from there. – Techmag Jul 10 '15 at 15:26
0

I ran into the same problem. I want to create a class or object that has the mailing functionality and then I can call it whenever I want to send out emails from any controllers.

I think what you were asking is equivalent to how to use the mailerclient outside play framework. So far as I understand, you cannot. Even you create a class in the app/controllers folder, it is just a regular scala class that has nothing to do with the magics in play framework. @Inject works only with controllers or modules. Because if you create a standalone class, you have to inject something by yourself when instantiate it. Unless you are building a module or extending a controller. Do you noticed that the AppController used to be object and now it is a class? You don't need to instantiate that when you use it. I believe the framework did something to instantiate it with configuration (injected).

So if you want to achieve the original design goal, you either write a module, publish it, and then use it in all controllers; or use some other email libraries in scala to wrap up the email sending functionality.

yang
  • 498
  • 5
  • 22
  • As I'm painfully discovering the real answer is that an Injected class can only be Instantiated via an Injected class (that's what they mean by turtles all the way down apparently). It is not true that you need to do this in a controller - it only appears so due to the above rule. The earliest you can get something "onboard" used to be GlobalSettings which is now migrated to the `play.modules.enabled` config key. You can create your own Injectable classes but they too have to be picked up via another Injected class (that damn stack of turtles). Bit of a Whiskey Tango Foxtrot situation IMHO :) – Techmag Jul 12 '16 at 14:21
  • That's what I mean by creating a module. Even you can create a class with @inject, you still have to instantiate when you use it if it is not a controller or a module, which is helpless in this case. – yang Jul 12 '16 at 15:09
0

( I don't have enough reputation to comment, so posting as an answer)

The answer posted by @aparo should be marked as the corect/approved answer, because it does solve the problem. You said that this doesn't solve the original question because it moves the dependency down to another class, but this is only partially true, as that other class will only need to provide you with a MyComponent instead of a MailerClient. Although dependency injection needs to get used all the way down to the final controller (In the case of Play), you usually won't have to inject more one single object.

Proof of this can be seen in a question I posted, as I had the same mindset as you at the time. In my example, my controller needs only a UserSearch dependency, the DatabaseConfigurationProvider dependency is dealt with by guice, so I don't need to state it anywhere again.

Community
  • 1
  • 1
rusins
  • 309
  • 4
  • 8
  • 1
    I finally come to peace with DI (or at least Guice in Play) and the magic moment was accepting that in order to get an Injected component you have to be IN an injected component in the first place. The problem is that (I suspect) more than a few people will be OUT of an injection loop and therefor asking a question, like I did, that can never be answered satisfactorily. (All trials lead to NULL inspire of what the documentation *seems* to say.) I do plan on coming back and documenting *that* as the answer to help others stuck int he loop I was in for the longest time... – Techmag Jan 05 '17 at 13:34