I have the following multi-module project structure built using sbt:
myProject-api
myProject-core
myProject-core is organized as below:
It contains certain actors which acts as a facade to my services. For example., I have a UserActor that sits in front of a UserService. A NotificationActor that sits in front of a NotificationService and so on.
I have another trait that exposes there actors to anybody that is interested:
trait MyProjectCoreActors {
def myAppCfg = MyProjConfig.appCfg
def userActor = myAppCfg.userActor
def notifyActor = myAppCfg.notifyActor
}
object MyProjectCoreActors {
... some initialization routing that initializes the MyProjConfig
}
My UserActor is thus defined as:
class UserActor(service: UserService) extends Actor {
...
...
}
My UserService is as follows:
class UserService(dbConfig: DbConfig) {
...
...
}
I have another class called MyProjectConfig which I initialize using the application.conf file. In this file I have the connection details to the database and so on. The MyProjectConfig is initialized as below:
trait MyProjectConfig {
def actorSystem: ActorSystem
// the actors
def userActor: ActorRef
}
object MyProjectConfig {
def apply(appConfig: Config, system: ActorSystem): MyProjectConfig = {
new MyProjectConfig {
private val dbConfig = loadDBConfig(appConfig)
override val actorSystem = system
// each actor gets its own DBConfigInstance instance
override val userActor =
actorSystem.actorOf(
Props(new UserActor(UserService(dbConfig)))
)
}
}
}
I have now the Spray routing as defined below:
trait MyProjectService extends HttpService with MyProjectCoreActors {
def pingRoute = path("ping") {
get {
userActor ! "newUser"
complete("pong!")
}
}
def pongRoute = path("pong") {
get { complete("pong!?") }
}
def route = pingRoute ~ pongRoute
}
What is now missing is a way to call the MyProjectConfig.apply(....) method and pass in the Actor System and the underlying application.conf!
This was originally a Play based application, where I had a Lifecycle plug in which had access to the underlying Application from where I got the config and the actor system. How could I now get the same here with Spray?
I have a Boot class that looks like this:
object MyBootHttpService extends App {
implicit val actorSystem = ActorSystem("myproj-actor-system")
}
How could I pass this ActorSytem to MyProjectConfig.apply(....)? and from where could I get the application.conf?
I think you can do such things (DI) in your MyBootHttpService class.
For example
object MyBootHttpService extends App {
implicit val actorSystem = ActorSystem("myproj-actor-system")
private val config = ConfigFactory.load
private val myAppConfig = MyProjectConfig(config, actorSystem)
// Initialise classes that depend on config and actorsystem....
private val service = new MyProjectService with HttpServiceActor {
override implicit val actorRefFactory = actorSystem
}
// Bind our service
IO(Http) ? Bind(listener = service, interface = "0.0.0.0", port = config.getInt("port"))
}
Typesafe config library object ConfigFactory is generally used to load config files. ConfigFactory.load with no args will try and load application.conf from the classpath.
Related
I am new to scala and I am using it with Playframework to build an API.
I have multiple docker containers (API, DB). Also, there is a module where DB configuration are specified. In this configuration, I use docker container name db in setJdbcUrl.
Everything works perfectly when running things within docker containers.
However, I have a healthcheck unit test that tests a route.
This test fails because it cant recognize the URL (as expected), but I am not sure if there is a way to update the fake application in the test to use the correct URL.
The Module
class ReadWriteDB extends ScalaModule {
#Singleton
#Provides
#Named("db.sql.readwrite.quill.context")
def quillContext(): PostgresJdbcContext[SnakeCase.type] = {
val ds = new HikariDataSource(DBConnectionConfig.hikariConfig())
QuillFactory.create(ds, Duration("2m"))
}
object DBConnectionConfig {
def hikariConfig(): HikariConfig = {
val config = new HikariConfig
config.setDriverClassName("org.postgresql.Driver")
config.setJdbcUrl("jdbc:postgresql://db:5432/postgres")
// config.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres")
// with the commented config, unit test runs correctly
config.setUsername(...)
config.setPassword(...)
config
}
}
The unit test
class HealthCheckSpec extends PlaySpec with GuiceOneAppPerTest with Injecting {
"HealthCheck status" should {
"reply from the router" in {
// can I edit app to use a different URL here?
val request = FakeRequest(GET, "/status")
val home = route(app, request).get
status(home) mustBe OK
contentAsString(home) must include("API is running!")
}
}
}
Controller
#Singleton
class HealthCheck #Inject()(val controllerComponents: ControllerComponents) extends BaseController {
def check(): Action[AnyContent] = Action {
Ok("API is running!\n")
}
}
Any guidelines would be appreciated.
You have all the tools you need. I would turn the object into a class and use #provides and #singleton like you have in your quillContext, and I'd also pull out the behaviour into a trait and have a method for just is the healthcheck okay:
trait DBObjectConfig {
def hikariConfig(): HikariConfig
def isOK() :Boolean
}
#provides
class RealDBConnection extends DBObjectConfig {
def hikariConfig(): HikariConfig = {
val config = new HikariConfig
config.setDriverClassName("org.postgresql.Driver")
config.setJdbcUrl("jdbc:postgresql://db:5432/postgres")
config.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres")
config.setUsername(...)
config.setPassword(...)
config
}
// todo: make something here that's a reliable test.
def isOK() :Boolean = {
Option(hakariConfig.getConnection()).isDefined
}
}
Your healthcheck controller can use isOK() to see if the db is up or not.
Then in your test, using Guice (your test extends an injector?) you should be able to bind a fake implementation of DBObjectConfig to your app, which will cause your controller to not instantiate a real db connection in the testing environment:
// inside your tests.
class MockDB extends DBObjectConfig {
def hikariConfig(): HikariConfig = ??? // implementation not required
def isOK() :Boolean = true // fake it being okay.
}
val application = new GuiceApplicationBuilder()
.overrides(bind[DBObjectConfig].to[MockDB])
.build()
Some technique using this general approach should work.
To implement Health Check I would recommend Play-Actuator dependency.
I want to inject dependency with Generic type using Guice. Find below example in scala which replicate the issue.
ProductModel.scala
trait BaseProduct
case class Product() extends BaseProduct
CartService.scala
class CartService[A <: BaseProduct] #Inject()(productService : ProductService[A]) {
def getCartItems = productService.getProduct
}
ProductService.scala
class ProductService[A]{
def getProduct = println("ProductService")
}
Main.scala
object Main extends App {
val injector = Guice.createInjector(new ShoppingModule)
val cartService = injector.getInstance(classOf[CartService[Product]])
cartService.getCartItems
}
class ShoppingModule extends AbstractModule with ScalaModule {
override def configure(): Unit = {
bind[BaseProduct].to(scalaguice.typeLiteral[Product])
}
}
while running this Main.scala app getting below error.
service.ProductService<A> cannot be used as a key; It is not fully specified.
I have tried binding using codingwell library. But it doesn't help to identify ProductService Type.
When you create instance of cartService at that time use typeLiteral to create instance like
val cartService = injector.getInstance(Key.get(scalaguice.typeLiteral[CartService[Product]])
if you create instance like above you don't need to create module.
Create injector using default Module (i.e. useful if you have any other bindings in default Module.scala at application level)
val appBuilder = new GuiceApplicationBuilder()
val injector = Guice.createInjector(appBuilder.applicationModule())
and if you don't have any module you can skip passing module as argument and create injector without passing any module as well like
val injector = Guice.createInjector()
I have an external library (scala-redis) that requires an implicit ActorSystem when initializing the client. I would like to have my RedisClient as a Singleton inside my Play (2.6) Application because it would make sense having it as a Singleton.
class CustomAppModule(environment: Environment,
configuration: Configuration) extends AbstractModule {
def configure() = {
//val system = getProvider(classOf[ActorSystem]).get()
//val system = ActorSystem()
//bind(classOf[ActorSystem]).toInstance(system)
val redis = RedisClient(configuration.get[String]("redis.host"))(system)
bind(classOf[RedisClient]).toInstance(redis)
}
}
First system fails because of "Provider cannot be used until the Injector has been created", and second system fails because Play Framework initializes the ActorSystem itself when the application starts, and second system fails because of "binding to akka.actor.ActorSystem was already configured at play.api.inject.BuiltinModule".
So what would be the idiomatic way with Guice/DI to proceed with this kind of situation? Do I need a wrapper Singleton that has the RedisClient as a value, and where the ActorSystem can be injected?
I think the provides method will solve your problem. Write your module as
class MyModule extends AbstractModule {
def configure() = {
}
#Provides
#Singleton
def givePrecious() : MyClass = {
new MyClass()
}
}
Here my Class looks like
#Singleton
class MyClass(a: String) {
def this() = {
this("a")
println("constructor called")
}
}
Now I try to create 3 instances of this class
val injector = Guice.createInjector(new MyModule())
val precious1 = injector.getInstance(classOf[MyClass])
val precious2 = injector.getInstance(classOf[MyClass])
val precious3 = injector.getInstance(classOf[MyClass])
You will see that the string "constructor called" is printed only once.
For sake of simplicity I have make a as a string. you can try to make it an instance of ActorSystem.
I am using Guice injections and Finatra with my service.
When trying to build a small test app I am getting this error:
Could not find a suitable constructor in com.twitter.inject.Injector. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private.
at com.twitter.inject.Injector.class(Injector.scala:9)
My Module with the injectors looks like this
object ServiceModule extends TwitterModule {
#Provides
#Singleton
def provideS2SAuthServiceConfig(): S2SAuthServiceConfig = {
val servicePath = DynamicProperty.getInstance("myorg.module.auth.servicePath").getString
val serviceUrl = DynamicProperty.getInstance("myorg.module.auth.serviceUrl").getString
val httpClient: Service[Request, Response] = Http.client.withTls(serviceUrl).newService(serviceUrl)
S2SAuthServiceConfig(httpClient, servicePath)
}
#Provides
#Singleton
def provideS2SAuthClient(injector: Injector): S2SAuthClient = {
val s2sAuthClientClass = DynamicProperty.getInstance("myorg.mymodule.s2s.s2sAuthClient").getString
val s2sAuthClientInstance = injector.instance(Class.forName(s2sAuthClientClass))
s2sAuthClientInstance.asInstanceOf[S2SAuthClient]
}
}
It works well when I inject these objects in the constructor of my classes, but I get the error when trying to get an object instance like this:
def main (args: Array[String]): Unit = {
val injector = new Injector(Guice.createInjector(ServiceModule))
val authClient = injector.instance[S2SAuthClientImpl](classOf[S2SAuthClientImpl])
val token = authClient.getToken("MyClientID", "MySecret", "MyScope")
println(token)
}
Any ideas why Guice is not able to find the constructor for the Twitter Injector class?
I'm using Play Framework, Akka and Guice. The need is to simply inject some service (or another actor) into an actor which is created by a router which is injected into some controller.
I have a router config:
akka.actor.deployment {
/job-router {
router = "round-robin-pool"
nr-of-instances = 10
}
}
And a module to bind this router
class ComponentModule extends AbstractModule with AkkaGuiceSupport {
def configure() ={
bindActor[JobActor]("job-router",(p: Props) => FromConfig.props(JobActor.props))
}
}
And the actor, with another router injected
class JobActor #Inject() (#Named("b-router")bRouter: ActorRef) extends Actor {
def this() = {
this(null)
}
override def receive = {
case SayHello(name: String) => {
println(bRouter)
sender() ! "hello,"+name
}
}
}
object JobActor {
def props = Props[JobActor].withDispatcher("akka.actor.default-dispatcher")
}
But instead of injecting the b-router , the constructor with no argument is invoked, so I can't simply get a b-router into the routee of this RoundRobinPool.
I do understand that the child actor (say Actor[akka://application/user/job-router/$a#-22311049]) is not directly injected by Guice, just wonder if there is a simplified manner to do this.
Since the actual routee job-router/$a#-22311049 is a child of RoundRobinPool, is it the best practice that I implement the RoundRobinPool to inject and pass it down to the routee?
Update:
This would do the job for injecting an actor:
context.actorSelection("akka://application/user/b-router")
Hou about other service injected by Guice?
Update 2:
Temporarily solved, by injecting into another "top level" actor (dao-provider-actor):
lazy val dao=Await.result((context.actorSelection("/user/dao-provider-actor") ? GetXDao).mapTo[XDAO],10.milliseconds)