ユーザー定義フィールド

2013年7月23日

ソフトウェアシステムにおける共通の機能として、ユーザーがデータ構造に独自のフィールドを定義できるようにすることがあります。アドレス帳を考えてみてください。追加したいものがたくさんあるでしょう。新しいソーシャルネットワークが毎日登場しているので、ユーザーは連絡先にBunglr ID用の新しいフィールドを追加したいと思うかもしれません。

インメモリでの目的には、多くの場合、クラスにユーザー定義フィールド用のハッシュマップフィールドを含めるのが最適な方法です(これは、ケント・ベックが可変状態と呼ぶパターンです)。

# ruby
class Contact
  attr_accessor :firstname, :lastname

  def initialize
    @data = {}
  end

  def [] arg
    return @data[arg]
  end
  def []= key, value
    @data[key] = value
  end
end

aCustomer = Contact.new
aCustomer.firstname = "Martin"
aCustomer[:bunglrId] = 'fowl'

このような設定を使用すると、ユーザーが新しいフィールドをオブジェクトに追加できるようにUIに機能を追加できます。一般的なユーザー定義フィールドが必要な場合は、クラス変数を使用して、ハッシュマップの一般的なキーのリストを保持できます。通常のフィールドがユーザー定義フィールドとは異なる方法でアクセスされるという不便さはありますが、使用する言語によっては、これも克服できます。言語が動的受信をサポートしている場合は、これを使用して通常のフィールドアクセスでハッシュマップにアクセスできます。

class Contact...

  def method_missing(meth, *args)
    if @data.has_key? meth
      return @data[meth]
    else
      super
    end
  end

多くの場合、この部分で最も難しいのは、これをどのように永続化するかを考えることです。スキーマレスデータベースを使用している場合は、通常は簡単です。アプリケーションで定義したキーにユーザー定義のキーを追加するだけです。難しさは、ストレージスキーマ、特にリレーショナルデータベースを備えたデータベースから生じます。

通常、最良の選択肢はシリアライズされたLOBを使用することです。これは、本質的に、ユーザー定義フィールドをJSONまたはXMLドキュメントとして格納する大きなテキスト列を作成します。最近の多くのデータベースは、このアプローチに対する非常に優れたサポートを提供しており、LOB内のデータ構造に基づくインデックス作成やクエリのサポートも含まれています。ただし、このようなサポートは、利用可能な場合でも、フィールドを使用するよりも煩雑になるのが通常です。[1]

もう1つの方法は、何らかの属性テーブルを使用することです。テーブルは次のようになる可能性があります。

CREATE TABLE ContactAttributes (
  contactId   INTEGER, 
  attribute   TEXT, 
  value       TEXT, 
  PRIMARY KEY (contactId, attribute))

ここでも、クエリとインデックス作成は面倒です。クエリには、かなり複雑になる可能性のある追加の結合がかなり含まれる場合があります。

事前定義されたカスタムフィールドは、別のシステムを提供します。ここでは、custom_field_1(およびおそらくcustom_field_1_nameのようなフィールドを使用してスキーマを設定します。事前定義したカスタムフィールドのインスタンスあたりの数にのみ制限されます。いつものように、インデックス作成とクエリは面倒です。

属性テーブルまたは事前定義されたカスタムフィールドを使用する場合、異なるSQLデータ型に異なる列を持つことを選択できます。したがって、事前定義されたフィールドはinteger_1, integer_2, text_1…のようになり、属性テーブルには複数の値フィールド(text_value, integer_value)がある可能性があります。

動的スキーマは、見落とされがちなアプローチです。これを行うには、誰かがフィールドを追加したときに、alter tableステートメントを使用してそのフィールドをテーブルに追加するように設定します。当社のMingleチームはこれを行っており、その成果に満足しています。[2]新しいフィールドは、アプリケーション定義フィールドと同じようにインデックスを付けてクエリできます。これは、すべてのインスタンスがすべてのフィールドを取得することを意味するため、インスタンス間で多くの分散が発生する場合はあまり便利ではありません。

永続化スキームの選択は、リレーショナルマッピングに何を使用するかによって影響を受けます。ユーザー定義フィールドは、リレーショナルマッピングの問題で最もよく知られている部分ではないため、さまざまなリレーショナルマッピングライブラリからのサポートには多くのバリエーションがあります。

ユーザー定義フィールドは、非均一型と同様の問題です[3]。どちらの問題も、より柔軟なスキーマ、または事実上スキーマレスなアプローチ(ただし、スキーマレスはスキーマがないという意味ではないことを覚えておいてください)の必要性につながります。ユーザーの要求で変更されていない非均一型がある場合は、継承指向のパターンのいずれかが理にかなっている可能性があります。(単一テーブル継承クラステーブル継承、または具体テーブル継承。)

注記

1: ブレット・テイラーは、インデックス可能なフィールドごとに個別のインデックステーブルを作成することにより、このようなスキームでフィールドにインデックスを付けるためのスキームについて説明しています

2: Mingleのアプローチは、実際には、既存のテーブルにフィールドを追加するよりも少し複雑です。Mingleの中心的なレコードタイプは、カード(ストーリー、タスクなどを表す)です。カードのフィールドはプロジェクトによって異なり、同じデータベースに多くのプロジェクトを含めることができます。したがって、単一のカードテーブルを使用するのではなく、Mingleはプロジェクトのカードごとに新しいテーブルを作成します。次に、ユーザーが必要に応じて、このテーブルにフィールドを動的に追加します。

3: 非均一型とは、インスタンスが少数の非常に異なるフィールドを使用する型です。場合によっては、これらはスパーステーブルとも呼ばれます。これは、テーブル全体を見ると、各行が多数の列のリストのうちごくわずかしか使用していないためです。非均一型とユーザー定義フィールドの違いは、非均一型には開発者に知られている可能性のあるすべてのフィールドがあるのに対し、ユーザー定義フィールドでは、開発者が知ることのないフィールドを作成できることです。