Scala 有没有一种方法可以;涓流;从顶级应用程序隐式到其他导入模块?
我试图为一个使用ActorSystem作为Http调用主干的程序重构一些代码 我的具体目标是使代码更加模块化,这样我就可以编写函数库,使用ActorSystem进行http调用,ActorSystem稍后将由应用程序提供 这是一个普遍的问题,虽然我倾向于遇到这个问题的合理数量 我有两个目标:Scala 有没有一种方法可以;涓流;从顶级应用程序隐式到其他导入模块?,scala,akka,implicit,Scala,Akka,Implicit,我试图为一个使用ActorSystem作为Http调用主干的程序重构一些代码 我的具体目标是使代码更加模块化,这样我就可以编写函数库,使用ActorSystem进行http调用,ActorSystem稍后将由应用程序提供 这是一个普遍的问题,虽然我倾向于遇到这个问题的合理数量 我有两个目标: 尽量减少我创建的ActorSystem的数量,以简化对它们的跟踪(目标是每个顶级应用程序创建一个ActorSystem) 避免在需要的地方显式传递ActorSystem和上下文 从概念上讲,下面的代码说明了
导入akka.actor.ActorSystem
导入中间模块_
导入scala.concurrent.ExecutionContextExecutor
对象MyApp扩展应用程序{
//创建actorsystem并放入范围
隐式val system=ActorSystem()
隐式val context=system.dispatcher
中级1(300)
}
//在中间模块的其他位置
对象中间模块{
导入expectsActorSystemModule_
def中间层UNC1(x:Int)={
//依赖于ActorSystem和执行上下文,
//但不会编译,因为应用程序ActorSystem和
//ec不在范围之内
使用存储系统(x)
}
}
//在此模块中,usesActorSystem需要一个Actor系统
对象expectsActorSystemModule{
def使用存储系统(x:Int)
(隐式系统:ActorSystem,上下文:ExecutionContextExecutor)=???
//…使用ActorSystem发送http请求等功能
}
是否有一种方法可以通过子模块“滴入”隐式操作来实现顶级应用程序提供所需隐式操作的目标
这是否可以做到模块导入的“深度”无关紧要(例如,如果我在顶级应用程序和需要ActorSystem的模块之间添加了几个中间库)?这里的答案是依赖注入。每个依赖于其他对象的对象都应该将它们作为构造函数参数接收。这里重要的是,更高的层只获得它们自己的依赖项,而不是它们的依赖项的依赖项 在您的示例中,
IntermediateModule
不使用ActorSystem
本身;它只需要将其传递到ExpectsActorSystemModule
。这是不好的,因为如果后者发生变化并需要另一个依赖项,那么您也需要改变前者——这是太多的耦合。您可以这样重构它:
import akka.actor.ActorSystem
import scala.concurrent.ExecutionContextExecutor
object MyApp extends App {
// Create the actorsystem and place into scope
// wire everything together
implicit val system = ActorSystem()
implicit val context = system.dispatcher
val expectsActorSystemModule = new ExpectsActorSystemModule
val intermediateModule = new IntermediateModule(expectsActorSystemModule)
// run stuff
intermediateModule.intermediateFunc1(300)
}
// Elsewhere in the intermediate module
class IntermediateModule(expectsActorSystemModule: ExpectsActorSystemModule) {
def intermediateFunc1(x: Int) = {
// Note: no ActorSystem or ExecutionContext is needed, because they were
// injected into expectsActorSystemModule
expectsActorSystemModule.usesActorSystem(x)
}
}
// In this module, usesActorSystem needs an ActorSystem
class ExpectsActorSystemModule(
implicit system: ActorSystem,
context: ExecutionContextExecutor) {
def usesActorSystem(x: Int) = ???
//... does some stuff like sending http requests with ActorSystem
}
请注意,IntermediateModule
不再需要ActorSystem
或ExecutionContext
,因为它们直接提供给ExpectsActorSystemModule
有点恼人的是,在某个时刻,您必须在应用程序中实例化所有这些对象,并将它们连接在一起。在上面的例子中,MyApp
中只有4行,但对于更多实质性的程序,它会明显变长
有像MacWire或Guice这样的库可以帮助实现这一点,但我建议不要使用它们。它们使事情变得不那么透明,而且它们也不会保存那么多代码——在我看来,这是一个糟糕的权衡。而这两种方法有更多的缺点。Guice来自Java世界,基本上不提供编译时保证,这意味着您的代码可能会编译得很好,然后因为Guice而无法启动。MacWire在这方面更好(一切都是在编译时完成的),但它不是未来的证明,因为它是作为Scala 2宏实现的——它不会在Scala 3上以当前的形式工作
在纯函数式编程社区中流行的另一种方法是使用ZIO的
ZLayer
。但是,由于您使用的是基于Lightbend技术堆栈的现有代码库,因此在这种特殊情况下,这不太可能是选择的方法。我建议只将隐式作为函数参数向下传递层次结构。这种方法在我在Anks@YikSanChan上工作的生产系统中效果很好。我尝试采用这种方法,并且我已经让它起作用了,尽管我遇到了这样一种情况:如果我将隐式值作为隐式值放入,例如,implicit val myImplicit=…
我会得到一个不明确隐式的编译错误。如果我在最上面的作用域中直接使用val myImplicit
,那么一切都会起作用-我需要运行一些案例,看看是什么导致了歧义,请按照可以导入的隐式读取。一旦你发现了额外的隐式,你就可以删除它。此外,如果使用intellij,您可以看到隐式,这有助于理解流: