借助ZIO、zio-http与Sttp Client实现多环境兼容的Coingecko客户端API咨询
核心问题根源
你当前的设计瓶颈在于CoingeckoApiClient接口绑定死了同步返回Either类型,导致异步实现必须硬转同步,这也是你需要调用unsafeRun的核心原因。参考sttp的多后端设计思路,只要把接口泛型化,就能无痛支持多技术栈适配,且不需要重写核心逻辑。
具体改造方案
1. 重构基础接口为泛型效果类型
将接口返回值绑定到泛型参数F[_],适配不同技术栈的效果类型(同步用Identity、ZIO用Task/IO、Future用Future):
trait CoingeckoApiClient[F[_]] extends CoingeckoClient { // ping方法自动适配对应效果类型 override def ping: F[Either[CoingeckoApiError, PingResponse]] = get[PingResponse](endpoint = "ping", QueryParams()) def get[T](endpoint: String, queryParams: QueryParams)( using Format[T] ): F[Either[CoingeckoApiError, T]] }
你原有CoingeckoApi[F[_], P]的签名完全不需要修改,天生已经适配sttp的多后端特性。
2. 同步实现完全兼容原有逻辑
同步场景下F取Identity类型,原有逻辑一行不用改:
import cats.Id // 等同于scala.util.Identity class CoingeckoApiBasic(api: CoingeckoApi[Id, Any]) extends CoingeckoApiClient[Id] { 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(_) => Left(CoingeckoApiError.internalApiError(Some("Unknown Api Error"))) } case Right(json) => Json.parse(json).validate[T] match { case JsSuccess(value, _) => Right(value) case JsError(_) => Left(CoingeckoApiError.internalApiError(Some(s"Invalid Response for $endpoint"))) } } }
3. ZIO异步实现完全不需要Runtime
库层面绝对不要硬编码Runtime.unsafeRun,Runtime由上层应用侧负责初始化和运行,你只需要返回ZIO效果即可:
import zio.Task 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(_) => Left(CoingeckoApiError.internalApiError(Some("Unknown Api Error"))) } case Right(json) => Json.parse(json).validate[T] match { case JsSuccess(value, _) => Right(value) case JsError(_) => Left(CoingeckoApiError.internalApiError(Some(s"Invalid Response for $endpoint"))) } } } }
4. Future场景同理可快速扩展
import scala.concurrent.Future import scala.concurrent.ExecutionContext class CoingeckoApiFuture(api: CoingeckoApi[Future, Any])(using ExecutionContext) extends CoingeckoApiClient[Future] { def get[T](endpoint: String, queryParams: QueryParams)(using Format[T]): Future[Either[CoingeckoApiError, T]] = api.get(endpoint, queryParams).map { r => // 同样复用完全相同的解析逻辑 r.body match { // 解析代码和同步场景完全一致 } } }
补充说明
如果需要给同步场景用户保留原有直接返回Either的使用习惯,可以额外提供一个同步专用的语法扩展,不需要改动核心适配逻辑。所有技术栈的核心HTTP调用、响应解析逻辑100%复用,只需要对应实现不同效果类型的包装层即可。
内容的提问来源于stack exchange,提问作者raul782
相关产品推荐
相关产品推荐

