如何从 Scala 的持久层中抽象域层 [英] How do I abstract the domain layer from the persistence layer in Scala

查看:25
本文介绍了如何从 Scala 的持久层中抽象域层的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

UPDATE: I've edited the title and added this text to better explain what I'm trying to achieve: I'm trying to create a new application from the ground up, but don't want the business layer to know about the persistence layer, in the same way one would not want the business layer to know about a REST API layer. Below is an example of a persistence layer that I would like to use. I'm looking for good advice on integrating with this i.e. I need help with the design/architecture to cleanly split the responsibilities between business logic and persistence logic. Maybe a concept along the line of marshalling and unmarshalling of persistence objects to domain objects.

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

From a SLICK (a.k.a. ScalaQuery) test example, this is how you create a many-to-many database relationship. This will create 3 tables: a, b and a_to_b, where a_to_b keeps links of rows in table a and 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 但很好地包装它),以处理对象.因此,在伪代码中,最好执行以下操作:

As my next step, I would like to take this up one level (I still want to use SLICK but wrap it nicely), to working with objects. So in pseudo code it would be great to do something like:

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

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

Or, something like that. I have my ideas on how I might approach this in Java, but I'm starting to realize that some of my object-oriented ideas from pure OO languages are starting to fail me. Can anyone please give me some pointers as to how approach this problem in Scala.

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

For example: Do I create simple objects that just wrap or extend the table objects, and then include these (composition) into another class that manages them?

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

Any ideas, guidance, example (please), that will help me better approach this problem as a designer and coder will be greatly appreciated.

推荐答案

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

A good solution for simple persistence requirements is the ActiveRecord pattern: http://en.wikipedia.org/wiki/Active_record_pattern . This is implemented in Ruby and in Play! framework 1.2, and you can easily implement it in Scala in a stand-alone application

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

The only requirement is to have a singleton DB or a singleton service to get a reference to the DB you require. I personally would go for an implementation based on the following:

  • 通用特性 ActiveRecord
  • 通用类型类 ActiveRecordHandler

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

Exploiting the power of implicits, you could obtain an amazing syntax:

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] 来处理这个.

With such a solution, you only need your class to extends ActiveRecord[T] and have an implicit ActiveRecordHandler[T] to handle this.

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

There is actually also an implementation: https://github.com/aselab/scala-activerecord which is based on similar idea, but instead of making the ActiveRecord having an abstract type, it declares a generic companion object.

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

A general but very important comment on the ActiveRecord pattern is that it helps meet simple requirements in terms of persistence, but cannot deal with more complex requirements: for example is when you want to persist multiple objects under the same transaction.

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

If your application requires more complex persistence logic, the best approach is to introduce a persistence service which exposes only a limited set of functions to the client classes, for example

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

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

Please also note that according to your application complexity, you might want to expose this logic in different fashions:

  • 在您的应用程序很简单并且不希望持久化逻辑可插入的情况下作为单例对象
  • 通过作为应用程序上下文"排序的单例对象,以便在启动时在应用程序中决定要使用的持久性逻辑.
  • 使用某种查找服务模式(如果您的应用程序是分布式的).

这篇关于如何从 Scala 的持久层中抽象域层的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆