Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Akka HTTP api routes structure

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 ?

like image 306
RB_ Avatar asked Jul 15 '17 10:07

RB_


Video Answer


1 Answers

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
  )
} 
like image 132
Ramón J Romero y Vigil Avatar answered Sep 20 '22 07:09

Ramón J Romero y Vigil