使用 slick 聚合根实现
Aggregate root implementation with slick
我正在尝试在 slick 中实现一个简单的聚合根。
但我真的不知道什么是最好的方法。
这是我的域对象:
case class Project(id: UUID,
name: String,
state: ProjectState,
description: String,
team: String,
tags: Set[String]
我想将 "tags" 存储在单独的 table 中,并从 "projects_table" 和 "project_tags_table"[=12= 构建 "Project" 对象]
这是我的 table 定义:
class ProjectTable(tag: Tag) extends Table[ProjectTableRecord](tag, Some("octopus_service"), "projects") {
def id: Rep[UUID] = column[UUID]("id", O.PrimaryKey)
def name: Rep[String] = column[String]("name")
def state: Rep[ProjectState] = column[ProjectState]("state")
def description: Rep[String] = column[String]("description")
def team: Rep[String] = column[String]("team")
override def * : ProvenShape[ProjectTableRecord] = (id, name, state, description, team, created, lastModified) <> (
(ProjectTableRecord.apply _).tupled, ProjectTableRecord.unapply
)
}
class ProjectTagTable(tag: Tag) extends Table[ProjectTag](tag, Some("octopus_service"), "project_tags") {
def projectID: Rep[UUID] = column[UUID]("project_id")
def name: Rep[String] = column[String]("name")
def project = foreignKey("PROJECT_FK", projectID, TableQuery[ProjectTable])(_.id, onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade)
override def * : ProvenShape[ProjectTag] = (projectID, name) <> (
ProjectTag.tupled, ProjectTag.unapply
)
}
如何通过连接这 2 个 table 生成 "Project" 个对象?
提前致谢:)
我认为对责任级别存在误解。 Slick 允许您访问关系数据库(在某种程度上与 SQL 允许您访问的方式相同)。它基本上是一个 DAO 层。
聚合根确实比这个高一个级别(这是一个领域的东西,而不是数据库级别的东西 - 尽管它们在很大程度上通常是相同的)。
所以基本上你需要有一个级别高于 Slick
的表,这样你就可以执行不同的查询和聚合结果化为一体。
不过在我们开始之前 - 您应该创建并存储您的 TableQuery
对象,也许像这样:
lazy val ProjectTable = TableQuery[ProjectTable]
lazy val ProjectTagTable = TableQuery[ProjectTagTable]
你可以把它们放在你附近的某个地方 Table definitions
。
首先,正如我提到的,您的聚合根 Project
需要被某些东西拉动。我们称它为 ProjectRepository
.
假设它将有一个方法def load(id: UUID): Future[Project]
。
此方法可能如下所示:
class ProjectRepository {
def load(id: UUID): Future[Project] = {
db.run(
for {
project <- ProjectTable.filter(_.id === id).result
tags <- ProjectTagTable.filter(_.projectId === id).result
} yield {
Project(
id = project.id,
name = project.name,
state = project.state,
description = project.description,
team = project.team,
tags = tags.map(_.name)
)
}
)
}
// another example - if you wanted to extract multiple projects
// (in reality you would probably apply some paging here)
def findAll(): Future[Seq[Project]] = {
db.run(
ProjectTable
.join(ProjectTag).on(_.id === _.projectId)
.result
.map { _.groupBy(_._1)
.map { case (project, grouped) =>
Project(
id = project.id,
name = project.name,
state = project.state,
description = project.description,
team = project.team,
tags = grouped.map(_._2.name)
)
}
}
)
}
}
题外话:
如果你想在 findAll
方法中进行分页,你需要做这样的事情:
ProjectTable
.drop(pageNumber * pageSize)
.take(pageSize)
.join(ProjectTag).on(_.id === _.projectId)
.result
上面会产生子查询,但它基本上是使用多个连接关系进行分页的典型方式(如果没有子查询,您将对整个结果集进行分页,这在大多数情况下不是您所需要的!)。
回到正文:
显然,如果您将 Project
定义为:
case class Project(project: ProjectRecord, tags: Seq[ProjectTag])
那么你的收益就是:
yield {
Project(project, tags)
}
但这绝对是个人喜好问题(按照您的方式制作它实际上是有意义的 - 隐藏内部记录布局)。
基本上这里有很多地方可以改进。我并不是真正的 DDD 专家,但至少从 Slick
的角度来看,应该做的第一个改变是改变方法:
def load(id: UUID): Future[Project]
到
def load(id: UUID): DBIO[Project]
并在更高级别上执行 db.run(...)
操作。这样做的原因是,在 Slick
中,一旦你触发 db.run
(因此将 DBIO
转换为 Future
),你就失去了在单个事务中组合多个操作的能力。因此,一种常见的模式是将 DBIO
推到应用层的相当高的位置,基本上达到定义事务边界的某些业务级别。
我正在尝试在 slick 中实现一个简单的聚合根。 但我真的不知道什么是最好的方法。
这是我的域对象:
case class Project(id: UUID,
name: String,
state: ProjectState,
description: String,
team: String,
tags: Set[String]
我想将 "tags" 存储在单独的 table 中,并从 "projects_table" 和 "project_tags_table"[=12= 构建 "Project" 对象]
这是我的 table 定义:
class ProjectTable(tag: Tag) extends Table[ProjectTableRecord](tag, Some("octopus_service"), "projects") {
def id: Rep[UUID] = column[UUID]("id", O.PrimaryKey)
def name: Rep[String] = column[String]("name")
def state: Rep[ProjectState] = column[ProjectState]("state")
def description: Rep[String] = column[String]("description")
def team: Rep[String] = column[String]("team")
override def * : ProvenShape[ProjectTableRecord] = (id, name, state, description, team, created, lastModified) <> (
(ProjectTableRecord.apply _).tupled, ProjectTableRecord.unapply
)
}
class ProjectTagTable(tag: Tag) extends Table[ProjectTag](tag, Some("octopus_service"), "project_tags") {
def projectID: Rep[UUID] = column[UUID]("project_id")
def name: Rep[String] = column[String]("name")
def project = foreignKey("PROJECT_FK", projectID, TableQuery[ProjectTable])(_.id, onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade)
override def * : ProvenShape[ProjectTag] = (projectID, name) <> (
ProjectTag.tupled, ProjectTag.unapply
)
}
如何通过连接这 2 个 table 生成 "Project" 个对象?
提前致谢:)
我认为对责任级别存在误解。 Slick 允许您访问关系数据库(在某种程度上与 SQL 允许您访问的方式相同)。它基本上是一个 DAO 层。
聚合根确实比这个高一个级别(这是一个领域的东西,而不是数据库级别的东西 - 尽管它们在很大程度上通常是相同的)。
所以基本上你需要有一个级别高于 Slick
的表,这样你就可以执行不同的查询和聚合结果化为一体。
不过在我们开始之前 - 您应该创建并存储您的 TableQuery
对象,也许像这样:
lazy val ProjectTable = TableQuery[ProjectTable]
lazy val ProjectTagTable = TableQuery[ProjectTagTable]
你可以把它们放在你附近的某个地方 Table definitions
。
首先,正如我提到的,您的聚合根 Project
需要被某些东西拉动。我们称它为 ProjectRepository
.
假设它将有一个方法def load(id: UUID): Future[Project]
。
此方法可能如下所示:
class ProjectRepository {
def load(id: UUID): Future[Project] = {
db.run(
for {
project <- ProjectTable.filter(_.id === id).result
tags <- ProjectTagTable.filter(_.projectId === id).result
} yield {
Project(
id = project.id,
name = project.name,
state = project.state,
description = project.description,
team = project.team,
tags = tags.map(_.name)
)
}
)
}
// another example - if you wanted to extract multiple projects
// (in reality you would probably apply some paging here)
def findAll(): Future[Seq[Project]] = {
db.run(
ProjectTable
.join(ProjectTag).on(_.id === _.projectId)
.result
.map { _.groupBy(_._1)
.map { case (project, grouped) =>
Project(
id = project.id,
name = project.name,
state = project.state,
description = project.description,
team = project.team,
tags = grouped.map(_._2.name)
)
}
}
)
}
}
题外话:
如果你想在 findAll
方法中进行分页,你需要做这样的事情:
ProjectTable
.drop(pageNumber * pageSize)
.take(pageSize)
.join(ProjectTag).on(_.id === _.projectId)
.result
上面会产生子查询,但它基本上是使用多个连接关系进行分页的典型方式(如果没有子查询,您将对整个结果集进行分页,这在大多数情况下不是您所需要的!)。
回到正文:
显然,如果您将 Project
定义为:
case class Project(project: ProjectRecord, tags: Seq[ProjectTag])
那么你的收益就是:
yield {
Project(project, tags)
}
但这绝对是个人喜好问题(按照您的方式制作它实际上是有意义的 - 隐藏内部记录布局)。
基本上这里有很多地方可以改进。我并不是真正的 DDD 专家,但至少从 Slick
的角度来看,应该做的第一个改变是改变方法:
def load(id: UUID): Future[Project]
到
def load(id: UUID): DBIO[Project]
并在更高级别上执行 db.run(...)
操作。这样做的原因是,在 Slick
中,一旦你触发 db.run
(因此将 DBIO
转换为 Future
),你就失去了在单个事务中组合多个操作的能力。因此,一种常见的模式是将 DBIO
推到应用层的相当高的位置,基本上达到定义事务边界的某些业务级别。