Scala 烫伤型管道API外部操作模式

Scala 烫伤型管道API外部操作模式,scala,hadoop,design-patterns,cascading,scalding,Scala,Hadoop,Design Patterns,Cascading,Scalding,我有一份由Antonios Chalkiopoulos编写的MapReduce与滚烫编程的副本。在这本书中,他讨论了滚烫代码的外部操作设计模式。你可以在他的网站上看到一个例子。我已经选择了使用。当然,这带来了新的挑战,但我更喜欢它而不是Fields API,这是我之前提到的书和网站中大量讨论的内容 我想知道人们是如何用类型安全API实现外部操作模式的。我的初步实施如下: 我创建了一个扩展com.twitter.spothing.Job的类,它将 作为我的工作班,我将“管理争论,定义 点击,并使用

我有一份由Antonios Chalkiopoulos编写的MapReduce与滚烫编程的副本。在这本书中,他讨论了滚烫代码的外部操作设计模式。你可以在他的网站上看到一个例子。我已经选择了使用。当然,这带来了新的挑战,但我更喜欢它而不是Fields API,这是我之前提到的书和网站中大量讨论的内容

我想知道人们是如何用类型安全API实现外部操作模式的。我的初步实施如下:

我创建了一个扩展com.twitter.spothing.Job的类,它将 作为我的工作班,我将“管理争论,定义 点击,并使用外部操作构建数据处理 管道'

我创建一个对象,在其中定义要在类型中使用的函数 安全管道。因为类型安全管道将参数作为函数, 然后我可以将对象中的函数作为参数传递给 管道

这将创建如下所示的代码:

class MyJob(args: Args) extends Job(args) {

  import MyOperations._

  val input_path = args(MyJob.inputArgPath)
  val output_path = args(MyJob.outputArgPath)

  val eventInput: TypedPipe[(LongWritable, Text)] = this.mode match {
    case m: HadoopMode => TypedPipe.from(WritableSequenceFile[LongWritable, Text](input_path))
    case _ => TypedPipe.from(WritableSequenceFile[LongWritable, Text](input_path))
  }

  val eventOutput: FixedPathSource with TypedSink[(LongWritable, Text)] with TypedSource[(LongWritable, Text)] = this.mode match {
    case m: HadoopMode => WritableSequenceFile[LongWritable, Text](output_path)
    case _ => TypedTsv[(LongWritable, Text)](output_path)
  }

  val validatedEvents: TypedPipe[(LongWritable, Either[Text, Event])] = eventInput.map(convertTextToEither).fork
  validatedEvents.filter(isEvent).map(removeEitherWrapper).write(eventOutput)
}

object MyOperations {

  def convertTextToEither(v: (LongWritable, Text)): (LongWritable, Either[Text, Event]) = {
    ...
  }

  def isEvent(v: (LongWritable, Either[Text, Event])): Boolean = {
    ...
  }

  def removeEitherWrapper(v: (LongWritable, Either[Text, Event])): (LongWritable, Text) = {
    ...
  }
}
如您所见,传递给烫伤类型安全操作的函数与作业本身是分开的。虽然这并不像外部操作模式那样“干净”,但这是编写此类代码的一种快速方法。此外,我可以使用JUnitRunner进行作业级集成测试,使用ScalateTest进行功能级单元测试

这篇文章的重点是问人们是如何做这类事情的?互联网上关于滚烫类型安全API的文档很少。有没有更适合Scala功能的方法?我是否缺少设计模式的一个关键组件?我对此有点紧张,因为通过Fields API,您可以使用BushingTest在管道上编写单元测试。据我所知,你不能用TypedPipes做那件事。请让我知道,是否有一个普遍同意的模式,为滚烫的类型安全API,或者您如何创建可重用的,模块化的,可测试的类型安全API代码。谢谢你的帮助

安东尼奥斯回复后更新2 谢谢你的回复。这基本上就是我想要的答案。我想继续谈话。正如我所评论的,我在您的回答中看到的主要问题是,此实现需要一个特定类型的实现,但是如果在整个工作过程中类型发生了变化,该怎么办?我已经探索了这段代码,它似乎可以工作,但它似乎被黑客入侵了

def self: TypedPipe[Any]

def testingPipe: TypedPipe[(LongWritable, Text)] = self.map(
    (firstVar: Any) => {
        val tester = firstVar.asInstanceOf[(LongWritable, Text)]
        (tester._1, tester._2)
    }
)

这样做的好处是我声明了self的一个实现,但缺点是这种丑陋的类型转换。此外,我还没有用更复杂的管道对此进行深入测试。因此,基本上,您对如何在类型更改时仅使用一个自我实现来处理类型以保持整洁/简洁有何想法?

我不确定您看到的代码片段存在什么问题,以及为什么您认为它“不够整洁”。我觉得很好

至于使用类型化API的单元测试作业,请看一看,它似乎正是您所寻找的

Scala是使用隐式类实现的。 您向编译器添加了将TypedPipe转换为包含外部操作的(包装器)类的功能:

import com.twitter.scalding.TypedPipe
import com.twitter.scalding._
import cascading.flow.FlowDef

class MyJob(args: Args) extends Job(args) {

  implicit class MyOperationsWrapper(val self: TypedPipe[Double]) extends MyOperations with Serializable

  val pipe = TypedPipe.from(TypedTsv[Double](args("input")))

  val result = pipe
    .operation1
    .operation2(x => x*2)
    .write(TypedTsv[Double](args("output")))

}

trait MyOperations {

  def self: TypedPipe[Double]

  def operation1(implicit fd: FlowDef): TypedPipe[Double] =
    self.map { x =>
      println(s"Input: $x")
      x / 100
    }

  def operation2(datafn:Double => Double)(implicit fd: FlowDef): TypedPipe[Double] =
    self.map { x=>
      val result = datafn(x)
      println(s"Result: $result")
      result
    }

}

import org.apache.hadoop.util.ToolRunner
import org.apache.hadoop.conf.Configuration

object MyRunner extends App {

  ToolRunner.run(new Configuration(), new Tool, (classOf[MyJob].getName :: "--local" ::
    "--input" :: "doubles.tsv" ::
    "--output":: "result.tsv" :: args.toList).toArray)

}
关于如何跨管道管理类型,我的建议是尝试找出一些有意义的基本类型和用例类。为了使用您的示例,我将方法
convertexttoeither
重命名为
extractEvents

case class LogInput(l : Long, text: Text)
case class Event(data: String)
def extractEvents( line : LogInput ): TypedPipe[Event] =
  self.filter( isEvent(line) )
      .map ( getEvent(line.text) ) 
那你会的

  • loginputo操作
    用于
    LogInput
    类型
  • 事件操作
    用于
    事件
    类型

也许我对代码质量真的很神经质。谢谢你的反馈。我将在第二天编写单元测试时研究JobTest。你好,Antonios,我很荣幸你能给我一个答案。这种实现有一个问题。如果在对数据运行操作时类型发生更改,该怎么办?在我的管道中,管道都使用元组,但是元组在转换时传递不同的数据类型。我会尝试一下你发布的内容。再次感谢!我使用了您的代码示例并接收到cascading.flow.planner.PlannerException:无法从程序集生成流:[Java和Kyro都不适用于类:class com.twitter.spothing.typed.MapFn实例:export CHILL\u EXTERNALIZER\u DEBUG=true以查看两个堆栈跟踪]我在回答中添加了一个普通的运行程序。用烫伤0.10进行测试,得到正确的结果。i、 e.对于输入:10.0,输出为0.2。我希望我能投更多的票。我一到办公室就试试这个。感谢您对案例类和特定案例类操作的建议。我也是一名Scala新手,这使得这项工作具有双重挑战性,但学习新东西很好。我会汇报事情的进展。嗨,安东尼奥斯,最后一个问题,为什么会出现隐式的FlowDef?你从哪里得到直觉来加上这个?为了让计数器与字段API管道一起工作,我们使用(隐式uuid:Option[UniqueID])。我现在也在努力让计数器工作。我以前在TypeSafeAPI中没有看到任何对这一点的引用。