使用 Play 框架 + Scala + Slick 将业务逻辑从 Controller 分离到 Model 层的最佳实践

Best practices to separate business logic from the Controller to the Model layer with Play framework + Scala + Slick

我是使用 Play 框架的新手。我正在努力设计一个一对多的模型并使用 Json 检索它。我正在使用 Play 2.6、Scala 2.11 和 Slick 3.0.0。我有两个表:Rackcase class RackRow(id: String, produced: Float, currentHour: Long)Gpucase class Gpu(id: String, rackId: String, produced: Float, installedAt: String)

我之前的一个错误是把业务逻辑放在了Controller层。所以我将业务逻辑转移到模型中。下面是我使用 Slick 的 RackRepository.scala: case class Rack(id: String, produced: Float, currentHour: String, gpuList: Seq[Gpu])

case class RackRow(id: String, produced: Float, currentHour: Long)

case class RackException(message: String) extends Exception(message)

class RackRepository @Inject()(protected val dbConfigProvider: DatabaseConfigProvider, gpuRepository: GpuRepository)
                              (implicit ec: ExecutionContext) extends HasDatabaseConfigProvider[JdbcProfile] {

  import profile.api._

  lazy val RackTable = new TableQuery(tag => new RackTable(tag))

  def getProfile: JdbcProfile = profile

  def database: JdbcBackend#DatabaseDef = db

  def create(row: List[RackRow]): Future[Option[Int]] =
    db.run(RackTable ++= row)

  def insert(row: RackRow): Future[Unit] =
    db.run(RackTable += row).map(_ => ())

  def updateProduced(rackId: String, produced: Float): Future[Unit] =
    db.run(RackTable.filter(_.id === rackId).map(r => r.produced).update(produced)).map(_ => ())

  def updateRack(rackId: String, produced: Float, currentHour: Long): Future[Unit] =
    db.run(RackTable.filter(_.id === rackId).map(r => (r.produced, r.currentHour)).update(produced, currentHour)).map(_ => ())

  def updateRackProduced(id: String): Future[Unit] = {
    gpuRepository.getByRack(id).map { seqGpuRow: Seq[GpuRow] =>
      val total: Float = seqGpuRow.map(_.produced).sum
      update(id, Some(total), Some(System.currentTimeMillis))
    }
  }

  def update(rackId: String, produced: Option[Float], currentHour: Option[Long]): Future[Unit] = {
    (produced, currentHour) match {
      case (Some(produced), Some(currentHour)) =>
        db.run(RackTable.filter(_.id === rackId).map(r => (r.produced, r.currentHour)).update(produced, currentHour)).map(_ => ())
      case (Some(produced), None) =>
        db.run(RackTable.filter(_.id === rackId).map(r => r.produced).update(produced)).map(_ => ())
      case (None, Some(currentHour)) =>
        db.run(RackTable.filter(_.id === rackId).map(r => r.currentHour).update(currentHour)).map(_ => ())
      case (None, None) => Future("update not executed.")
    }
  }

  def listAllRacksWithSetup(): Future[Setup] = {
    list().flatMap { seqRackRow: Seq[RackRow] =>
      val futureSeqRackRow: Seq[Future[Rack]] = seqRackRow.map { rackRow: RackRow =>
        gpuRepository.getByRack(rackRow.id).map { seqGpuRow: Seq[GpuRow] =>
          val seqGpu = seqGpuRow.map(gpuRepository.gpuRowToGpu) // return Seq[Gpu]
          Rack(rackRow.id, rackRow.produced, Util.toDate(rackRow.currentHour), seqGpu)
        } // return Future[Rack]
      }
      val futureSeqRack: Future[Seq[Rack]] = Future.sequence(futureSeqRackRow)
      futureSeqRack.map(racks => Setup(getProfitPerGpu, racks))
    }
  }
}

如果你看到classRackRepository的构造函数,我传递的参数是gpuRepository: GpuRepository,那就是其他模型。我想这种做法是不对的。但是我这样做是为了使用 def listAllRacksWithSetup() 中的方法 gpuRepository.getByRack()。 当我转到另一个模型 GpuRepository 时,问题就来了,我无法在构造函数中传递 rackRepository: RackRepository,因为它会引发有关递归依赖的错误。所以,这就是为什么我的方法不对。 如何将 RackRepositoryGpuRepository 设计成一个可以访问另一个?如果这基于最佳实践是正确的。

我想我在做题的时候就知道答案是什么了。但感谢所有阅读我的问题的人...... 我在 GpuRepository 中创建了一个 lazy val rackRepository = new RackRepository(dbConfigProvider)。我想知道这是否仍然是 Play 框架的一个好习惯。如果有人仍然可以回答关于 Play 框架中 MVC 的最佳实践。 谢谢

class GpuRepository @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
                             (implicit ec: ExecutionContext) extends HasDatabaseConfigProvider[JdbcProfile] {

  import profile.api._

  lazy val GpuTable = new TableQuery(tag => new GpuTable(tag))
  lazy val rackRepository = new RackRepository(dbConfigProvider)