我对akka-http实现感兴趣,但有一件事让我觉得它是一种反模式,就是为所有路由创建DSL,解析参数,错误处理等等。文件中给出的例子极其微不足道。然而,我在市场上看到了一个真实产品的路线,它是一个巨大的10k线文件,其中包含许多级别的深层次和路线中的大量业务逻辑。真实世界系统可以处理传递错误参数的用户,没有正确的权限等等,因此简单的DSL在现实生活中快速爆炸。对我来说,最佳解决方案是将路线完成交给演员,每个演员都有相同的api,然后他们将完成路线所需的工作。这将分散逻辑并启用可维护的代码,但在几小时后我无法管理它。使用低级API,我可以传递HttpRequest并以旧方式处理它,但这使我无法使用DSL中的大多数工具。那么有没有一种方法可以将一些东西传递给一个演员,使其能够在那时继续使用DSL,处理特定于路线的东西?即我在谈论这样的事情:
age = int(input())
当然,甚至不会编译。尽管我付出了最大的努力,但我还没有找到ContextOfSomeKind的类型,或者在我进入actor之后如何重新进入DSL。这可能是不可能的。如果不是我不认为我喜欢DSL,因为它鼓励我会考虑可怕的编程方法。然后,低级API的唯一问题是获得对实体marshallers的访问,但我宁愿这样做,然后在单个源文件中创建一个大型应用程序。
答案 0 :(得分:2)
卸载路线完成
直接回答你的问题:Route
只不过是一个功能。 The definition being:
type Route = (RequestContext) => Future[RouteResult]
因此,您可以简单地编写一个能够满足您需求的功能,例如:将RequestContext
发送给Actor并获取结果:
class MySlashHandler extends Actor {
val routeHandler = (_ : RequestContext) => complete("Actor Complete")
override def receive : Receive = {
case requestContext : RequestContext => routeHandler(requestContext) pipeTo sender
}
}
val actorRef : ActorRef = actorSystem actorOf (Props[MySlashHandler])
val route : Route =
(requestContext : RequestContext) => (actorRef ? requestContext).mapTo[RouteResult]
演员不要解决您的问题
您尝试处理的问题是现实世界的复杂性以及对代码复杂性的建模。我同意这是一个问题,但Actor
不是您的解决方案。对于您寻求的设计解决方案,有几个原因可以避免Actors:
receive
方法就会被冲走。您将获得更多运行时错误,例如死信,使用演员。路线组织
如前所述:Route
是一个简单的函数,是scala的构建块。仅仅因为你看到一个草率开发者的例子,将所有逻辑保存在1个文件中并不意味着这是唯一的解决方案。
我可以在main
方法中编写所有应用程序代码,但这并不能使其成为良好的函数式编程设计。同样,我可以在一个for循环中编写所有的集合逻辑,但我通常使用filter / map / reduce。
相同的组织原则适用于路线。从最基本的角度来看,您可以根据方法类型分解Route逻辑:
//GetLogic.scala
object GetLogic {
val getRoute = get {
complete("get received")
}
}
//PutLogic.scala
object PutLogic {
val putRoute = put {
complete("put received")
}
}
另一个常见的组织原则是将您的业务逻辑与您的Route逻辑分开:
object BusinessLogic {
type UserName = String
type UserId = String
//isolated business logic
val dbLookup(userId : UserId) : UserName = ???
val businessRoute = get {
entity(as[String]) { userId => complete(dbLookup(userId)) }
}
}
然后可以将这些全部合并到您的主要方法中:
val finalRoute : Route =
GetLogic.getRoute ~ PutLogic.putRoute ~ BusinessLogic.businessRoute
路由DSL可能会产生误导,因为它有时看起来像魔术,但在它下面只是scala可以组织和隔离的普通功能...
答案 1 :(得分:1)
上周我也遇到过这样的问题。最终我最终在this blog并决定采用与那里描述相同的方式。
我创建了一个自定义指令,使我可以将请求上下文传递给Actors。
def imperativelyComplete(inner: ImperativeRequestContext => Unit): Route = { ctx: RequestContext =>
val p = Promise[RouteResult]()
inner(new ImperativeRequestContext(ctx, p))
p.future
}
现在我可以在我的Routes文件中使用它,如下所示:
val route =
put {
imperativelyComplete { ctx =>
val actor = actorSystem.actorOf(Props(classOf[RequestHandler], ctx))
actor ! HandleRequest
}
}
我的RequestHandler Actor如下所示:
class RequestHandler(ctx: ImperativeRequestContext) extends Actor {
def receive: Receive = {
case handleRequest: HandleRequest =>
someActor ! DoSomething() // will return SomethingDone to the sender
case somethingDone: SomethingDone =>
ctx.complete("Done handling request")
context.stop(self)
}
}
我希望这能让您找到更好的解决方案。我不确定这个解决方案是否应该成为可行的方法,但到目前为止它对我来说非常好。