Scala 有没有一种方法可以;涓流;从顶级应用程序隐式到其他导入模块?

Scala 有没有一种方法可以;涓流;从顶级应用程序隐式到其他导入模块?,scala,akka,implicit,Scala,Akka,Implicit,我试图为一个使用ActorSystem作为Http调用主干的程序重构一些代码 我的具体目标是使代码更加模块化,这样我就可以编写函数库,使用ActorSystem进行http调用,ActorSystem稍后将由应用程序提供 这是一个普遍的问题,虽然我倾向于遇到这个问题的合理数量 我有两个目标: 尽量减少我创建的ActorSystem的数量,以简化对它们的跟踪(目标是每个顶级应用程序创建一个ActorSystem) 避免在需要的地方显式传递ActorSystem和上下文 从概念上讲,下面的代码说明了

我试图为一个使用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,您可以看到隐式,这有助于理解流: