我正在构建一个使用Sttp.client3作为基础的库,该库可以在同步和异步环境中实现,我正在为我的服务使用zio-http,并使用sttp-client与其他服务交互。
我有以下特点:
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
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)
}
}同步实现如下所示:
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
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。
发布于 2021-10-02 04:58:58
所以这里的主要问题是,你实际上并没有提供一个异步接口,你仍然强制ZIO同步执行,这将阻塞调用线程。您需要将返回类型“提升”到您的底层客户端正在使用的效果类型中。
而且,由于您似乎会将后端传递给类型,而不是进行callsite方差,因此您需要在效果类型上使特征发生变化:
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将如下所示
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"))
)
}
})
}然后,您的调用者将能够通过执行以下操作来调用此方法
AsyncHttpZioBackend().flatMap { backend =>
val client = new CoingeckoApiZIO(new CoingeckoApi(backend))
client.get("", QueryParams(...))
}然而,我仍然觉得这作为一个接口有点令人费解。Sttp提供了一个内置的MonadError类型,您可以通用地使用它,而不是为每个效果类型创建一个后端实现,并且您可以将请求构建部分与执行部分分开。
所以我建议这样写:
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
)
}https://stackoverflow.com/questions/69385227
复制相似问题