2019 年 2 月

Volume 34 Number 2

[働くプログラマ]

Naked のコーディング:Naked のスピーカー

Ted Neward 氏 | 2019 年 2 月

Ted Neward前回の記事で、Naked Objects フレームワーク (NOF) について紹介しました。ワーク アラウンド UI やオブジェクトのデータ持続性といったものをすべて目につかない部分で行うことを目指したフレームワークです。そのため開発者は、MVC、Web API、HTTP、SQL、データベース接続管理、依存関係の挿入など時間とエネルギーの大部分を費やすことになるすべての事柄について学ぶ前に、学校で教わった方法で、ドメイン オブジェクトを構築することだけに集中することができます。これが実際に機能したら、本当にすばらしいアイデアだと言えます。しかし、前回の記事では機能については扱いませんでした。この記事では、NOF のクラスで定義できること、またそれが UI やデータベースの持続性にどのように変わっていくかについて掘り下げていきたいと思います。

Naked の概念

コードの詳細に入る前に、NOF の用語のいくつかについて触れておきたいと思います。そうすることで、作成者と管理者がどのような世界観を持って作業していくかある程度理解できるようになります。

まず第一に、この世界の中心にあるのはドメイン オブジェクトです。これは、システムの動作と状態を表すオブジェクトです。NOF では、NOF が必要なサポートを提供できるように、仮想プロパティを使用する、コレクション メンバーを初期化する、といったいくつかのルールに従いさえすれば "ごく普通の C# オブジェクト" がドメイン オブジェクトになります。また NOF では、メモリ内のオブジェクトのライフサイクルが馴染みのあるものとは異なっている可能性があるため、コンストラクターの使用は勧められていません。これについては、後で詳しく説明します。

ただし、すべてがオブジェクトのパラダイムに適合するわけではありません。オブジェクトに対して行われるものの、オブジェクトに特別適合するわけではない動作もあります。NOF では、乱暴な仕方でオブジェクトに動作を強制せずに、オブジェクトの "外部" で動作 (ドメイン オブジェクトでの作成、取得などの操作) を提供するサービスをサポートします。サービスは、電子メール サーバーのような外部システム サービスへのゲートウェイとして機能することもできます。

ユーザーの観点から、UI は、ユーザーがトリガーできる動作を提供する必要があります。それらはアクションと総称され、多くの場合、UI のメニューに対応します。アクションはさまざまなソースから検出 (または "提供") できますが、ほとんどの場合、ドメイン オブジェクトまたはサービスに対するリフレクションを介して検出され、UI に表示されます。この検出と表示の大部分は、カスタム属性を使用して、必要なときに必要な場所で調整することができます。後ほど、この点については詳しく説明します。

こうしたことすべては、コードによってさらに明確になります。それでは、始めましょう。

Naked カンファレンス

MEAN シリーズでは、コードを作成する背景として、シンプルなドメインの例 (会議で話をする講演者) を用いました。同一条件で比較するというだけの理由であっても、ここでも同じ例を使用するのが良いと思います。ドメイン モデルについて言えば、非常にシンプルです。Speaker は、名、姓、年齢、主題 (C#、VB、Java、JavaScript、TypeScript、C++)、および平均の評価 (0 から 5) で構成されます。オブジェクト間の関係を設定する方法がわかったら、後からプレゼンテーション (タイトルと説明で構成される) を追加できます。ですが、今はシンプルなままにしておきましょう。

前回の記事で説明したように、NOF Template.zip を元にして開始します。そこで、NOF GitHub プロジェクトの README (bit.ly/2PMojiQ) にあるリンクからそのファイルをダウンロードして新しく作成したサブディレクトリに解凍し、Visual Studio でソリューションを開きます。ここで Template.Model プロジェクト ノードを開き、Student.cs と ExampleService.cs ファイルを削除します。Student は保存しません。保存するのは Speaker です (後で Presentation も保存しますが、後はリファクタリングに任せそのままにし、NakedObjects がどのようにリファクタリングをサポートしているかだけご確認ください)。

念のため、Naked Objects プロジェクト内のドメイン タイプとして Speaker がサポートされるように、次の操作を行う必要があります。ドメイン タイプを作成する必要があります。それらを取得したり、作成したりするためのリポジトリ オブジェクトがあるかどうかを確認する必要があります。Naked Objects フレームワークでメニューを生成するために、リポジトリを使用できることを確認する必要があります。最後に、何人かの講演者についてデータベースに値を指定する必要があります (正直に言うと、これは任意です)。

Speaker オブジェクト

Speaker クラスの作成は、実は 4 つの手順の中で最も簡単です。Template.Model プロジェクトで、Speaker.cs ファイルを作成し、そのファイルの Template.Model 名前空間内に、Speaker という名前のパブリック クラスを作成します。名、姓、年齢で Speaker を定義します (今のところは)。そのため、3 つのプロパティを作成しますが、それらを "virtual" としてマークします。これは、目的の動作を実行するために、実行時に NakedObjects が追加の動作を正しい位置に配置できるようにするためです (サブクラスを介して)。

Speaker に少し複雑な設定を追加してみましょう。最初に、主キーとして使用できるデータベースに格納されているオブジェクトのドメインに関係ない識別子を使用し、ユーザーがその識別子を使用したり、変更したりできないようにしたいと思うかもしれません。NakedObjects システムでは、UI に表示されないように、"NakedObjectsIgnore" カスタム属性で任意のプロパティにフラグを設定できます。では、この属性でフラグが設定された整数の "Id" プロパティを追加しましょう (繰り返しますが virtual で)。さらに、その他のデータ要素を使用して "計算" されるプロパティを作成することも珍しくありません。名と姓を連結した "FullName" 読み取り専用プロパティを追加してみましょう。NakedObjects はこのプロパティについて UI を編集しないため、virtual である必要はありません。

完了したら、コードは次のようになります。

public class Speaker
{
  [NakedObjectsIgnore]
  public virtual int Id { get; set; }

  public virtual string FirstName { get; set; }
  public virtual string LastName { get; set; }
  public virtual int Age { get; set; }

  [Title]
  public string FullName { get { return FirstName + " " + LastName; } }
}

ここまでは問題ありません。

Speaker のリポジトリ

このパターンに慣れていない方のため説明しますが、リポジトリ オブジェクトは、基本的にデータベースへのアクセスを表すオブジェクトです。標準のデータベース要求で簡単なメソッド呼び出しを提供します。リポジトリ オブジェクトは、NakedObjects システムにおけるサービスの一形態で、ドメイン オブジェクトではないものの、システムの代わりに UI 要素と動作を提供できるオブジェクトとして NakedObjects によって認識されるオブジェクトです。この場合、リポジトリは、データベースから講演者を取得するためのメニュー項目を提供します (たとえば、姓で並べ替えられた)。

具体的に言うと、NakedObject サービスは別個のクラスであり、IDomainObjectContainer オブジェクト (NakedObjects フレームワークによって提供されるオブジェクトで、システムの残りの部分へのアクセスを提供する) への参照など、Speaker クラスにはない働きをします。使用可能なシステム サービスの一覧が NakedObjects のドキュメントに示されていますが、特に 2 つの要素に注目したいと思います。新しい Speaker オブジェクトを構築し、使用するために NakedObjects インフラストラクチャの残りの部分と関連付ける NewTransientInstance メソッドと、データベースから Speaker オブジェクトの完全なセットを返す Instances メソッドです。最小の SpeakerRepository は、図 1 のようになります。

図 1 SpeakerRepository クラス

public class SpeakerRepository
{
  public IDomainObjectContainer Container { set; protected get; }

  public Speaker CreateNewSpeaker()
  {
    // 'Transient' means 'unsaved' - returned to the user
    // for fields to be filled in and the object saved.
    return Container.NewTransientInstance<Speaker>();
  }

  public IQueryable<Speaker> AllSpeakers()
  {
    return Container.Instances<Speaker>();
  }
}

NewTransientInstance と Instances の両方のメソッドが汎用関数であることに注目してください。関数呼び出しへの型パラメーターとして使用するオブジェクト タイプを取ります (これは、
DomainObjectContainer では、Speaker だけでなく、さまざまなドメイン オブジェクトのタイプを使用するために必要です)。

新しい Speaker を作成し、それらのすべてのリストを取得するだけのリポジトリは、あまり便利ではありません。ただし、特定のオブジェクトをフィルター処理して取り除き、特定の順序で並べ替える必要が生じることはよくあります。ここで、NakedObjects フレームワーク内の LINQ と EntityFramework が威力を発揮します。AllSpeakers メソッドが IQueryable を返すため、LINQ メソッドを使用して、返されるオブジェクトをフィルター処理したり、並べ替えたりすることができます。姓で講演者を検索する場合は、リポジトリ クラスにこのメソッドを追加するだけです。結果をフィルター処理する場合には、LINQ を使用します。

public class SpeakerRepository
{
  // ... as before

  public IQueryable<Speaker> FindSpeakerByLastName(string name)
  {
    return AllSpeakers().
      Where(c => c.LastName.ToUpper().Contains(name.ToUpper()));
  }
}

LINQ で行えることはすべて、リポジトリのメソッドとして表現できます。

講演者のコンテキスト

その他に管理の必要がある詳細に、次の 2 つがあります。NakedObject 常設エンジンによって使用される Entity Framework DbContext を設定する必要があります。また、新しい講演者の作成、既存の講演者の検索などを実行するメニュー項目を用意するために、SpeakerRepository を UI と関連付ける必要があります。

1 つ目は、Template.DataBase プロジェクトの ExampleDbContext.cs ファイル (必要であれば、名前を自由に変更できます) の ExampleDbContext クラスで行います。そのクラスのパブリック プロパティとして、"Speakers" という名前の Speaker タイプに DbSet をパラメーター化する必要があります。

namespace Template.DataBase
{
  public class ExampleDbContext : DbContext
  {
    public ExampleDbContext(string dbName,
      IDatabaseInitializer<ExampleDbContext> initializer) : base(dbName)
    {
      Database.SetInitializer(initializer);
    }

    public DbSet<Speaker> Speakers { get; set; }
  }
}

最上位レベルのドメイン オブジェクトのタイプをシステムに追加するときに、DbSet を返すより詳細なプロパティを追加する必要がありますが、ここでは、ここまでで問題ありません。

始めに何人かの講演者の値をデータベースに設定したい場合、Template.SeedData プロジェクトを開き、ExampleDbInitializer.cs ファイルを見つけ、Seed メソッド (そのファイルで既に定義されている) を書き込んで、その Speaker の DbSet を介して Speaker オブジェクトを ExampleDbContext (前の段落でリファクタリングだけしたオブジェクト) に追加する必要があります (図 2 を参照)。

図 2 Speaker オブジェクトの DbContex への追加

public class ExampleDbInitializer : 
  DropCreateDatabaseIfModelChanges<ExampleDbContext>
{
  private ExampleDbContext Context;
  protected override void Seed(ExampleDbContext context)
    {
    this.Context = context;

    Context.Speakers.Add(new Speaker() {
      FirstName = "Ted", LastName = "Neward", Age = 47
    });
  }
}

これは、単純な Entity Framework のコードであるため、ドロップの変更方法やデータベースのポリシー作成方法の詳細については、Entity Framework ドキュメント セットを参照してください。

コードを追加する必要がある最後の箇所は、Template.Server プロジェクトの NakedObjectsRunSettings.cs ファイルです。このコードは、NakedObjects システムへの登録のフックやヒントのコレクションで、NakedObjectsFramework が何を認識する必要があるかを示します。たとえば、"Template.Model" がこの会議アプリのモデル オブジェクトの適切な名前空間ではないと判断した場合、この NakedObjectsRunSettings の ModelNamespaces に変更後の名前が反映すれさえすれば、任意の名前に変更できます。

SpeakerRepository については、"Services" タイプの配列に追加することによって、NakedObjects にそのことを通知する必要があります。SpeakerRepository メソッドをメニュー項目を介してアクセスできるようにする場合、メニュー項目として使用するアクションについて SpeakerRepository を調べるように NakedObjects に指示する必要もあります。そのため、図 3 に示されているように、MainMenus メソッドから返されるメニュー項目の配列の一部としてこのメソッドを含める必要があります。

図 3 NakedObjects 実行設定

public class NakedObjectsRunSettings
{
  // ...

  private static Type[] Services
  {
    get
    {
      return new Type[] {
        typeof(SpeakerRepository)
      };
    }
  }

  // ...

  public static IMenu[] MainMenus(IMenuFactory factory)
  {
    return new IMenu[] {
      factory.NewMenu<SpeakerRepository>(true, "Menu")
    };
  }
}
}

これらの変更を行ったら、プロジェクトを起動し、クライアントを起動して何人かの講演者を作成できます。FullName プロパティ (FirstName と LastName で作成) は表示されますが、Id は表示されません。また、SpeakerRepository の FindSpeakersByLastName メソッドのおかげで、姓で講演者を検索できます。複数の講演者が返された場合、クライアントは個々の処理に選択できる講演者の一覧を表示する方法を自動的に認識します。

まとめ

この記事では Template.Client プロジェクトには何も手を加えませんでした。これが要点です。UI は、カスタム属性 (今後の記事でさらに扱われます) によって提供される補足情報を使用して、ドメイン オブジェクトとサービスの外で構築されます。同様に持続性は、フレームワークがタイプから検出する構造とメタデータの外で構築されます。

さらに学ぶ必要があるのは、Speaker に講演者が扱う主題のリストや提供するプレゼンテーションのコレクションが存在しているというような、より複雑なドメインのシナリオを NOF が処理する方法です。また、NOF がそれらのシナリオで UI を提供する方法についても学習する必要があります。次の記事では、これらのトピックのいくつか取り上げます。それまでの間、コーディングを楽しんでください。


Ted Neward 氏は、シアトルを拠点に活躍している、ポリテクノロジに関するコンサルタント、講演者、および指導者です。これまでに非常に多くの記事を執筆している Ted は、さまざまな書籍を執筆および共同執筆し、世界を股に掛けて講演を行っています。彼の連絡先は、ted@tedneward.com です。また、blogs.tedneward.com にブログを公開しています。


この記事について MSDN マガジン フォーラムで議論する