我如何从抽象斯卡拉持久层领域层(How do I abstract the domain layer

2019-07-30 11:45发布

更新:我已经编辑了标题,并加入这段文字,以便更好地解释我努力实现:我试图创建从头开始一个新的应用程序,但不希望业务层了解的持久性层,以同样的方式是不希望业务层知道关于REST API层。 下面是我想用一个持久层的一个例子。 我在寻找好的建议与此即我需要的设计/架构有助于业务逻辑和持久性逻辑之间的分裂干净的职责整合。 也许一起编组和持久的解组线概念反对域对象。

从光滑的(又名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)

正如我的下一步计划 ,我想借此达到一个级别(我还是想用光滑,但很好地把它包),与对象的工作。 因此,在伪代码将是巨大的做一些事情,如:

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

或类似的东西。 我有我的,我怎么可能在Java中处理这个想法,但我开始意识到,一些纯面向对象的语言我的面向对象的思想也开始令我失望。 任何人都可以请给我一些指点就如何在Scala中解决这个问题。

例如:我创建简单的对象只是包装或扩展表对象,然后包括这些(组成)到管理他们另一个类?

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

Answer 1:

简单的持久性要求一个好的解决方法是ActiveRecord的模式: http://en.wikipedia.org/wiki/Active_record_pattern 。 这在Ruby和在游戏中实现! 框架1.2,你可以很容易地实现它在斯卡拉在一个独立的应用程序

唯一的要求是有一个单DB或单独服务来获得你所需要的DB的参考。 我个人会去基于以下的实现:

  • 一个普通的ActiveRecord的特质
  • 泛型类型类ActiveRecordHandler

利用implicits的力量,你可以得到一个惊人的语法:

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])

另请注意,根据您的应用程序的复杂性,你可能想揭露以不同的方式这样的逻辑:

  • 在的情况下单对象应用程序很简单,你不希望你的持久性逻辑是可插拔
  • 通过它作为一种为“应用程序上下文”,让你在启动应用程序,你可以决定你要使用的持久性逻辑一个单身的对象。
  • 带有某种查询服务模式,如果你的应用是分布式的。


Answer 2:

最好的办法是将实现类似的数据映射器模式。 其中,相对于活动记录,不会违反SRP。

由于我不是一个斯卡拉开发者,我不会让任何代码。

这个想法是以下几点:

  • 创建域对象实例
  • 在元件上设置的条件(例如setId(42)如果你正在寻找由ID元件)
  • 创建数据映射器实例
  • 执行fetch()通过在域对象传递作为参数的方法的映射器

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

此外,我必须指出,对于特定的域对象的工作界面所创建的数据映射器,但他们从域对象传递给存储和回的信息,可以映射到多个SQL表或多个REST资源。

这种方式可以很容易地替换映射器,当切换到不同的存储介质,或者甚至单元测试在域对象的逻辑而不触及实存储器。 另外,如果你决定在某些时候添加缓存,这将是又一个映射,它试图从缓存中读取信息,并且,如果失败了,在持久性存储踢的映射。

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

如果这是所有在MVC方面,那么,要充分实现这一点,就需要在模型层另一组结构。 我称他们为“服务”(请分享一下,你想出更好的名字)。 他们负责包含数据映射器和域对象之间的交互。 这样可以防止将业务逻辑从表现层泄漏(控制器,是精确的),并且这些服务为企业创造之间的相互作用的自然界面(也称为模型)层和表现层。

PS再次,抱歉,我不能提供任何代码示例,因为我是一个PHP开发人员,而且不知道如何用Scala编写代码。

PPS如果你正在使用的数据映射器模式,最好的选择是手工编写映射器,并且不使用任何第三方的ORM,号称实现它。 它会给你更多的控制权代码库,避免无谓的技术债务 [1] [2] 。



文章来源: How do I abstract the domain layer from the persistence layer in Scala