Akka HTTP api路由结构

RB_*_*RB_ 8 architecture scala routes akka-http

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

trait Resource extends Directives with JsonSupport{
   ...
}
Run Code Online (Sandbox Code Playgroud)

然后我有其他组织如下所示.

trait UserResource extends Resource{

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

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

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  
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

最后在app入口点:

object Main extends App{

  ... usual Akka stuff ..

  val routes = new Routes ()
  val router = routes.allRoutesUnified
  Http().bindAndHandle(router, "localhost", 8080)
}
Run Code Online (Sandbox Code Playgroud)

什么可以是更好或更优雅的组织路线的方式?

Ram*_*gil 6

问题中的编码组织和结构更类似于面向对象编程而不是函数式编程.功能是否优于OO超出了stackoverflow的范围,但可能我们选择了scala而不是原因.

此外,在特定示例中,即使您要使用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 
  }

}
Run Code Online (Sandbox Code Playgroud)

使用对象仍然允许您在一个统一结构下将类似的路由组合在一起,但不需要实例化一个类对象,只是为了能够访问路由.

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

object Routes {
  import UserResources.userRoutes

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

现在这个函数可以用于除了之外的路由userRoutes.

最后,可以以类似于问题的方式访问统一的高阶函数,但不需要Routes使用new以下方法创建对象:

object Main extends App {

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

  Http().bindAndHandle(
    Routes.allRoutesUnified(), 
    "localhost", 
    8080
  )
} 
Run Code Online (Sandbox Code Playgroud)

  • @RB_不客气.另一个建议是,不要明确依赖电子邮件服务等事情.而是执行类似`type EmailService = String => Unit`的操作,并将其作为我提到的函数的参数类型.这样可以很容易地传入`println`如果你想测试,并且lambda函数使用真正的电子邮件服务进行生产,例如你可以传入`realEmail.send(_)`作为参数.快乐的黑客. (2认同)