鉴于现实生活中极少的应用不使用外部资源,因此环境感知的资源解析称为应用程序基础结构的关键部分。squbs 通过 ResolverRegistry 提供资源解析, 并允许任何类型的资源通过名称和环境来解决。后者允许在生产、qa 和开发环境之间区分资源。
资源解析的示例是 HTTP 端点、消息传递端点和数据库。所有这些都由一个单一的注册中心处理。
依赖
解析器位于squbs-ext。添加如下依赖:
"org.squbs" %% "squbs-ext" % squbsVersion
用法
Resolver 基本用法是查找资源。需要提供一个类型, 因为注册表可以持有多种类型的资源, 如 HTTP 端点、消息端点或数据库连接。本文档中的示例中使用了类型 URI。查找调用,如下所示:
// To resolve a resource for a specific environment.
val resource: Option[URI] = ResolverRegistry(system).resolve[URI]("myservice", QA)
ResolverRegistry
ResolverRegistry是一个Akka扩展,遵循Akka扩展使用模式。它可以托管各种类型的资源解析器,因此必须提供资源类型(在注册时,将资源类型传递给注册器调用)。可以注册相同类型或多个类型的多个解析器。
注册解析器
有两种风格的API用于解析器的注册。一个是快捷 API,允许传入闭包或 lambda 作为解析器。闭包或者lambda的返回类型必须是Option[T]。另一个完整的API使用Resolver [T],T是资源类型。如下所示:
// To register a new resolver for type URI using a closure. Note the return
// type of the closure must be `Option[T]` or in this case `Option[URI]`
ResolverRegistry(system).register[URI]("MyResolver") { (svc, env) =>
(svc, env) match {
case ("myservice", QA) => Some(URI.create("http://myservice.qa.mydomain.com"))
case ("myservice", Default) => Some(URI.create("http://myservice.mydomain.com"))
case ("myservice2", QA) => Some(URI.create("http://myservice2.qa.mydomain.com"))
case ("myservice2", Default) => Some(URI.create("http://myservice2.mydomain.com"))
case _ => None
}
}
// To register a new resolver for type URI by extending the `Resolver` trait
class MyResolver extends Resolver[URI] {
def name: String = "MyResolver"
def resolve(svc: String, env: Environment = Default): Option[URI] = {
(svc, env) match {
case ("myservice", QA) => Some(URI.create("http://myservice.qa.mydomain.com"))
case ("myservice", Default) => Some(URI.create("http://myservice.mydomain.com"))
case ("myservice2", QA) => Some(URI.create("http://myservice2.qa.mydomain.com"))
case ("myservice2", Default) => Some(URI.create("http://myservice2.mydomain.com"))
case _ => None
}
}
}
// Then just register the instance
ResolverRegistry(system).register[URI](new MyResolver)
发现链
资源发现遵循后进先出模型。最近注册的解析器优先于以前注册的解析器。ResolverRegistry 沿着链一个一个地遍历,直到有一个解析器与资源所给的类型兼容或者链已搜索到尽头(这种情况下,返回None)。
类型兼容
ResolverRegistry 在解析调用时检查请求的类型。如果注册的解析器的类型是请求类型的同一类型或子类型, 则该解析器将尝试按名称解析资源。
由于JVM类型擦除,注册的类型的类型参数或者请求的类型的类型参数无法考虑。例如,一个注册类型java.util.List<String>会与解析调用的类型java.util.List<Int>相匹配,因为类型参数String或Int运行时擦除了。由于这个限制,非常不推荐含有类型参数的类型用于注册和查找。结果未定义——可能只是由于使用了错误的资源。
为了简单起见,强烈建议不要使用类型层次结构。 所有注册的类型应该是不同的类型。
资源解析
与注册类似, 解析要求类型与注册类型兼容;已注册的类型必须是解析类型的相同或子类型。
// To resolve a resource with Default
environment.
val resource: Option[URI] = ResolverRegistry(system).resolveURI
// To resolve a resource for a specific environment.
val resource: Option[URI] = ResolverRegistry(system).resolve[URI]("myservice", QA)
注销解析器
使用下面的API,通过名称注销。
ResolverRegistry(system).unregister("MyResolver")
并发注意事项
解析器注册和注销调用应当在初始化时以非并发的方式。对于并发注册没有任何保障, 因此并发注册的结果是未定义的。在并发下注册或注销,你的解析器可能是注册的,也可能不是。
但是, 解析调用是线程安全的, 可以同时访问, 而不受 ResolverRegistry 级别的限制。每个已注册的解析器都需要线程安全。
网友评论