Spring Fu is an experimental Kotlin micro-framework that makes it easy to create lightweight Spring-powered applications with functional APIs instead of annotations.
A simple Spring Fu web application rendering an HTML page and exposing a JSON HTTP endpoint looks like that:
fun main(args: Array<String>) = application {
bean<UserRepository>()
bean<UserHandler>()
webflux {
server(netty()) {
mustache()
codecs {
jackson()
}
routes(import = ::appRoutes)
}
}
}.run(await = true)
fun appRoutes() = routes {
val userHandler = ref<UserHandler>()
GET("/", userHandler::listView)
GET("/api/user", userHandler::listApi)
}
It is a thin but opinionated layer on top of a subset of Spring Framework, Spring Data, Spring Security and other Spring projects that provides an alternative configuration model to Spring Boot to Spring Boot to develop applications in Kotlin.
-
Functional bean registration instead of JavaConfig for both internals and application
-
Features are configured explicitly via expressive configuration leveraging Kotlin domain-specific languages (DSL)
-
Flexible programmatic configuration
-
Minimal reflection usage
-
No classpath scanning
-
No annotation processing
-
Modules provide configuration DSL and dependencies for other Spring projects and JVM ecosystem
-
Reactive and coroutines web client and server based on WebFlux functional API
-
Reactive and coroutines persistence via Spring Data (MongoDB supported)
-
No blocking web/persistence support is provided since coroutines provides support for imperative programming.
Upcoming features:
-
Spring packages filtering in order to have a minimal classpath and deploy just what you need
-
Self-sufficient opinionated documentation (Kotlin, constructor injection, functional configuration, WebFlux, Coroutines, etc.)
-
Single language fullstack support with frontend written in Kotlin
Note
|
Some features experimented in Spring Fu, like Coroutines support, may be later integrated in other Spring projects. |
In addition to the whole Spring and Reactor teams, credits to Thomas Girard for its spring-webflux-kotlin-dsl experiment that initially demonstrated this approach was possible and to Konrad Kaminski for his awesome spring-kotlin-coroutine project.
Please send us your feedback on the #spring
channel of Kotlin Slack. Feel free to open issues, contribute fixes or new modules via pull requests.
Spring Fu 1.0.0.M1
is currently under active development so no release is available yet, but you can clone the repository, import it into Intellij IDEA, try to play with the tests and sample applications.
To start with Spring Fu, you can read the reference documentation.
API documentation will be available shortly.
You can have a look to the sample applications:
Spring Fu functional configuration is leveraging Kotlin DSL that allows you to configure your application explicitly. Each custom block like configuration
, actuators
or webflux
is in fact a more high level beans {}
block with a custom DSL provided for easy configuration. Since this configuration is code, you can use any kind of custom programmatic bean registration without having to implement your own @Conditional
annotation.
Here is an example of a typical Spring Fu application functional configuration.
fun main(args: Array<String) = application {
configuration {
AppConfiguration(name = env["SYSTEM_ENV"] ?: "default")
}
actuators(beans = false, mapping = false)
logging {
level(INFO)
level("org.springframework", DEBUG)
logback {
consoleAppender()
rollingFileAppender(file = File("log.txt"))
}
}
profile("data") {
bean<UserRepository>()
bean<ArticleRepository>()
mongodb(uri = "mongodb://myserver.com/foo")
listener<ContextStartedEvent> {
ref<UserRepository>().init()
ref<ArticleRepository>().init()
}
}
profile("web") {
bean<HtmlHandler>()
bean<ApiHandler>()
webflux {
server(netty()) {
cors(origin = "example.com")
mustache()
codecs {
jackson()
protobuf()
}
routes(import = ::appRoutes)
security { // TODO }
}
client {
codecs {
jackson()
}
}
}
}
// Any kind of custom conditional bean definition is possible
if (env.activeProfiles.any { it.startsWith("foo") }) {
bean<Foo>()
}
}.app.run(await = true, profiles = "data, web")
data class AppConfiguration(
val name: String,
val remoteUrl: String = "http://localhost:8080"
)
fun appRoutes() = routes {
val htmlHandler = ref<HtmlHandler>()
val apiHandler = ref<ApiHandler>()
GET("/", htmlHandler::blog)
GET("/article/{id}", htmlHandler::article)
"/api".nest {
GET("/", apiHandler::list)
POST("/", apiHandler::create)
PUT("/{id}", apiHandler::update)
DELETE("/{id}", apiHandler::delete)
}
}
Functional bean definition allows to define beans in an efficient way with minimal reflection usage, no proxy and with a concise Kotlin DSL that takes advantage of reified type parameters to avoid type erasure. The beans {}
block is in fact a regular ApplicationContextInitializer
.
JavaConfig |
Functional bean definition |
@Configuration
class MyConfiguration {
@Bean
fun foo() = Foo()
@Bean
fun bar(foo: Foo) = Bar(foo)
} |
val myConfiguration = beans {
bean<Foo>()
// Implicit autowiring by constructor
bean<Bar>()
} |
Functional bean definition is explicit, does not imply any classpath scanning and supports constructor parameters autowiring.
|
Functional bean definition |
@Component
class Foo {
// ...
}
@Component
class Bar(private val f: Foo) {
// ...
} |
class Foo {
// ...
}
class Bar(private val f: Foo) {
// ...
}
beans {
bean<Foo>()
bean<Bar>()
} |
Kotlin WebFlux router provides a simple but powerful way to implement your web application. HTTP API, streaming but also viw rendering are supported.
Annotation-based controller |
Kotlin WebFlux routes |
@RestController
@RequestMapping("/api/article")
class MyController(private val r: MyRepository) {
@GetMapping("/")
fun findAll() =
r.findAll()
@GetMapping("/{id}")
fun findOne(@PathVariable id: Long) =
repository.findById(id)
}
} |
routes {
val r = ref<MyRepository>()
"/api/article".nest {
GET("/") {
r.findAll()
}
GET("/{id}") {
val id = it.pathVariable("id")
r.findById(id)
}
}
} |