首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

通过 Scala 和 Cats 实现模型优先的微服务-2

通过 Scala 和 Cats 实现模型优先的微服务-2

Future 倍增:Cats 和 Semigroupal 类型Cats 不是指猫咪;而是 categories                    的缩写,是对研究数学本身中结构的数学分支的称谓。类别理论关注的是在使用强类型语言时非常有用的抽象。具体而言,类别理论采用经过验证为“正确”的合法方式,为类型的遍历、转换和组合提供了蓝图配方
其中一种类型是 (先前称为“Cartesian”),允许您使用其 product 函数加入                Future。如果您在 SQL 中编写过连接两张表的代码,那么表示您已熟悉。SQL                连接需要一组元组(即您所连接的表的行),并产生一个新的集合,每个元素都是一个由所有源表中的值组成的元组。这将有效地使源表倍增。您可以将此想法传达到类型系统:每一种类型表示该类型的一组可能的值(就类别理论而言,是指具有类型作为对象的类别),因此如果您将                A 和 B 两种类型“相乘”,您会收到一个新的类型 (A, B),其值是来自 A 和 B 的值对。
将此应用于我们的示例,您现在可以说:给定 Future[Author] 和                Future[List[Publication]],将两者“相乘”以生成元组化的                Future[(Author, List[Publication])](参见图 4)。
图 4. 元组化的 future与前面章节中两个 Future 返回函数 f 和 g 链接在一起(即使 g 根本不依赖于 f                的输出)不同的是,元组类型会更好地表明您的意图:只有当所有值都已生成时,值的元组才能存在,但是允许以任何顺序发生(您可能知道这个规则的名字:交换性)。换句话说,作者还是发布内容先到达并不重要,现在可以并行运行它们:
1
2
3
4
5
def findPublications(query: String): Future[AuthorPublications] =
  for {
    authorId <- findAuthor(query)
    (author, pubs) <- getAuthor(authorId) product getPublications(authorId)
  } yield AuthorPublications(author, pubs)




将 getAuthor 和 getPublications 与                product 函数组合将同时触发两个 Future,并在两个 Future                成功后立即生成包含两个值的元组,或者在其中任一项失败时生成错误。这不仅是针对您的问题的好解决方案,而且也更清晰地表达了                getAuthor 和 getPublications 中的域逻辑不彼此依赖。
在我们查看 Cats 的其他优势之前,让我们回头看看早前提及但未完全解决的另一个问题:如何有效地处理文章搜索中出现的错误。
处理服务失败Future 随附内置的错误处理功能。它们使用 Scala 的 捕获其计算的结果,可以是成功(携带 A 的值)或失败(携带异常)。异常非常适合处理诸如 I/O                故障、运行时平台错误以及未满足的系统期望等操作问题,因此 Try 似乎是捕获 Future 内部状态的合适选择。
您可以尝试完全依赖该机制,但是当涉及到在问题域中表示失败时,应该避免异常。这是因为应用程序中的逻辑失败最好由您可以在返回类型中传递的合适值来表示,而不应将其视为异常。例如,在文章搜索引擎中未能找到作者并非一个异常的失败,但确实是一个失败。想一想:如果您浏览至指向缺失资源的                URL,您会期望浏览器失控吗?当然不期望!您会希望它返回 404 页面,这实际上只是您原来想要的页面的一个替代值。我们来看看如何在 Scala                中实现这一点。
密封的类型层次结构密封的类型层次结构是在 Scala                中编码失败值的一种简便方法。由于密封层次结构不能扩展到其词法范围以外,因此它们允许编译器检查针对其案例的模式匹配是否全面。如果您忘记处理特定故障(可能会强制您处理所有可能的情况),这可以防止您在运行时崩溃。下面演示了如何使用此功能来表示文章搜索服务中的失败:
1
2
3
sealed trait ServiceError
case object InvalidQuery extends ServiceError
case object NotFound extends ServiceError




请注意,这些都是,由于它们的类型作用于它们自己,因此您可以通过函数返回它们,而不必使用像抛出异常这样的令人讨厌的方法。您现在可能会问:我如何通过服务函数调用来返回                ServiceError 或该调用的实际结果?您自己说过:使用 Either。
Either 类型Either[L, R] 是在 Scala 中表示互斥选择两个值的规范方式:将使用 L(“左”)或                R(“右”),但不会同时使用二者。从 Scala 2.12(也使用早期版本的类路径上的 Cats)开始,Either                类型是所谓“right-biased”的右偏 ,“right”意味着您可以使用 map 和 flatMap                对其进行转变以遵循正确的路径。这使得它成为表示任何种类计算(包括服务调用)的结果的绝佳选择,其中左分支将替换为错误类型,右分支将替换为成功类型。因此,如您在                Future 中看到的那样,右偏将变为成功偏向。让我们实现 findAuthor                函数来说明可能会生成 ServiceError 而不是作者标识的事实:
1
2
3
4
5
6
def findAuthor(query: String): Future[Either[ServiceError, Long]] =
  Future.successful(
    if (query == "matthias k") 42L.asRight
    else if (query.isEmpty) InvalidQuery.asLeft
    else NotFound.asLeft
  )




当然,这不是一个非常有用的搜索实现,但它的确说明了使用返回类型来表示失败和成功案例。虽然这个实现是明确的(方法合约在签名中清晰可见),它有点冗长。以此方式嵌套效果意味着您现在具有两个级别的体系                Future 和 Either,必须分别处理。这可能会让您很难获得您寻找的值,因为您需要先映射                Future 以抽取 Either,然后映射 Either                以获取正确的值,或者在代码中:
findAuthor("matthias k") map { idOrError => idOrError map { id => ...}  }




图 5 显示了使用包含隐喻的这个效果堆叠:
图 5. 包含隐喻使用堆叠效果也将破坏 for-comprehensions 的便利性,因为无法一次遍历两个效果。 理想情况下,您需要一个将两个效果合二为一的抽象:如果                Future 成功并且 Either 包含正确的结果,那么映射结果。事实证明,Cats                就提供这样一个抽象:monad 转换程序。
将效果堆叠与 monad 转换程序合并Monad 转换程序允许您堆叠效果如“异步运行”(Future)                和“多个结果”(Either),并将其视为一体。换句话说,您可以兼得鱼和熊掌!Cats 为一些现有的效果类型(如                Option 和 Either)提供 monad 转换程序,对于                Either,称为 EitherT。让我们使用它定义定制 Result                类型,该类型将表示服务中搜索结果的输出:
1
2
3
4
5
// bind Future and SearchError together while leaving the inner result type unbound
type Result[A] = EitherT[Future, SearchError, A]

// this allows you to invoke the companion object as "Result"
val Result = EitherT




通过该方便的类型定义,您可以紧凑且可读的方式重新定义服务功能,并且不失去您通过使用 Either 获得的任何实用程序:
1
2
3
4
def findAuthor(query: String): Result[Long] =
  if (query == "matthias k") Result.rightT(42L)
  else if (query.isEmpty) Result.leftT(InvalidQuery)
  else Result.leftT(NotFound)




此处,leftT 和 rightT 是助手函数,会将给定的值提升到用于实例化                EitherT(在此例中为 Future)的效果类型中,然后针对                Either,分别转入左侧(错误)和右侧(成功)情况中。就 Result[Long]                而言,重新编写该函数可解决上面提及的嵌套映射问题,如图 6 所示。
图 6. 解决嵌套映射可以对 getAuthor 和 getPublications 执行同样的操作:
1
2
3
4
5
6
7
def getAuthor(id: Long): Result[Author] =
  Result.rightT(Author(id, "Matthias Käppler"))

def getPublications(authorId: Long): Result[List[Publication]] =
  Result.rightT(List(
    Publication(1L, authorId, "Model-First Microservices with Scala & Cats")
  ))




由于 EitherT 本身是简单堆叠 Future 和 Either 的                monad,因此构成这些功能的文章搜索程序基本保持不变:
1
2
3
4
5
6
def findPublications(query: String): Result[AuthorPublications] =
  for {
    authorId <- findAuthor(query)
    result <- getAuthor(authorId) product getPublications(authorId)
    (author, pubs) = result
  } yield AuthorPublications(author, pubs)




唯一的变化是由于 Scala 编译器中的特点,您不再能够直接生成 (author, pubs)                元组,而是必须先在临时结果中捕获乘积,然后在一个单独的步骤中执行抽取。这是一点小代价。
您现在准备运行包含各种功能的文章搜索:
1
2
3
4
5
6
7
8
9
val query = "matthias k"
val search: Result[Unit] = findPublications(query) map { authorPubs =>
  renderResponse(200, s"Found $authorPubs")
} recover {
  case InvalidQuery => renderResponse(400, s"Not a valid query: '$query'")
  case NotFound => renderResponse(404, s"No results found for '$query'")
}

Await.result(search.value, Duration.Inf)




在您早前定义的成功路径之上,您还将使用定制处理程序调用 recover,会将您可能遇到的错误映射到相应的服务响应。由于                search 不再是 Future 而是                Result,因此您必须调用其值方法以获取底层的 Future,之后才可以等待其结果。
结束语本教程首先承认微服务组合很容易出错,要高效执行,必须进行并行化处理,然后说明了可组合的 Future                如何成为以函数方式对并发服务调用进行建模的强大工具。您也看到了并行组合的有限能力,这可以通过 Cats 扩充 Future                来解决;这使您能够生成结果元组。然后,我们向您展示了如何使用密封层次结构表示域错误,以及如何使用 Either                编写可以返回错误和值的服务函数。但是,使用此不同效果的堆栈可能会非常麻烦,因此我们通过解释如何让 monad                转换程序工作来为您的代码带来清晰性、可读性和便利性来解决问题。整合后,所有一切都在说明具有强大类型系统的函数式编程样式可以如何生成富有表达力的微服务,这些微服务会将模型放在第一位,而不会影响其背后往往高级且复杂的机制。
返回列表