【问题标题】:Akka HTTP api routes structureAkka HTTP api 路由结构
【发布时间】:2017-07-15 10:31:02
【问题描述】:

我正在编写基于 Akka-HTTP 的 REST API。由于我是 Akka 和 Scala 的新手,我不确定在我的项目中组织代码的最佳方式是什么。我将有大约。具有基本 CRUD 的 7 个不同实体。这意味着我将在 API 中有超过 25 条路由。我想根据逻辑关联的实体对路由进行分组。什么是实现这一目标的好方法?目前,我从 GitHub 上的一些可用项目中获得灵感,并将这些路线分组为特征。我有一个主要特征,其中包括一些通用内容,扩展指令并添加编组:

trait Resource extends Directives with JsonSupport{
   ...
}

然后我安排了其他路线,如下所示。

trait UserResource extends Resource{

  def userRoutes:Route =
    pathPrefix("authenticate") {
      pathEndOrSingleSlash {
        post {
          entity(as[LoginRequest]) { request =>
            ...
            }
          }
        }
      }
    } ~
    pathPrefix("subscribe") {
      pathEndOrSingleSlash {
        post {
          entity(as[UserSubscribeRequest]) { request =>
            ...
            }
          }
        }
      }
    }
}

有一个类定义异常处理程序,实例化一些帮助程序并将路由放在一起:

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  
      }
   }
}

终于在app入口点:

object Main extends App{

  ... usual Akka stuff ..

  val routes = new Routes ()
  val router = routes.allRoutesUnified
  Http().bindAndHandle(router, "localhost", 8080)
}

还有什么更好或更优雅的路线组织方式?

【问题讨论】:

  • 我认为这已经是一种很好的路线组织方式了,我也是这样做的。

标签: scala architecture routes akka-http


【解决方案1】:

问题中的编码组织和结构更类似于面向对象编程,而不是函数式编程。功能是否比 OO 更好不在 stackoverflow 的范围内,但大概我们选择 scala 而不是 java 是有原因的。

此外,在特定示例中,即使您要走 OO 路线,似乎也不太需要继承。继承实现的唯一一件事就是避免使用单个 import 语句。

职能组织

更实用的方法是在对象而不是类中指定更简单的路由。此外,您创建的 Route 值不需要使用 def 进行实例化,因为您可以为不同的目的反复重用相同的 Route:

import akka.http.scaladsl.server.Directives._

//an object, not a class
object UserResource {

  //Note: val not def
  val userRoutes : Route = { 
    //same as in question 
  }

}

使用对象仍然允许您将相似的路由组合在一个统一的结构下,但无需实例化类对象来访问路由。

同样,您对allRoutesUnified 的定义应该是一个以“内部逻辑”作为参数的高阶函数。这将有助于更好地组织代码并使单元测试更容易:

object Routes {
  import UserResources.userRoutes

  def allRoutesUnified(innerRoute : Directive0 = userRoutes) : Route = 
    handleErrors {
      cors() {
        pathPrefix {
          authenticateOAuth2("api", PasswordAuthenticator) { _ =>
            innerRoute
          }
        }
      }
    }
}

现在这个函数可以用于userRoutes以外的路由。

最后,统一高阶函数可以通过类似问题的方式访问,但无需使用new创建Routes对象:

object Main extends App {

  //no need to create a routes objects, i.e. no "new Routes()"

  Http().bindAndHandle(
    Routes.allRoutesUnified(), 
    "localhost", 
    8080
  )
} 

【讨论】:

  • 如何在这些“资源”对象中使用服务?
  • @RB_ 我更新了答案的最后一部分以反映服务创建,类似于您问题中的代码。
  • 谢谢,也许我的代码示例并不清楚我的意思,因为我在发布之前删除了一些代码以使事情更清洁。例如,在 UserResource 对象中,我想使用一个 AuthService 类来处理与 DB 层的通信,创建 JWT 令牌,并做一些其他的事情。也许我还想使用 EmailService,向用户发送电子邮件或其他东西。我如何在我的对象/路由中使用这些“服务层”类,因为对象没有构造函数并且我不能将 EmailService 或 AuthService 的实例传递给 UserResource ?
  • 你为什么说“我不能将 EmailService 或 AuthService 的实例传递给 UserResource”?这可以作为参数通过allRoutesUnified 传递,无论是否经过咖喱,然后如果将其转换为函数,则可以将其传递给userRoutes
  • @RB_ 欢迎您。另一项建议是,不要明确依赖电子邮件服务之类的东西。而是执行type EmailService = String => Unit 之类的操作,并将其作为我提到的函数的参数类型。如果您想测试,这可以很容易地传递println,以及使用真实电子邮件服务进行生产的 lambda 函数,例如您可以将realEmail.send(_) 作为参数传入。快乐的黑客攻击。
猜你喜欢
  • 1970-01-01
  • 2017-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-23
  • 2020-06-05
  • 2017-10-24
相关资源
最近更新 更多