scala + jdbc + case class + actor设计混乱

时间:2013-05-08 05:41:29

标签: scala jdbc akka actor

我正在编写一个导出器,它将从数据库中获取结果并获取每个单独的记录并将其写入逗号分隔文件。不同的查询将为其创建不同的工作程序,因为它们需要编写单独的csv文件。首先,我将任务分解为两个不同的角色。 Actor1是一个JdbcWorker,它查询数据库提供的查询参数,Actor2是一个CSVWriter,它接收表示需要附加到CSV的查询结果的case类。我的第一个问题是,即使我喜欢这两个工作者提供的关注点分离,但是将jdbc查询与CSV编写器分离是一种好的设计吗?

所以,我写了如下演员:

class DataQueryWorker(csvExporterWorker: ActorRef) extends Actor with ActorLogging{

  private implicit def ModelConverter(rs: ResultSet): QueryModel = {
    QueryModel(
      id = rs.getString(0),
      name = rs.getString(1),
      age = rs.getString(2),
      gender = rs.getString(3))
}

  private def sendModelToCsvWorker(model: QueryModel): Unit = {
    csvExporterWorker ! model
  }

  private def startExport[T](queryString: String)(resultFunc: T => Unit)(implicit ModelConverter: ResultSet => T): Unit = {
    try {
      val connection = DriverManager.getConnection(DbConfig.connectionString,
        DbConfig.user,
        DbConfig.password)
      val statement = connection.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY)
      statement.setFetchSize(Integer.MIN_VALUE)
      val rs = statement.executeQuery(queryString)
      while (rs.next()) {
        resultFunc(ModelConverter(rs))
      }
    } catch {
      case e: Exception => //What to do in case of an exception???
    }
  }

  override def receive() = {
    case startEvent => startExport(DbConfig.ModelExtractionQuery)(sendModelToCsvWorker)
  }

}

我的下一个问题是,上面编写的代码是查询数据库的正确方法,将其包装在模型中并将结果发送给CSVWorker?我不确定我是否正确地遵循了scala惯用语。另外,在这种情况下处理异常的正确方法是什么?

很高兴能得到一些指导。

由于

2 个答案:

答案 0 :(得分:2)

我认为您的方法可以通过一些小的改动来实现:

对于数据库演员,您可能希望将这些长期存在的演员集中在Router后面。让这个actor保持一个Connection作为它的状态,在启动和关闭时打开它一次,然后在由于失败而重启的情况下重新打开。我认为这可能是一种更好的方法,因为您不一定需要打开导出数据调用的连接。您只需要编写一些代码,以便在调用之前检查连接状态(并重新连接)。

一旦你使DB演员有状态并且长寿,你将无法通过构造函数传递CSVWorker。您应该通过消息将其传递给此actor,表明您想要导出。你可以通过像这样的案例类来做到这一点:

case class ExportQuery(query:String, csvWorker:ActorRef)

将您的receive更改为:

def receive = {
  case ExportQuery(query, csvWorker) =>
    ...
}

最后,删除try/catch逻辑。除非你可以根据这个失败做一些有意义的事情(比如调用一些备用代码路径),否则捕获它是没有意义的。让演员失败并重新启动(并关闭/重新打开连接)并继续前进。

答案 1 :(得分:-1)

我认为在这里使用演员可能有点矫枉过正。

当您想要安全地操作具有多个线程的可变状态时,Actors非常有用。但是,在您的情况下,您说每个查询都写入一个单独的CSV文件(因此每个CSV文件只有一个线程)。我不认为CSVWorker actor是必要的。它甚至可能是有害的,因为如果DBWorker比CSVWorker快得多,则actor的邮箱可能会增长并占用大量内存。

就个人而言,我只是直接调用CSV编写器。

关于问题分离的问题取决于您是否希望此代码在不相关的上下文中重复使用。如果您可能想要将JDBC工作者与其他作者一起使用,那么它可能是值得的(尽管有一种思想流派认为您最好等到重构之前需要出现 - 您不需要它或者YAGNI)。否则,你可能最好简化。

如果您决定直接将JDBC代码附加到CSV代码,您可能还需要取出案例类转换。同样,如果这是将在其他地方重复使用的代码,那么最好保留它。

异常处理取决于您的应用程序,但在Scala中(与Java不同),如果您不知道如何处理异常,您可能不应该做任何事情。取出try..catch块,然后让异常传播 - 有些东西会捕获它,并报告它。

Java迫使你处理异常,这在理论上是一个好主意,但在实践中经常导致错误处理代码没有任何实际用途(重新抛出,或者更糟糕的是,吞咽错误)。

哦,如果您正在编写大量将ResultSet转换为案例类的代码,反之亦然,您可能需要查看使用对象关系映射框架,如Slick或Squeryl。它们针对这个用例进行了优化。