/ SPRING BOOT, DSL, FUNCTIONAL, CLEAN CODE

Refining functional Spring

Last week, I wrote on how to migrate an existing Spring Boot application with a functional approach toward configuration. Since then, I had a presentation on that subject at Rockstar Night in Kiev and I had interesting feedback.

Handler class is necessary

Handler functions cannot be moved to the package.

They need to conform to the ServerRequest → Mono<ServerResponse> signature.

class PersonHandler(private val personRepository: PersonRepository) {
  fun readAll(request: ServerRequest) =
      ServerResponse.ok().body(personRepository.findAll())
  fun readOne(request: ServerRequest) =
      ServerResponse.ok().body(
           personRepository.findById(request.pathVariable("id").toLong()))
}

If dependencies are required - and this is the case, they need to be provided in a wider scope. Classes make such a scope readily available.

Wrapper class around routes is not necessary

This is how routes could be written:

class PersonRoutes(private val handler: PersonHandler) {
  fun routes() = router {
    "/person".nest {
      GET("/{id}", handler::readOne)
      GET("/", handler::readAll)
    }
  }
}

And this is how they can be configured accordingly:

beans {
  bean {
    PersonRoutes(PersonHandler(ref())).routes()
  }
}

Because routes can be directly injected with the handler dependency, the wrapping class is not necessary. The above code can be re-written as:

fun routes(handler: PersonHandler) = router {
  "/person".nest {
    GET("/{id}", handler::readOne)
    GET("/", handler::readAll)
  }
}

beans {
  bean {
    routes(PersonHandler(ref()))
  }
}

Keeping the class is just an old reflex from a pre-functional world.

Organizing routes

The demo project only configures 2 routes, but in a real-world project, they are bound to be many more. This is going to become unmanageable at some point.

In the annotation world, paths are organized into controller classes. What could be the organization pattern for class-free route functions?

Routes can be easily composed with the andOther() function. It’s defined as such:

org/springframework/web/reactive/function/server/RouterFunction.java
interface RouterFunction {

  default RouterFunction<?> andOther(RouterFunction<?> other) {
    return new RouterFunctions.DifferentComposedRouterFunction(this, other);
  }

  ...
}

Let’s define different route functions:

fun routeId(handler: PersonHandler) = router {
    GET("/person/{id}", handler::readOne)
}

fun routeAll(handler: PersonHandler) = router {
    GET("/person", handler::readAll)
}

Composing them is now quite straightforward:

bean {
  val handler = PersonHandler(ref())
  routeId(handler).andOther(routeAll(handler))
}

Or with stdlib:

bean {
  with(PersonHandler(ref())) {
    routeId(this).andOther(routeAll(this))
  }
}
The complete source code for this post can be found on Github.
Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a technologist focusing on cloud-native technologies, DevOps, CI/CD pipelines, and system observability. His focus revolves around creating technical content, delivering talks, and engaging with developer communities to promote the adoption of modern software practices. With a strong background in software, he has worked extensively with the JVM, applying his expertise across various industries. In addition to his technical work, he is the author of several books and regularly shares insights through his blog and open-source contributions.

Read More
Refining functional Spring
Share this