Routing DSL style guide
Apache Pekko HTTP’s routing DSL is at the center of most Apache Pekko 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]
) (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.
source// prefer
val prefer = path("item" / "listing") & get
// over
val over = get & path("item" / "listing")
sourceimport static org.apache.pekko.http.javadsl.server.PathMatchers.*;
import static org.apache.pekko.http.javadsl.server.Directives.*;
import org.apache.pekko.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.
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("")
}
})
sourceimport static org.apache.pekko.http.javadsl.server.PathMatchers.*;
import static org.apache.pekko.http.javadsl.server.Directives.*;
import org.apache.pekko.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.
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("")
}
// ...
)
})
sourceimport static org.apache.pekko.http.javadsl.server.PathMatchers.*;
import static org.apache.pekko.http.javadsl.server.Directives.*;
import org.apache.pekko.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.
sourceval 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("")
}
})
})
sourceimport java.util.function.Function;
import java.util.function.Supplier;
import org.apache.pekko.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("")))))));