I'm writing Akka-HTTP based REST API. As I'm new to Akka and Scala, I'm not sure what could be the best way of organize code in my project. I will have approx. 7 different entities with basic CRUD. Which means I will have over 25 routes in the API. I would like to keep routes grouped based on the entity they are logically associated with. What could be a good way to achieve this ? Currently I took inspiration from some of the projects available on the GitHub, and grouped those routes into traits. I have a main trait which include some general stuff, extends Directive and adds marshaling:
trait Resource extends Directives with JsonSupport{
...
}
Then I have other routes organized like the one below.
trait UserResource extends Resource{
def userRoutes:Route =
pathPrefix("authenticate") {
pathEndOrSingleSlash {
post {
entity(as[LoginRequest]) { request =>
...
}
}
}
}
} ~
pathPrefix("subscribe") {
pathEndOrSingleSlash {
post {
entity(as[UserSubscribeRequest]) { request =>
...
}
}
}
}
}
}
There is a class which defines exception handlers, instantiates some helpers and puts routes together:
class Routes extends UserResource with OtherResource with SomeOtherResource{
... handlers definitions ...
... helpers ...
def allRoutesUnified: Route =
handleErrors {
cors() {
pathPrefix("api") {
authenticateOAuth2("api", PasswordAuthenticator) { _ =>
//Routes defined in other traits
otherRoutes ~ someOtherRoutes
}
} ~ userRoutes
}
}
}
Finally in the app entry point:
object Main extends App{
... usual Akka stuff ..
val routes = new Routes ()
val router = routes.allRoutesUnified
Http().bindAndHandle(router, "localhost", 8080)
}
What could be some better or more elegant ways of organizing routes ?
The coding organization and structure in the question is more akin to object oriented programming than it is to functional programming. Whether or not functional is better than OO is outside the scope of stackoverflow, but presumably we picked scala over java for a reason.
Also, in the particular example there doesn't seem to be much need for the inheritance even if you were to go the OO route. The only thing the inheritance achieves is avoiding a single import statement.
Functional Organization
A more functional approach would be to specify your simpler Routes inside of objects instead of classes. Also, the Route
values you are creating don't need to be instantiated with def
because you can reuse the same Route over and over for different purposes:
import akka.http.scaladsl.server.Directives._
//an object, not a class
object UserResource {
//Note: val not def
val userRoutes : Route = {
//same as in question
}
}
Using an object still allows you to group similar routes together under one unifying structure but without the need to instantiate a class object just to be able to access the routes.
Similarly, your definition of allRoutesUnified
should be a higher order function that takes in the "inner logic" as an argument. This will help organize your code better and make unit testing easier as well:
object Routes {
import UserResources.userRoutes
def allRoutesUnified(innerRoute : Directive0 = userRoutes) : Route =
handleErrors {
cors() {
pathPrefix {
authenticateOAuth2("api", PasswordAuthenticator) { _ =>
innerRoute
}
}
}
}
}
Now this function can be used with Routes other than userRoutes
.
Finally, the unified higher order function can be accessed in a manner similar to the question but without the need to create a Routes
object by using new
:
object Main extends App {
//no need to create a routes objects, i.e. no "new Routes()"
Http().bindAndHandle(
Routes.allRoutesUnified(),
"localhost",
8080
)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With