首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >利用带有Sttp客户端的ZIO和zio-http创建客户端API

利用带有Sttp客户端的ZIO和zio-http创建客户端API
EN

Stack Overflow用户
提问于 2021-09-30 02:02:43
回答 1查看 76关注 0票数 0

我正在构建一个使用Sttp.client3作为基础的库,该库可以在同步和异步环境中实现,我正在为我的服务使用zio-http,并使用sttp-client与其他服务交互。

我有以下特点:

代码语言:javascript
复制
trait CoingeckoApiClient extends CoingeckoClient {

  .....
   override def ping: Either[CoingeckoApiError, PingResponse] =
    get[PingResponse](endpoint = "ping", QueryParams())

  def get[T](endpoint: String, queryParams: QueryParams)(
      using Format[T]
  ): Either[CoingeckoApiError, T]
}

和API

代码语言:javascript
复制
class CoingeckoApi[F[_], P](using val backend: SttpBackend[F, P]) {
  def get(endpoint: String, params: QueryParams): F[Response[Either[String, String]]] = {
      val apiUrl = s"${CoingeckoApi.baseUrl}/$endpoint"
      basicRequest
        .get(
          uri"$apiUrl"
            .withParams(params)
        )
        .send(backend)
  }
}

同步实现如下所示:

代码语言:javascript
复制
class CoingeckoApiBasic(api: CoingeckoApi[Identity, Any]) extends CoingeckoApiClient {
  def get[T](endpoint: String, queryParams: QueryParams)(using Format[T]): Either[CoingeckoApiError, T] =
    api.get(endpoint, queryParams).body match {
      case Left(json) =>
        Json.parse(json).validate[CoingeckoApiError] match {
          case JsSuccess(value, _) => Left(value)
          case JsError(errors) =>
            Left(CoingeckoApiError.internalApiError(Some("Unknown Api Error")))
        }

      case Right(json) =>
        Json.parse(json).validate[T] match {
          case JsSuccess(value, _) =>
            Right(value)
          case JsError(errors) =>
            Left(
              CoingeckoApiError
                .internalApiError(Some(s"Invalid Response for $endpoint"))
            )
        }
    }
}

因此,我希望提供一个异步实现与ZIO

代码语言:javascript
复制
class CoingeckoApiZIO(api: CoingeckoApi[UIO, Any]) extends CoingeckoApiClient {
  def get[T](endpoint: String, queryParams: QueryParams)(using Format[T]): Either[CoingeckoApiError, T] =
  Runtime.unsafeRun {
    api.get(endpoint, queryParams).map(r => r.body match {
      case Left(json) =>
        Json.parse(json).validate[CoingeckoApiError] match {
          case JsSuccess(value, _) => Left(value)
          case JsError(errors) =>
            Left(CoingeckoApiError.internalApiError(Some("Unknown Api Error")))
        }

      case Right(json) =>
        Json.parse(json).validate[T] match {
          case JsSuccess(value, _) =>
            Right(value)
          case JsError(errors) =>
            Left(
              CoingeckoApiError
                .internalApiError(Some(s"Invalid Response for $endpoint"))
            )
        }
    })
  }
}

这是不是意味着,我需要在这个级别提供一个运行时?在我看来,提供一个足够灵活的API来供ZIO、Future和其他公司使用是有点困难的,可能我在这里遗漏了一些重要的东西。

我可能需要更改class CoingeckoApi[F[_], P]的签名才能支持某个环境?

我正在尝试遵循可以使用多个后端的sttp的步骤,但它似乎有点难以扩展,或者我需要重写我的API。

EN

回答 1

Stack Overflow用户

发布于 2021-10-02 04:58:58

所以这里的主要问题是,你实际上并没有提供一个异步接口,你仍然强制ZIO同步执行,这将阻塞调用线程。您需要将返回类型“提升”到您的底层客户端正在使用的效果类型中。

而且,由于您似乎会将后端传递给类型,而不是进行callsite方差,因此您需要在效果类型上使特征发生变化:

代码语言:javascript
复制
trait CoingeckoApiClient[F[_]] extends CoingeckoClient[F] {

  .....
 override def ping: F[Either[CoingeckoApiError, PingResponse]] =
  get[PingResponse](endpoint = "ping", QueryParams())

 def get[T](endpoint: String, queryParams: QueryParams)(
  using Format[T]
 ): Either[CoingeckoApiError, T]
}

然后,您的ZIO API将如下所示

代码语言:javascript
复制
class CoingeckoApiZIO(api: CoingeckoApi[Task, Any]) extends CoingeckoApiClient[Task] {
  def get[T](endpoint: String, queryParams: QueryParams)(using Format[T]): Task[Either[CoingeckoApiError, T]] =
    api.get(endpoint, queryParams).map(r => r.body match {
      case Left(json) =>
        Json.parse(json).validate[CoingeckoApiError] match {
          case JsSuccess(value, _) => Left(value)
          case JsError(errors) =>
            Left(CoingeckoApiError.internalApiError(Some("Unknown Api Error")))
        }

      case Right(json) =>
        Json.parse(json).validate[T] match {
          case JsSuccess(value, _) =>
            Right(value)
          case JsError(errors) =>
            Left(
              CoingeckoApiError
                .internalApiError(Some(s"Invalid Response for $endpoint"))
            )
        }
    })
}

然后,您的调用者将能够通过执行以下操作来调用此方法

代码语言:javascript
复制
AsyncHttpZioBackend().flatMap { backend =>
  val client = new CoingeckoApiZIO(new CoingeckoApi(backend))
  client.get("", QueryParams(...))
}

然而,我仍然觉得这作为一个接口有点令人费解。Sttp提供了一个内置的MonadError类型,您可以通用地使用它,而不是为每个效果类型创建一个后端实现,并且您可以将请求构建部分与执行部分分开。

所以我建议这样写:

代码语言:javascript
复制
object CoingeckoRequests {
  final val baseUrl = "https://api.coingecko.com/api/v3"

  def get(endpoint: String, params: QueryParams): Request[Either[String, String]], Any] = {
      val apiUrl = s"${CoingeckoApi.baseUrl}/$endpoint"
      basicRequest
        .get(uri"$apiUrl?params")
  }
}

class CoingeckoApiClientImpl[F[_]](backend: SttpBackend[F, Any]) extends CoingeckoApiClient[F] {
    def get[T](endpoint: String, queryParams: QueryParams)(using Format[T]): F[Either[CoingeckoApiError, T]] =
      backend.send(
        CoingeckoRequests.get(endpoint, queryParams)
          .response(asJson[T])
         // Handle your error response here
      )
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69385227

复制
相关文章

相似问题

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