Cloud Zone is brought to you in partnership with:

Entrepreneur. Creator of Groovy++ Alex is a DZone Zone Leader and has posted 31 posts at DZone. You can read more from them at their website. View Full User Profile

Kotlin + Guice Example

02.04.2012
| 14628 views |
  • submit to reddit

While Kotlin programming language prepares for the public early access program I want to share with you one an example how easly it can be used with existing Java codebases.

This short note does not intend to teach the reader how to use Kotlin but only to make him/her intersted to learn.


We will create small toy application using Kotlin and the charming Guice framework.  The application is nothing more than the usual "Hello, World!" but it contains an application service and logging service and each service will have two incarnations - for testing and for production use.

Let us start with an abstract LoggingService. It will be an abstract class with only one abstract method to output the passed string. Most probably the code below does not require much explaination.

abstract class LoggingService() {
    abstract fun info(message: String?)
}

Now we will define a simple implementation, which prints the given message to the standard output

class StdoutLoggingService() : LoggingService(){
    override fun info(message: String?) = println("INFO: $message")
}

override modifier tells the compiler that the method overrides some method of a superclass.

Note also string interpolation

Now we will create a little more sophisticated implementation, which we will use in production :)

class JdkLoggingService [Inject] (protected val jdkLogger: Logger) : LoggingService() {
    override fun info(message: String?)  = jdkLogger.info(message)
}

[Inject] annotation on constructor tells Guice how to create JdkLoggerService.

val on constructor parameter asks to create final property initialized by given value. var asks for non-final one and neither of them define simple constructor parameter

Now we are ready to define the abstract application service and both implementations of it for test and production use.

abstract class AppService(protected val logger: LoggingService, protected val mode: String) {
    fun run() {
        logger.info("Application started in '$mode' mode with ${logger.javaClass.getName()} logger")
        logger.info("Hello, World!")
    }
}

class RealAppService [Inject] (logger: LoggingService) : AppService(logger, "real")

class TestAppService [Inject] (logger: LoggingService) : AppService(logger, "test")

Note how elegantly Kotlin allows us to define properties and pass values to superconstructor 

Now we go to more interesting part - creating min-DSL for Guice. But let us start with how we want our application to look like.

fun main(args: Array<String>) {
fun configureInjections(test: Boolean) = injector {
+module {
if(test) {
bind<LoggingService>().toInstance (StdoutLoggingService())
bind<AppService>().to<TestAppService> ()
}
else {
bind<LoggingService>().to<JdkLoggingService>()
bind<AppService>().to<RealAppService> ()
}
}
}

configureInjections(test=false).getInstance<AppService> ().run()
}

There are few things to notice here

  1. We use local function to configure injections. It is not necessary but allow some nice code grouping.
  2. Return type of function configureInjections is inferred from it's body (call to finction injector - root of our DSL which we will define soon)
  3. We call configureInjections using named arguments syntax. It is not necessary in our case but very convinient when we have many parameters and some of them are optional.

Now it is time to define simple parts of our DSL. It will be three extension functions for some Guice classes.

fun <T> Binder.bind() = bind(javaClass<T>()).sure()

fun <T> AnnotatedBindingBuilder<in T>.to() = to(javaClass<T>()).sure()

fun <T> Injector.getInstance() = getInstance(javaClass<T>())
  1. Please read about extension functions in Kotlin documentation
  2. Note in variance used in function 'to'. Please read about generics and variance in Kotlin documentation. It is really interesting
  3. javaClass<T>() call is Kotlin standard library for Java (remember that Kotlin can be compiled for other platforms like javascript as well) is equivalent to T.class in Java. The huge difference here is that in Kotlin it applicable not only to real classes but also to generic type parameters (thanks to runtime type information).
  4. Call to method sure() is Kotlin way (soon to be replaced by special language construct) to ensure non-nullability. As Java has not notation of nullable and non-nullable types we need to take some care on integration point. Please read amazing story of null-safety in Kotlin documentation.

Finally we are ready to most complex part - definition of method injector.

fun injector(config: ModuleCollector.() -> Any?) : Injector {
    val collector = ModuleCollector()
    collector.config()
    return Guice.createInjector(collector.collected).sure()
}

It has one parameter of type ModuleCollector.()->Any? which means "extension function for ModuleCollector(we will define ModuleCollector few lines below), which does not accept any parameters and returns value of any type potentially nullable"

The implementation is very straight-forward

  1. we create ModuleCollector
  2. we use it as receiver to call given configuration function
  3. we ask Guice to create Injector for all modules configured (consult Guice documentation for details) 

This is extremely powerful method of defining DSLs in Kotlin

As we saw above we call method injector with function block expression.

The fact that parameter is extension function makes all (modulo visibility rules) methods of ModuleCollector available inside function block.

The last part of our DSL is definition of ModuleCollector.

It contains internal list of collected modules and only two methods

  • method module - uses variation of the same extension-function-as-parameter trick - we use extension function to implement method of anonimous class.
Please consult Kotlin documentation on object expressions
  • method plus - overrides unary plus operation
Please consult Kotlin documentation on operator overloading.

It is very important to note that this method is both extension method and member of class ModuleCollector. It allows very good context catching on call site. In our case above we call this method inside extention function with ModuleCollectoror receiver and apply it to Module created by call to method Module.
class ModuleCollector() {
    val collected = ArrayList<Module> ()

    fun module (config: Binder.()->Any?) : Module = object: Module {
        override fun configure(binder: Binder?) {
            binder.sure().config()
        }
    }

    fun Module.plus() {
        collected.add(this)
    }
}

We are done. I hope that was interesting. This note is a very brief and non-detailed introduction on goodies of Kotlin.  If this has motivated you to read more about Kotlin then my goal is achieved.

Thank you and till next time!

Published at DZone with permission of its author, Alex Tkachman.
Tags:

Comments

Arnaud Des_vosges replied on Sat, 2012/02/04 - 2:08pm

val collected = ArrayList<Module> ()

Maybe it's a detail, but I think we have lost something compared to traditional java code:

List<Module> collected = new ArrayList<Module>()

... 

We have lost the explicit information that the code that follows doesn't depend on ArrayList (which is just an implementation choice), but can work with any List<Module>.

(Same problem in usual Scala examples.) 

 It would like a language that can handle this kind of code:

 List<Module> collected = new _() // choose automatically a default impl for List

Alex Tkachman replied on Sat, 2012/02/04 - 2:50pm in response to: Arnaud Des_vosges

If you need explicit type you can do val collected : List = ArrayList ()

Arnaud Des_vosges replied on Sat, 2012/02/04 - 3:31pm in response to: Alex Tkachman

Ok thanks.

That's similar to what a Scala developer told me.

The problem (for me) is that it seems nobody is using this pattern. :)

So with String specification, would it look like:

val collected : List<String> = ArrayList<> () 

Is there a "diamond" too ? 

Anton Arhipov replied on Sun, 2012/02/05 - 5:17am

Missing the import statements for the source examples. With sources it would be easier to understand, for instance which Inject annotation is actually used, com.google.inject.Inject or javax.inject.Inject

Alex Tkachman replied on Mon, 2012/02/06 - 2:47am in response to: Anton Arhipov

It is com.google.inject.Inject

Ladislav Thon replied on Mon, 2012/02/06 - 8:49am

Thanks for the article. This really starts to show Kotlin's nature, which can't be seen in those tiny examples on Kotlin wiki. And sadly, I don't like it. Seriously, fun injector(config: ModuleCollector.() -> Any?) : Injector is pretty horrible function signature. Yeah, it's highly subjective, I know :-)

Ingo Kegel replied on Mon, 2012/02/06 - 10:41am in response to: Ladislav Thon

In all fairness, this article shows something that is not possible in Java at all, namely defining a statically typed DSL. This is a complex task and the corresponding code is necessarily somewhat complex, otherwise it would just be built-in magic.

The signature you compain about takes a function of type () -> Any? (no arguments to anything, even nullable) as an argument and that function is an extension function with a receiver type of ModuleCollector. Both concepts are not present in Java, and I don't see how they could be expressed much better.

 

If you compared Java code to its equivalent Kotlin code, the Kotlin version would look a lot nicer and much less verbose.

Kookee Gacho replied on Mon, 2012/06/18 - 5:30am

JetBrains is a Czech software development company with offices in Prague, Czech Republic; Saint Petersburg, Russia; Boston, USA and Munich, Germany. It is best known for its Java IDE, IntelliJ IDEA and for its Visual Studio plugin ReSharper. JetBrains was founded in 2000 as a private company, by Sergey Dmitriev, Eugene Belyaev and Valentin Kipiatkov.-Dr. Marla Ahlgrimm

Liezel Jandayan replied on Wed, 2013/01/23 - 7:02am

 What is worse is that I now need to manually construct the pull command in the command line, whereas GitHub previously offered the option to generate that for me, which I liked much more.-Donald Leon Farrow

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.