22

更新:我编辑了标题并添加了这段文字,以更好地解释我想要实现的目标:我正在尝试从头开始创建一个新应用程序,但不希望业务层知道持久性层,就像我们不希望业务层知道 REST API 层一样。下面是我想使用的持久层的示例。我正在寻找与此集成的好建议,即我需要设计/架构方面的帮助,以清晰地划分业务逻辑和持久性逻辑之间的职责。也许是一个将持久性对象编组和解组为域对象的概念。

从 SLICK(又名 ScalaQuery)测试示例中,这就是您创建多对多数据库关系的方式。这将创建 3 个表:a、b 和 a_to_b,其中 a_to_b 保留表 a 和 b 中的行链接。

object A extends Table[(Int, String)]("a") {
  def id = column[Int]("id", O.PrimaryKey)
  def s = column[String]("s")
  def * = id ~ s
  def bs = AToB.filter(_.aId === id).flatMap(_.bFK)
}

object B extends Table[(Int, String)]("b") {
  def id = column[Int]("id", O.PrimaryKey)
  def s = column[String]("s")
  def * = id ~ s
  def as = AToB.filter(_.bId === id).flatMap(_.aFK)
}

object AToB extends Table[(Int, Int)]("a_to_b") {
  def aId = column[Int]("a")
  def bId = column[Int]("b")
  def * = aId ~ bId
  def aFK = foreignKey("a_fk", aId, A)(a => a.id)
  def bFK = foreignKey("b_fk", bId, B)(b => b.id)
}

(A.ddl ++ B.ddl ++ AToB.ddl).create
A.insertAll(1 -> "a", 2 -> "b", 3 -> "c")
B.insertAll(1 -> "x", 2 -> "y", 3 -> "z")
AToB.insertAll(1 -> 1, 1 -> 2, 2 -> 2, 2 -> 3)

val q1 = for {
  a <- A if a.id >= 2
  b <- a.bs
} yield (a.s, b.s)
q1.foreach(x => println(" "+x))
assertEquals(Set(("b","y"), ("b","z")), q1.list.toSet)

作为我的下一步,我想把它提升一个层次(我仍然想使用 SLICK,但很好地包装它)来处理对象。所以在伪代码中做这样的事情会很棒:

objectOfTypeA.save()
objectOfTypeB.save()
linkAtoB.save(ojectOfTypeA, objectOfTypeB)

或类似的东西。我对如何在 Java 中实现这一点有自己的想法,但我开始意识到,我从纯 OO 语言中获得的一些面向对象的想法开始让我失望。谁能给我一些关于如何在Scala中解决这个问题的指示。

例如:我是否创建了仅包装或扩展表对象的简单对象,然后将这些(组合)包含到另一个管理它们的类中?

任何想法,指导,示例(请),将帮助我作为设计师和编码人员更好地解决这个问题,将不胜感激。

4

2 回答 2

6

最好的想法是实现类似数据映射器模式的东西。与活动记录相比,这不会违反 SRP。

由于我不是 Scala 开发人员,因此我不会展示任何代码。

思路如下:

  • 创建域对象实例
  • 对元素设置条件(例如setId(42),如果您正在按 ID 查找元素)
  • 创建数据映射器实例
  • fetch()通过传入域对象作为参数在映射器上执行方法

映射器将查找提供的域对象的当前参数,并根据这些参数从存储(可能是 SQL 数据库、JSON 文件或远程 REST API)中检索信息。如果检索到信息,它会将值分配给域对象。

另外,我必须注意,数据映射器是为与特定域对象的接口一起工作而创建的,但是它们从域对象传递到存储并返回的信息可以映射到多个 SQL 表或多个 REST 资源。

这样,当您切换到不同的存储介质时,您可以轻松地替换映射器,甚至可以在不接触真实存储的情况下对域对象中的逻辑进行单元测试。此外,如果您决定在某个时候添加缓存,那将只是另一个映射器,它试图从缓存中获取信息,如果失败,用于持久存储的映射器就会启动。

域对象(或者,在某些情况下,域对象的集合)将完全不知道它是被存储还是被检索。那将是数据映射器的责任。

如果这一切都在 MVC 上下文中,那么,要完全实现这一点,您将需要模型层中的另一组结构。我称它们为“服务”(请分享,你们中的人想出更好的名字)。它们负责包含数据映射器和域对象之间的交互。通过这种方式,您可以防止业务逻辑在表示层(确切地说是控制器)中泄漏,并且这些服务为业务(也称为模型)层和表示层之间的交互创建了一个自然接口。

PS 再次抱歉,我无法提供任何代码示例,因为我是一名 PHP 开发人员,不知道如何在 Scala 中编写代码。

PPS 如果您使用数据映射器模式,最好的选择是手动编写映射器而不使用任何声称实现它的第 3 方 ORM。它可以让您更好地控制代码库并避免无意义的技术债务 [1] [2]

于 2012-08-12T06:38:40.630 回答
5

ActiveRecord 模式是简单持久性需求的一个很好的解决方案:http ://en.wikipedia.org/wiki/Active_record_pattern 。这是在 Ruby 和 Play 中实现的!framework 1.2,您可以在独立应用程序中轻松地在 Scala 中实现它

唯一的要求是拥有一个单例数据库或单例服务来获取对您需要的数据库的引用。我个人会根据以下内容进行实施:

  • 通用特征 ActiveRecord
  • 泛型类型类 ActiveRecordHandler

利用隐式的力量,你可以获得惊人的语法:

trait ActiveRecordHandler[T]{

  def save(t:T):T

  def delete[A<:Serializable](primaryKey:A):Option[T]

  def find(query:String):Traversable[T]
}

object ActiveRecordHandler {
  // Note that an implicit val inside an object with the same name as the trait 
  // is  one of the way to have the implicit in scope.
  implicit val myClassHandler = new ActiveRecordHandler[MyClass] {

    def save(myClass:MyClass) = myClass

    def delete[A <: Serializable](primaryKey: A) = None

    def find(query: String) = List(MyClass("hello"),MyClass("goodbye"))
  }
}

trait ActiveRecord[RecordType] {
  self:RecordType=>


  def save(implicit activeRecordHandler:ActiveRecordHandler[RecordType]):RecordType = activeRecordHandler.save(this)

  def delete[A<:Serializable](primaryKey:A)(implicit activeRecordHandler:ActiveRecordHandler[RecordType]):Option[RecordType] = activeRecordHandler.delete(primaryKey)
}

case class MyClass(name:String) extends ActiveRecord[MyClass] 

object MyClass {
  def main(args:Array[String]) = {
    MyClass("10").save
  }
}

有了这样的解决方案,你只需要你的类来扩展 ActiveRecord[T] 并有一个隐式的 ActiveRecordHandler[T] 来处理它。

实际上还有一个实现:https ://github.com/aselab/scala-activerecord ,它基于类似的想法,但不是使 ActiveRecord 具有抽象类型,而是声明了一个通用的伴随对象。


关于 ActiveRecord 模式的一个普遍但非常重要的评论是,它有助于满足持久性方面的简单需求,但不能处理更复杂的需求:例如,当您希望在同一事务下持久化多个对象时。

如果您的应用程序需要更复杂的持久性逻辑,最好的方法是引入一个持久性服务,该服务仅向客户端类公开一组有限的功能,例如

def persist(objectsofTypeA:Traversable[A],objectsOfTypeB:Traversable[B])

另请注意,根据您的应用程序复杂性,您可能希望以不同的方式公开此逻辑:

  • 在您的应用程序很简单的情况下作为单例对象,并且您不希望您的持久性逻辑是可插入的
  • 通过充当“应用程序上下文”的排序的单例对象,以便在您的应用程序启动时,您可以决定要使用哪种持久性逻辑。
  • 如果您的应用程序是分布式的,则使用某种查找服务模式。
于 2012-08-08T09:58:19.180 回答