2017-07-04 18 views
1

私はdbにさまざまなタイプのエンティティを持っており、ある種の条件でタイプセーフな方法でそれらをフィルタリングできるようにしたいと考えています。私はdbアクセスのためにSlickを使用していますが、それは本当に問題ではありません。Scalaで型安全なフィルタリングを作成するには?

たとえば、ユーザーエンティティと投稿エンティティがあるとします。ユーザーにはidフィールドとemailフィールドがあり、Postにはidフィールドとtitleフィールドがあります。

今はADTを使用してフィルタを表しています。次のようになります。

sealed trait Filter { 

    def byId(id: Long): Filter = and(ById(id)) 

    def byEmail(email: String): Filter = and(ByEmail(email)) 

    def byTitle(title: String): Filter = and(ByTitle(title)) 

    def and(other: Filter): Filter = And(this, other) 

} 

object Filter { 
    def apply(): Filter = NoFilter 
} 

case class ById(id: Long) extends Filter 

case class ByEmail(email: String) extends Filter 

case class ByTitle(title: String) extends Filter 

case class And(a: Filter, b: Filter) extends Filter 

case object NoFilter extends Filter 

UserDaoでそれを解釈します。 PostDaoは同じになります。

trait Dao[A] { 
    def find(filter: Filter, offset: Int, limit: Int):Seq[A] 
} 

object UserDao extends Dao[User]{ 

    override def find(filter: Filter, offset: Int, limit: Int): Seq[Any] = { 
    filterTable(filter).drop(offset).take(limit).result 
    } 

    private def filterTable(table: Query[UserTable, User, Seq], filter: Filter): Query[UserTable, User, Seq] = 
    filter match { 
     case ById(id) => table.filter(_.id === id) 
     case ByEmail(email) => table.filter(_.email === email) 
     case And(a, b) => filterTable(filterTable(table, a), b) 
     case NoFilter => table 
     case other => 
     log.warn(s"Filter not supported $other") 
     table 
    } 
} 

そして汎用サービスにより使用されます。あなたはそれが動作参照が、それは安全で入力していないとして

class Service[A](dao: Dao[A]) { 
    def find(filter: Filter): Seq[A] = { 
    // do some stuff 
    dao.find(filter, offset = 0, limit = 100) 
    // do some other stuff 
    } 
} 

。タイトルによるユーザーのフィルタリングは失敗しませんが、それは意味をなさないし、バグの原因となる可能性があります。私は別のエンティティのためにADTの異なるセットを作成するかもしれないと思ったが、その後、私は非常に似ているフィルタを複製する必要があるだろう(例えばIDフィルタ)。要約すると、私はフィルターを持っています:

  1. タイプセーフです。あなたはユーザー
  2. あなたがそうで ByIdAndOrInなどの一般的なフィルタを複製する必要はありません
  3. 用フィルターByTitleを作成することはできません。

どうすればいいですか?たぶんADTは私が必要としているものではありませんか?

答えて

1

あなたのドメインオブジェクトのための「マーカー」形質の束を作成した場合、その後、あなたはデータの種類を使用してFilterタイプをパラメータすることができ、それをフィルタリングすることができます。

trait Filter[-T] 

    case class ById(id: Long) extends Filter[HasId] 
    case class ByEmail(email: String) extends Filter[HasEmail] 
    case class ByTitle(title: String) extends Filter[HasTitle] 
    case class And[A, B](a: Filter[A], b: Filter[B]) extends Filter[A with B] 
    case class Or[A, B](a: Filter[A], b: Filter[B]) extends Filter[A with B] 

を今、あなたにfilterTableを宣言することができます

def filterTable(table: Query[UserTable, User, Seq], filter: Filter[User]) = ... 

ので、filterTable(myTable, ById(1) and ByEmail("foo"))がコンパイルされますが、 filterTable(myTable, ByTitle("foo"))はしません:それは受け入れるフィルタの種類を制限します。

+0

ありがとうございました!それは非常に興味深いアプローチであり、うまくいくかもしれません。私は本当に私のドメインオブジェクトにそのような特性を追加したくないが、私は回避策があると思う。私を悩ますことの1つは、網羅的でないパターンマッチです。このアプローチには網羅的なパターンマッチがありますか?私は今日それを試してみると、うまくいけば私はあなたの答えを受け入れる。再度、感謝します。 –

+0

まあ、 'フィルター'形質を封印すると、完全に一致するはずです。 typeclassesを使用してドメインオブジェクトに特性を追加する必要がなくなりますが、(1)それはより多くのタイプ入力であり、さらに多くの "魔法"がリーダーやコードのために行われるため、通常はありません他のオプションがある限り(つまり、あなたのドメインオブジェクト定義を管理している限り)、(2)そのような場合には完全に一致させる方法は考えられません。 – Dima

関連する問題