Akka HTTP api 路由结构

Akka HTTP api routes structure

我正在编写基于 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 定义异常处理程序,实例化一些帮助程序并将路由放在一起:

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

终于在应用入口点:

object Main extends App{

  ... usual Akka stuff ..

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

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

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

此外,在特定示例中,即使您走 OO 路线,似乎也没有太大的继承需求。继承实现的唯一目的是避免单个导入语句。

职能组织

一种更实用的方法是在对象内部指定更简单的路由,而不是 classes。此外,您正在创建的 Route 值不需要用 def 实例化,因为您可以为不同的目的一遍又一遍地重复使用相同的路由:

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

//an object, not a class
object UserResource {

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

}

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

同样,您对 allRoutesUnified 的定义应该是一个接受 "inner logic" 作为参数的高阶函数。这将有助于更好地组织代码并使单元测试更容易:

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