2015-10-19 3 views
2

Может ли кто-нибудь указать некоторые хорошие указания о том, как структурировать маршрутизацию в спрее? Мои маршруты получили очень многословны, и даже IDEA получили очень медленно (5-10 секунд для автозаполнения) при редактировании файла, который содержит маршрутизацию ....Распыление REST маршрутизации - слишком много подробностей

 pathPrefix("customers") { 
     pathEnd { 
      get { 
      handleRejections(RejectionHandler.apply(handleMissingAuthSessionKey orElse handleValidationErrorAsUnauthorized)) { 
       withSessionKey[String]("groups") { g => 
       validate(g.contains("admin"), "Not authorized") { 
        complete { 
        m.getCustomers 
        } 
       } 
       } 
      } 
      } 
     } ~ 
     path(IntNumber) { id => 
      post { 
      handleRejections(RejectionHandler.apply(handleMissingAuthSessionKey orElse handleValidationErrorAsUnauthorized)) { 
       withSessionKey[String]("groups") { g => 
       validate(g.contains("admin"), "Not authorized") { 
        entity(as[Customer]) { c => 
        complete { 
         m.updateCustomer(c).map { 
         case 0 => StatusCodes.UnprocessableEntity 
         case 1 => StatusCodes.Accepted 
         case _ => StatusCodes.InternalServerError 
         }.handleSuccessWith { case _ => 
         siblingWorkers ! Push("customers", None) 
         } 
        } 
        } 
       } 
       } 
      } 
      } ~ 
      delete { 
      withSessionKey[String]("groups") { g => 
       validate(g.contains("admin"), "Not authorized") { 
       complete { 
        m.deleteCustomer(id).map { 
        case 0 => StatusCodes.UnprocessableEntity 
        case 1 => StatusCodes.Accepted 
        case _ => StatusCodes.InternalServerError 
        }.handleSuccessWith { case _ => 
        siblingWorkers ! Push("customers", None) 
        } 
       } 
       } 
      } 
      } 
     } ~ 
     path("new") { 
      handleRejections(RejectionHandler.apply(handleMissingAuthSessionKey orElse handleValidationErrorAsUnauthorized)) { 
      post { 
       withSessionKey[String]("groups") { g => 
       validate(g.contains("admin"), "Not authorized") { 
        entity(as[Customer]) { c => 
        complete { 
         m.insertCustomer(c).handleSuccessWith { case _ => 
         siblingWorkers ! Push("customers", None) 
         } 
        } 
        } 
       } 
       } 
      } 
      } 
     } 
     } ~ 
     pathPrefix("groups") { 
     pathEnd { 
      get { 
      handleRejections(RejectionHandler.apply(handleMissingAuthSessionKey orElse handleValidationErrorAsUnauthorized)) { 
       withSessionKey[String]("groups") { g => 
       validate(g.contains("admin"), "Not authorized") { 
        complete { 
        m.getGroups 
        } 
       } 
       } 
      } 
      } 
     } ~ 
     pathPrefix(IntNumber) { groupId => 
      pathEnd { 
      complete { 
       m.getGroupById(groupId) 
      } 
      } ~ 
      path("users") { 
      complete { 
       m.getGroupById(groupId).flatMap { groupO => 
       groupO.map { group => 
        m.getUsers(group) 
       }.getOrElse(Future.successful(Seq())) 
       } 
      } 
      } 
     } ~ 
     pathPrefix(Segment) { groupName => 
      pathEnd { 
      complete { 
       m.getGroupByName(groupName) 
      } 
      } ~ 
      path("users") { 
      complete { 
       m.getGroupByName(groupName).flatMap { groupO => 
       groupO.map { group => 
        m.getUsers(group) 
       }.getOrElse(Future.successful(Seq())) 
       } 
      } 
      } 
     } 
     } ~ 
     pathPrefix("users") { 
     path("me") { 
      handleRejections(RejectionHandler.apply(handleMissingAuthSessionKey orElse handleValidationErrorAsUnauthorized)) { 
      withSessionKey[String]("userId") { uid => 
       complete { 
       m.getUserById(uid.toInt).map(_.get) 
       } 
      } 
      } 
     } ~ 
     path("new") { 
      handleRejections(RejectionHandler.apply(handleMissingAuthSessionKey orElse handleValidationErrorAsUnauthorized orElse RejectionHandler.Default)) { 
      withSessionKey[String]("groups") { g => 
       validate(g.contains("admin"), "Not authorized") { 
       entity(as[NewUser]) { r => 
        complete { 
        m.addUser(r).handleCompletionWith{ _ => siblingWorkers ! Push("users", None)} 
        } 
       } 
       } 
      } 
      } 
     } ~ 
     pathPrefix(IntNumber) { uid => 
      pathEnd { 
      get { 
       handleRejections(RejectionHandler.apply(handleMissingAuthSessionKey orElse handleValidationErrorAsUnauthorized orElse RejectionHandler.Default)) { 
       withSessionKey[String]("groups") { g => 
        validate(g.contains("admin"), "Not authorized") { 
        complete { 
         m.getUserById(uid) 
        } 
        } 
       } 
       } 
      } ~ 
      post { 
       handleRejections(RejectionHandler.apply(handleMissingAuthSessionKey orElse handleValidationErrorAsUnauthorized orElse RejectionHandler.Default)) { 
       withSessionKey[String]("groups") { g => 
        validate(g.contains("admin"), "Not authorized") { 
        entity(as[User]) { u => 
         complete { 
         m.updateUser(u).map { 
          case 0 => StatusCodes.UnprocessableEntity 
          case 1 => StatusCodes.Accepted 
          case _ => StatusCodes.InternalServerError 
         }.handleCompletionWith { _ => siblingWorkers ! Push("users", None) } 
         } 
        } 
        } 
       } 
       } 
      } ~ 
      delete { 
       handleRejections(RejectionHandler.apply(handleMissingAuthSessionKey orElse handleValidationErrorAsUnauthorized orElse RejectionHandler.Default)) { 
       withSessionKey[String]("groups") { g => 
        validate(g.contains("admin"), "Not authorized") { 
        complete { 
         m.deleteUserById(uid).map { 
         case 0 => StatusCodes.UnprocessableEntity 
         case 1 => StatusCodes.Accepted 
         case _ => StatusCodes.InternalServerError 
         }.handleCompletionWith{ _ => siblingWorkers ! Push("groups", None)} 
        } 
        } 
       } 
       } 
      } 
      } ~ 
      pathPrefix("groups") { 
      path("new") { 
       entity(as[NewUserGroup]) { g => 
       complete { 
        m.addUserGroup(UserGroup(uid, g.id)).map { 
        case 0 => StatusCodes.UnprocessableEntity 
        case 1 => StatusCodes.Accepted 
        case _ => StatusCodes.InternalServerError 
        }.handleCompletionWith{ _ => siblingWorkers ! Push("groups", None)} 
       } 
       } 
      } ~ 
      pathEnd { 
       get { 
       complete { 
        m.getUserGroups(uid) 
       } 
       } ~ 
       post { 
       entity(as[Seq[Int]]) { groups => 
        complete { 
        m.setUserGroups(uid, groups).map { 
         case Some(x) if x < groups.length => StatusCodes.UnprocessableEntity 
         case Some(x) if x == groups.length => StatusCodes.OK 
         case _ => StatusCodes.InternalServerError 
        }.handleCompletionWith{ _ => siblingWorkers ! Push("users", None)} 
        } 
       } 
       } 
      } 
      } 
     } ~ 
     pathEnd { 
      get { 
      handleRejections(RejectionHandler.apply(handleMissingAuthSessionKey orElse handleValidationErrorAsUnauthorized)) { 
       withSessionKey[String]("groups") { g => 
       validate(g.contains("admin"), "Not authorized") { 
        complete { 
        m.getUsers 
        } 
       } 
       } 
      } 
      } 
     } 

ответ

1

Вы можете разделить свой маршрут на несколько маршрутов. В вашем случае: клиенты, группы, пользователи могут быть извлечены на соответствующие маршруты.

class CustomersRoute extends ... { 
    def route: Route = pathPrefix("customers") { 
    ... 
    } 
} 

Тогда вы объедините их:

val routes = pathPrefix("v1") { 
    customers.route ~ 
    groups.route ~ 
    users.route 
} 
+0

Как это будет работать с уже извлеченными параметрами (подразумеваемая сессия выше)? –

+0

Вы можете объявлять неявные параметры в конструкторе каждого маршрута – Nyavro

+0

Не означает ли это, что маршруты будут пересматриваться по каждому запросу? В отличие от оценки при запуске и просто побежали по каждому запросу? –

1

Вы можете разделить свои ресурсы на отдельные черты, например UserRoutes.scala, ContentRoutes.scala, AdminRoutes.scala и у них все простираться HttpService. Теперь у вас может быть новый класс, который образует состав всех ваших маршрутов и расширяет класс HttpServiceActor, а составной класс может связывать разделенные маршруты с помощью оператора ~. Например. val routes = userRoutes.routes ~ adminRoutes.routes ~ contentRoutes.routes. Это также дает вам хорошее место для инъекций зависимостей.

+0

Как это работает с уже извлеченными параметрами? (неявный сеанс в приведенном выше случае) –

1

В дополнение к тому, что уже упоминалось ранее, я бы воспользовался возможностью составления директив и создания пользовательских директив, чтобы сократить повторяющийся код и сделать структуру маршрута более читаемой.

Одна вещь, которая часто повторяется в коде это:

handleRejections(RejectionHandler.apply(handleMissingAuthSessionKey orElse handleValidationErrorAsUnauthorized orElse RejectionHandler.Default)) { 
    withSessionKey[String]("groups") { g => 
    validate(g.contains("admin"), "Not authorized") { 
    … 
    } 
    } 
} 

Вы могли бы рассмотреть факторинг, что из в пользовательской директивы. Что-то вроде этого (непроверенные):

def handleSessionKeyValidation(key: String, requiredValue: String) = { 
    val rejectionHandlers = handleRejections(RejectionHandler.apply(handleMissingAuthSessionKey orElse handleValidationErrorAsUnauthorized)) 
    val sessionKeyDir = withSessionKey[String](key) 
    (rejectionHandlers & sessionKeyDir).flatMap[HNil](value => 
     if (value.contains(requiredValue)) pass 
     else reject(AuthorizationFailedRejection) 
    ) 
    } 

Теперь начните совмещая метод и путь директивы HTTP, а также использовать пользовательские директивы, и первая часть вашего маршрута может выглядеть примерно так:

(get & path("customers") & pathEnd) { 
    handleSessionKeyValidation("groups", "admin"){ 
    complete{ 
     m.getCustomers 
    } 
    } 
} ~ 
(post & path("customers"/IntNumber)) { id => 
    handleSessionKeyValidation("groups", "admin"){ 
    entity(as[Customer]) { c => 
     complete { 
     m.updateCustomer(c).map { 
      case 0 => StatusCodes.UnprocessableEntity 
      case 1 => StatusCodes.Accepted 
      case _ => StatusCodes.InternalServerError 
     }.handleSuccessWith { case _ => 
      siblingWorkers ! Push("customers", None) 
     } 
     } 
    } 
    } 
} ~ 
(delete & path("customers"/IntNumber)) { id => 
    handleSessionKeyValidation("groups", "admin") { 
    complete { 
     m.deleteCustomer(id).map { 
     case 0 => StatusCodes.UnprocessableEntity 
     case 1 => StatusCodes.Accepted 
     case _ => StatusCodes.InternalServerError 
     }.handleSuccessWith { case _ => 
     siblingWorkers ! Push("customers", None) 
     } 
    } 
    } 
} ~ 
(post & path("customers"/"new")) { 
    handleSessionKeyValidation("groups", "admin"){ 
    entity(as[Customer]) { c => 
     complete { 
     m.insertCustomer(c).handleSuccessWith { case _ => 
      siblingWorkers ! Push("customers", None) 
     } 
     } 
    } 
    } 
} 

Даже в приведенном выше примере использование директивы handleSessionKeyValidation несколько повторяется. Мы могли бы сократить повторение, увеличив объем handleSessionKeyValidation и обернув большую часть маршрута. Что-то вроде этого.

pathPrefixTest("customers"){ 
    handleSessionKeyValidation("groups", "admin") { 
    (get & path("customers") & pathEnd) { 
     complete{ 
     m.getCustomers 
     } 
    } ~ 
    (post & path("customers"/IntNumber)) { id => 
     entity(as[Customer]) { c => 
     complete { 
      m.updateCustomer(c).map { 
      case 0 => StatusCodes.UnprocessableEntity 
      case 1 => StatusCodes.Accepted 
      case _ => StatusCodes.InternalServerError 
      }.handleSuccessWith { case _ => 
      siblingWorkers ! Push("customers", None) 
      } 
     } 
     } 
    } ~ 
    (delete & path("customers"/IntNumber)) { id => 
     complete { 
     m.deleteCustomer(id).map { 
      case 0 => StatusCodes.UnprocessableEntity 
      case 1 => StatusCodes.Accepted 
      case _ => StatusCodes.InternalServerError 
     }.handleSuccessWith { case _ => 
      siblingWorkers ! Push("customers", None) 
     } 
     } 
    } ~ 
    (post & path("customers"/"new")) { 
     entity(as[Customer]) { c => 
     complete { 
      m.insertCustomer(c).handleSuccessWith { case _ => 
      siblingWorkers ! Push("customers", None) 
      } 
     } 
     } 
    } 
    }  
} ~ 
+0

В дополнение к этому ответу функция 'runRoute' спреда получает неявный' RejectionHandler', поэтому вы можете перенести его из функции handleSessionKeyValidation' в другое место. –