首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何为http4s编写惯用scala代码

如何为http4s编写惯用scala代码
EN

Stack Overflow用户
提问于 2022-09-10 18:23:29
回答 1查看 49关注 0票数 0

我有点纠结于如何将命令式风格转换为功能性风格。

在命令式web请求中,我习惯于这样说,比如下面的psudo代码:

代码语言:javascript
复制
public Response controllerAction(Request request) {
    val (req, parserErrors) = parser.parseRequest(request);
    if (parserErrors.any()) {
        return FourHundredError(parserErrors);
    }
    
    val businessErrors = model.validate(req);
    if (businessErrors.any()){
        return FourOhFour(businessErrors);
    }

    val (response, errorsWithOurStuff) = model.doBusinessLogicStuff(req);
    if (errorsWithOurStuff.any()) {
        return FiveHundredError(errorsWithOurStuff);
    }

    return OK(response)
}

我正在尝试使用http4s将其转换为一种功能风格。

代码语言:javascript
复制
  def businessRoutes[F[_]: Sync](BL: BusinessLogic[F]): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F]{}
    import dsl._
    HttpRoutes.of[F] {
      case req @ POST -> Root / "sms" =>
        for {
          request <- req.as[BL.BuisnessRequest]
          requestErrors <- BL.validateRequest(request)
          response <- if (requestErrors.isEmpty) {
            BL.processRequest(request) match {
              case Failure(e) => InternalServerError(e)
              case Success(response) => Ok(response)
            }
          } else {
            BadRequest(requestErrors)
          }
        } yield response
    }
  }

上面的代码看起来..。对我不好,我不知道怎样才能做得更好。我的目标是保留这里包含的所有http样式抽象,因为我不想将http4s或circe泄漏到业务层。我觉得我有一个for,一个if,然后一个match,所有的反应都是乱七八糟的。我在这里很难理解代码,我希望一些scala大师能够向我展示如何清理这些代码,并使其具有可读性。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-09-10 19:36:55

这个问题的根源在于您对数据建模的方式;主要是使用validateRequest

永远记住,解析,不要验证

此外,我将使用这样的主处理程序执行非类型化错误路由:

代码语言:javascript
复制
import cats.syntax.all._
import io.circe.{Error => CirceError}

object model {
  final case class RawRequest(...)
  final case class BuisnessRequest(...)
  final case class BuisnessResponse(...)
}

object errors {
  // Depending on how you end up using those,
  // it may be good to use scala.util.control.NoStackTrace with these.
  // They may also be case classes to hold some context.
  final case object ValidationError extends Throwable
  final case object BusinessError extends Throwable
}

trait BusinessLogic {
  def validateRequest(rawRequest: RawRequest): IO[BusinessRequets]
  def processRequest(request: BusinessRequets): IO[BuisnessResponse]
}

final class HttpLayer(bl: BusinessLogic) extends Http4sDsl[IO] {
  private final val errorHanlder: PartialFunction[Throwable, IO[Response[IO]] = {
    case circeError: CirceError =>
      BadRequest(...)

    case ValidationError =>
      NotFound(...)

    case BusinessError =>
      InternalServerError(...)
  }

  val routes: HttpRoutes[IO] = HttpRoutes[F] {
     case req @ POST -> Root / "sms" =>
       req
        .as[RawRequest] // This may fail with CirceError.
        .flatMap(bl.validateRequest) // This may fail with ValidationError.
        .flatMap(bl.processRequest) // This may fail with BusinessError.
        .redeemWith(recover = errorHandler, response => Ok(response))
  }
}

为了简单起见,我在这里使用了具体的IO,如果您愿意的话,可以使用F[_]

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73674256

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档