/ 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

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
Refining functional Spring
Share this