Routing DSL style guide
Akka HTTP’s routing DSL is at the center of most Akka HTTP-based servers. It’s where the incoming requests diverge into the different parts of the implemented services.
Keeping all routing in one big structure will easily become hard to grasp and maintain. This page gives a few hints for how you may want to break down the routing logic.
Main recommendations
- Most
Route
s consist of multipleRoute
s in themselves, isolate them into values or methods. - Directives combine into other directives, isolate repeated combinations into values.
- Keep the most static part of a route outermost (eg. the fixed path segments), end with the HTTP methods.
- Encapsulate patterns you want to establish into helpers.
Structure
Routes are built out of directives
Think of a route as a function describing how an incoming request maps to a reply (technically RequestContext => Future[RouteResult]
RequestContext -> CompletionStage<RouteResult>
) (see Routes). A route is expressed in directives. Directives compose into new directives (see Composing directives).
Paths
Keep the most static part of a route outermost (eg. the fixed path segments), end with the HTTP methods.
- Scala
-
source
// prefer val prefer = path("item" / "listing") & get // over val over = get & path("item" / "listing")
- Java
-
source
import static akka.http.javadsl.server.PathMatchers.*; import static akka.http.javadsl.server.Directives.*; import akka.http.javadsl.server.Route; // prefer Route prefer = path(segment("item").slash("listing"), () -> get(() -> complete("") ) ); // over Route over = get(() -> path(segment("item").slash("listing"), () -> complete("") ) );
Group routes with a pathPrefix
where possible, use path
for the last bit.
- Scala
-
source
// prefer val prefer = pathPrefix("item") { concat( path("listing") { get { complete("") } }, path("show" / Segment) { itemId => get { complete("") } } ) } // over val over: Route = concat( path("item" / "listing") { get { complete("") } }, path("item" / "show" / Segment) { itemId => get { complete("") } } )
- Java
-
source
import static akka.http.javadsl.server.PathMatchers.*; import static akka.http.javadsl.server.Directives.*; import akka.http.javadsl.server.Route; // prefer Route prefer = pathPrefix("item", () -> concat( path("listing", () -> get(() -> complete("") ) ), path(segment("show").slash(segment()), itemId -> get(() -> complete("") ) ) ) ); // over Route over = concat( path(segment("item").slash("listing"), () -> get(() -> complete("") )), path(segment("item").slash("show").slash(segment()), itemId -> get(() -> complete("") ) ) );
Create “sub-routes” independently and stitch them together with their prefixes.
- Scala
-
source
// prefer // 1. First, create partial matchers (with a relative path) val itemRoutes: Route = concat( path("listing") { get { complete("") } }, path("show" / Segment) { itemId => get { complete("") } } ) val customerRoutes: Route = concat( path(IntNumber) { customerId => complete("") } // ... ) // 2. Then compose the relative routes under their corresponding path prefix val prefer: Route = concat( pathPrefix("item")(itemRoutes), pathPrefix("customer")(customerRoutes) ) // over val over: Route = concat( pathPrefix("item") { concat( path("listing") { get { complete("") } }, path("show" / Segment) { itemId => get { complete("") } } ) }, pathPrefix("customer") { concat( path(IntNumber) { customerId => complete("") } // ... ) } )
- Java
-
source
import static akka.http.javadsl.server.PathMatchers.*; import static akka.http.javadsl.server.Directives.*; import akka.http.javadsl.server.Route; // prefer // 1. First, create partial matchers (with a relative path) Route itemRoutes = concat( path("listing", () -> get(() -> complete("") ) ), path(segment("show").slash(segment()), itemId -> get(() -> complete("") ) ) ); Route customerRoutes = concat( path(integerSegment(), customerId -> complete("") ) // ... ); // 2. Then compose the relative routes under their corresponding path prefix Route prefer = concat( pathPrefix("item", () -> itemRoutes), pathPrefix("customer", () -> customerRoutes) ); // over Route over = concat( pathPrefix("item", () -> concat( path("listing", () -> get(() -> complete("") ) ), path(segment("show").slash(segment()), itemId -> get(() -> complete("") ) ) ) ), pathPrefix("customer", () -> concat( path(integerSegment(), customerId -> complete("") ) // ... ) ) );
Directives
If you find yourself repeating certain directives in combination at lot, combine them to a new directive. Directives that extract values always produce a tuple.
- Scala
-
source
val useCustomerIdForResponse: Long => Route = (customerId) => complete(customerId.toString) val completeWithResponse: Route = complete("") // prefer val getOrPost: Directive0 = get | post val withCustomerId: Directive1[(Long)] = parameter("customerId".as[Long]) val prefer: Route = concat( pathPrefix("data") { concat( path("customer") { withCustomerId(useCustomerIdForResponse) }, path("engagement") { withCustomerId(useCustomerIdForResponse) }) }, pathPrefix("pages") { concat( path("page1") { getOrPost(completeWithResponse) }, path("page2") { getOrPost(completeWithResponse) } ) } ) // over val over: Route = concat( pathPrefix("data") { concat( (pathPrefix("customer") & parameter("customerId".as[Long])) { customerId => useCustomerIdForResponse(customerId) }, (pathPrefix("engagement") & parameter("customerId".as[Long])) { customerId => useCustomerIdForResponse(customerId) } ) }, pathPrefix("pages") { concat( path("page1") { concat( get { complete("") }, post { complete("") } ) }, path("page2") { (get | post) { complete("") } } ) } )
- Java
-
source
import java.util.function.Function; import java.util.function.Supplier; import akka.http.javadsl.unmarshalling.StringUnmarshallers; // prefer Route getOrPost(Supplier<Route> inner) { return get(inner) .orElse(post(inner)); } Route withCustomerId(Function<Long, Route> useCustomerId) { return parameter(StringUnmarshallers.LONG, "customerId", useCustomerId); } Function<Long, Route> useCustomerIdForResponse = (customerId) -> complete(customerId.toString()); Supplier<Route> completeWithResponse = () -> complete(""); Route prefer = concat( pathPrefix("data", () -> concat( path("customer", () -> withCustomerId(useCustomerIdForResponse) ), path("engagement", () -> withCustomerId(useCustomerIdForResponse) ) ) ), pathPrefix("pages", () -> concat( path("page1", () -> getOrPost(completeWithResponse) ), path("page2", () -> getOrPost(completeWithResponse) ) ) ) ); // over Route over = concat( pathPrefix("data", () -> concat( pathPrefix("customer", () -> parameter(StringUnmarshallers.LONG, "customerId", customerId -> complete(customerId.toString()) ) ), pathPrefix("engagement", () -> parameter(StringUnmarshallers.LONG, "customerId", customerId -> complete(customerId.toString()) ) ) ) ), pathPrefix("pages", () -> concat( path("page1", () -> concat( get(() -> complete("") ), post(() -> complete("") ) ) ), path("page2", () -> get(() -> complete("") ).orElse(post(() -> complete(""))) ) ) ) );