ZIO Quill:如何为不同实体通用化共享操作?
实现Scala查询中通用的
changedAfter方法 问题背景
我们有两个包含时间戳字段的Scala case class:
import java.time.LocalDate case class Person(name: String, age: Int, createdAt: LocalDate, lastModified: LocalDate) case class Address(street: String, zip: Int, createdAt: LocalDate, lastModified: LocalDate)
希望写出如下风格的查询代码,其中.changedAfter()方法能通用作用于任何包含createdAt和lastModified字段的实体:
for { p <- query[Person] .changedAfter(lift(LocalDate.of(2022, 1, 1))) a <- query[Address].join(a => a.ownerId == p.id) .changedAfter(lift(LocalDate.of(2022, 1, 1))) } yield (p, a)
实现方案
1. 定义类型类约束实体
用Scala的类型类HasTimestamps来抽象所有带有createdAt和lastModified字段的实体,避免强制继承父类的侵入式设计:
trait HasTimestamps[T] { def createdAt(entity: T): LocalDate def lastModified(entity: T): LocalDate }
2. 为实体提供类型类实例
手动实现(Scala 2/3通用)
给Person和Address分别实现类型类实例:
// Person的类型类实例 implicit val personTimestamps: HasTimestamps[Person] = new HasTimestamps[Person] { override def createdAt(p: Person): LocalDate = p.createdAt override def lastModified(p: Person): LocalDate = p.lastModified } // Address的类型类实例 implicit val addressTimestamps: HasTimestamps[Address] = new HasTimestamps[Address] { override def createdAt(a: Address): LocalDate = a.createdAt override def lastModified(a: Address): LocalDate = a.lastModified }
自动派生(Scala 3简化版)
利用Scala 3的镜像(Mirror)功能自动生成实例,无需手动编写每个实体的实现:
object HasTimestamps { inline given [T <: Product](using mirror: Mirror.ProductOf[T]): HasTimestamps[T] = new HasTimestamps[T] { override def createdAt(entity: T): LocalDate = mirror.productElement(entity, mirror.productElementNames.indexOf("createdAt")).asInstanceOf[LocalDate] override def lastModified(entity: T): LocalDate = mirror.productElement(entity, mirror.productElementNames.indexOf("lastModified")).asInstanceOf[LocalDate] } }
只要case class包含createdAt和lastModified字段,就能自动获得HasTimestamps实例。
3. 给查询类型扩展changedAfter方法
假设你的query[T]返回的是Query[T]类型(比如Slick等ORM的查询对象),通过扩展方法为其添加通用的changedAfter:
// 适配带lift的查询DSL场景(比如Slick) extension [T](query: Query[T])(using ts: HasTimestamps[T]) def changedAfter(dateExpr: Rep[LocalDate]): Query[T] = query.filter(entity => ts.createdAt(entity) > dateExpr || ts.lastModified(entity) > dateExpr )
这里的Rep[LocalDate]是查询DSL中代表表达式的类型,lift方法负责将普通LocalDate转换为查询表达式类型。
如果是简单的内存查询场景,扩展方法可以简化为:
extension [T](query: Iterable[T])(using ts: HasTimestamps[T]) def changedAfter(date: LocalDate): Iterable[T] = query.filter(entity => ts.createdAt(entity).isAfter(date) || ts.lastModified(entity).isAfter(date) )
效果验证
完成上述实现后,就能直接使用你期望的查询语法,changedAfter会自动作用于所有符合条件的实体类型。
内容的提问来源于stack exchange,提问作者Askar Bozcan
相关产品推荐
相关产品推荐

