9

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 ?

RB_
  • 1,195
  • 15
  • 35

1 Answers1

6

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
  )
} 
Ramón J Romero y Vigil
  • 17,373
  • 7
  • 77
  • 125
  • How do I use Services inside those "Resource" objects ? – RB_ Aug 19 '17 at 19:16
  • @RB_ I updated the last section of the answer to reflect the service creation, similar to the code in your question. – Ramón J Romero y Vigil Aug 19 '17 at 19:50
  • Thanks, may-be it's not clear from my code samples what I mean because I removed some code from there before posting it to make things cleaner. For instance inside the UserResource object I want to use an AuthService class which handles communication with the DB layer, creates JWT token, and do some other stuff. May-be I also want to user EmailService, to send email to user, or somethings. How can I use these "service layer" classes inside my objectes/routes, since objects dont have constructors and I can't pass instace of EmailService or AuthService to the UserResource ? – RB_ Aug 19 '17 at 19:58
  • Why do you say you "I can't pass instace of EmailService or AuthService to the UserResource"? This could be passed through `allRoutesUnified` as arguments, either curried or not, which could then be passed into `userRoutes` if it was converted to a function. – Ramón J Romero y Vigil Aug 19 '17 at 20:20
  • 2
    @RB_ You are welcome. One other suggestion,don't put an explicit dependency on things like email service. Instead do something like `type EmailService = String => Unit`, and make that your parameter type for the functions I mentioned. This makes it easy to pass in `println` if you want to test, and a lambda function that uses a real email service for production, e.g. you can pass in `realEmail.send(_)` as a parameter. Happy hacking. – Ramón J Romero y Vigil Aug 19 '17 at 23:37