使用 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。我有两个表:Rack
:case class RackRow(id: String, produced: Float, currentHour: Long)
和 Gpu
:case 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
,因为它会引发有关递归依赖的错误。所以,这就是为什么我的方法不对。
如何将 RackRepository
和 GpuRepository
设计成一个可以访问另一个?如果这基于最佳实践是正确的。
我想我在做题的时候就知道答案是什么了。但感谢所有阅读我的问题的人......
我在 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)
我是使用 Play 框架的新手。我正在努力设计一个一对多的模型并使用 Json 检索它。我正在使用 Play 2.6、Scala 2.11 和 Slick 3.0.0。我有两个表:Rack
:case class RackRow(id: String, produced: Float, currentHour: Long)
和 Gpu
:case 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
,因为它会引发有关递归依赖的错误。所以,这就是为什么我的方法不对。
如何将 RackRepository
和 GpuRepository
设计成一个可以访问另一个?如果这基于最佳实践是正确的。
我想我在做题的时候就知道答案是什么了。但感谢所有阅读我的问题的人......
我在 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)