22

更新: タイトルを編集し、達成しようとしていることをよりよく説明するためにこのテキストを追加しました: ゼロから新しいアプリケーションを作成しようとしていますが、永続性についてビジネス層に知られたくありませんビジネス層に REST API 層を知られたくないのと同じように。以下は、使用したい永続化レイヤーの例です。これとの統合に関する適切なアドバイスを探しています。つまり、ビジネス ロジックと永続化ロジックの間で責任を明確に分割するための設計/アーキテクチャの支援が必要です。おそらく、ドメイン オブジェクトへの永続化オブジェクトのマーシャリングとアンマーシャリングに沿った概念です。

SLICK (別名 ScalaQuery)テスト例から、これは多対多のデー​​タベース関係を作成する方法です。これにより、a、b、a_to_b の 3 つのテーブルが作成されます。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)

次のステップとして、これを 1 レベル上げて (まだ 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 データ マッパー パターンを使用している場合、最適なオプションは、マッパーを手動で記述し、それを実装すると主張するサード パーティの ORM を使用しないことです。これにより、コードベースをより細かく制御でき、無意味な技術的負債を回避できます [1] [2]

于 2012-08-12T06:38:40.630 に答える
5

単純な永続化要件の優れたソリューションは、ActiveRecord パターンです: http://en.wikipedia.org/wiki/Active_record_pattern。これは Ruby と Play で実装されています! フレームワーク 1.2 であり、スタンドアロン アプリケーションの Scala で簡単に実装できます。

唯一の要件は、必要な DB への参照を取得するためのシングルトン DB またはシングルトン サービスを用意することです。個人的には、以下に基づいて実装を行います。

  • ジェネリック トレイト 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 に答える