投稿数 - 24, コメント - 191, トラックバック - 26

2008年2月29日

ご挨拶

 この度わんくま同盟を脱退させて頂きました。

 他のメンバーと同時に脱退したことについて様々な御意見を頂戴している様ですが、客観的に見れば御意見の多くはごもっともだと思っております。
 ただ、私が偶々同時に脱退したことで、その方にご迷惑をお掛けしてしまったのではないかと自責の念に囚われています。
 御意見を頂戴した皆様へ申し上げさせて頂くとすれば、私自身はわんくま同盟の脱退を昨年より考えておりました。

 「様々な方面のプロフェッショナルが集まったわんくまの中で、自分は異色じゃないか?」
実はこの半年ほどの皆様のエントリを見て頻繁に感じていました。
 この半年の間に様々な「祭り」がありましたが、私はそのどれにもコメントやエントリを上げませんでした。
 正直に申し上げますと、あまり興味がわかなかったのです。
 今思うと脱退を考えたのは、「なぜ自分は興味が無いのか」を自分に問うた結果に起因するかも知れません。

 私は脱退しますが、わんくま同盟の皆様は心より尊敬しておりますし、いつの日か一歩でも近づけたらと思っております。
 今後イベントやセミナーなどで私を見かけましたら、ご迷惑でなければ声を掛けて頂ければと思います。

 半年間ほどわんくまに在籍させて頂き、23のエントリを書かせて頂きました。
 ひとつの目標であったぽぴ王子のエントリ数を超えることもできました。
 少ないエントリにも関わらず、180を超えるコメントも頂戴しました。
 コメントを頂戴した皆様には心より御礼申し上げます。
 今後は他に場を探し、皆様にご迷惑をお掛けしない範囲で細々と情報を発信して参りたいと考えております。

 今まで本当に有難う御座いました。
 そして、今後とも宜しくお願い申し上げます。

posted @ 11:00 | Feedback (4)

2008年2月17日

.NET のアプリケーション アーキテクチャ実装例 (Part 3)

はじめに

前回から大分経ってしまいましたが、.NET のアーキテクチャ「3レイヤ サービス アプリケーション パターン」実装例の第3段を紹介させて頂きます。
Part 3では Data Access Logic Component コンポーネントの私なりの解釈と実装例をご紹介させて頂きます。
尚、本文中では各コンポーネント名を以下のカッコ名表記の通りに省略します。

  • ユーザ インタフェース コンポーネント(UI)
  • ユーザ インタフェース プロセスコンポーネント(UIP)
  • サービス インタフェース(SI)
  • ビジネス ワークフロー(BW)
  • ビジネス コンポーネント(BC)
  • ビジネス エンティティ(BE)
  • データ アクセス ロジック コンポーネント(DALC)
  • サービス エージェント(SA)

# データ層の設計に関しては Patterns & Practices (以下 P&P )でも詳しく紹介されています。
( http://www.microsoft.com/japan/msdn/net/bda/BOAGag.aspx )
 

前回までのエントリ

 

Data Access Logic Component とは

DALC はデータストアへのアクセスを担当するコンポーネントです。( DAC と略される場合も有ります。)
データストアにはリレーショナル データベース、ファイル システム、メッセージング データベース等が有りますが、一般的な業務システムではリレーショナル データベースが使用されます。
尚、本エントリはリレーショナル データベースにアクセスする DALC を対象としています。

20080217-1

3レイヤ サービス アプリケーション アーキテクチャでは、データストアへのアクセスを DALC に集約することで DALC を呼び出す層(ビジネスレイヤ層など)をデータベース固有のスキーマによる制約やデータベース固有の操作から解放します。
以下は DALC の設計および実装のポイントです。

  • 一つのデータストアに対して、一つのコンポーネントを作成する。
  • DALC を呼び出すのは主に BC あるいは BW である。
  • 内部に状態を持たない”ステートレス”な設計を心がける。
  • 提供する主な機能は以下の通り。
    • エンティティに対する CRUD 操作。( Create / Read / Update / Delete )
    • 多数のテーブルからデータを取得する処理。(必要に応じてページングも実装)
    • セット指向処理による複数データの一括更新処理 。
  • 複数データストアを更新するトランザクションのルートにはならない。
  • 他の DALC を呼び出さない。(データの入出力経路を複雑にしてしまうため)

P&P では UI も DALC を直接呼び出し得ると述べています。
しかしその場合は UI の配置に制限が生じたり、セキュリティ上のリスクが増したり、ビジネスロジックをバイパスしたデータ操作が可能になってしまう等の問題が有ります。
この事から、私は UI から直接 DALC を参照させる事を極力避ける様にしています。
  

Data Access Logic Component のデータの受渡し

DALC 呼び出しのパラメータ( DALC への入力)や、DALC からの戻り値( DALC からの出力)の型には主に以下のものが利用できます。

  • スカラ値
  • XML文字列
  • DataSet または DataTable
  • DataReader
  • カスタムビジネスエンティティ

それぞれに次の様な長所/短所がありますので、用途や状況に応じて選択します。

受渡しの型

スカラ値

用途

入力/出力(更新結果件数など)

長所

シリアル化への対応。

パフォーマンス。

メモリの有効利用。(必要な情報のみを受渡し)

短所

密結合。(スキーマ変更による影響を受けやすい)

XML 文字列

用途

入力/出力(XML 形式データ検索結果など)

長所

疎結合。(スキーマ定義のみを把握すれば良い)

ビジネスエンティティのコレクションを表現可。

短所

XML の解析にかかわる労力。

メモリの非効率な利用。(XMLの冗長性に起因)

DataSet または DataTable

用途

入力/出力(データ検索結果など)

長所

シリアル化への対応。

ビジネスエンティティのコレクションを表現可。

疎結合。(スキーマ変更による影響を受けにくい)

短所

パフォーマンス。リソース消費が多い。

単一データの表現には冗長。

DataReader

用途

出力(データ検索結果など)

長所

パフォーマンスが良い。リソース消費が少ない。

短所

リモート処理時にデータ接続が長時間開かれる可能性。

シリアル化非対応。

カスタムビジネスエンティティ

用途

入力/出力

長所

疎結合。(スキーマ変更による影響を受けにくい)

ビジネスエンティティのコレクションを表現可。

短所

呼び出し側が型を認識する必要がある。

ビジネスレイヤへの参照。

私は多くの場合で DALC への入力にスカラ値を使用しています。
スカラ値を使用すると、スキーマ変更による影響は受けやすくなると言う欠点はありますが、スカラ値を用いると DALC のインタフェースやサービス内容が明確になるからです。

DALC からの出力に関しては サーバーリソース消費が少ない点では DataReader が良いのですが、SI 層から上位への転送が必要な場合は何らかのシリアル化可能オブジェクトへの格納が必要になります。
また、DataReader はデータベース接続が閉じられてしまうと内容を取得できなくなりますので、 DataReader を出力するメソッドは呼出し元からデータベース接続を受け取る作りにしておく必要があります。
 

Data Access Logic Component と同時実行制御

同時実行制御には

  • 後勝ちルール
  • 悲観同時実行制御(ペシミスティック同時実行制御)
  • 楽観同時実行制御(オプティミスティック同時実行制御)

があります。
後勝ちルールは、「後から更新したものが先に更新した内容を上書きする」と言うもので、システム的な同時実行制御をせずに運用で問題を回避することを指します。
悲観同時実行制御は途中で更新対象が変更されないように、データ読み取り時に何らかの排他制御をおこなうものです。具体的には、データベース上にステータスを保持するテーブルや項目を用意し、このステータスを随時確認しながら処理を行います。
楽観同時実行制御はデータ更新時に対象データが(読み取り時点から)変更されたかどうかをチェックするものです。 編集されたか否かの判定には、①変更項目のつきあわせ、②全項目のつきあわせ、③ Timestamp 値のつきあわせ、の3方法があります。

悲観同時実行制御の実装は業務要件に大きく依存するので DALC で画一的に実装することは困難ですが、楽観同時実行制御は DALC で制御を実装することが可能です。
上記②の更新部分の SQL は次の様になります。(テーブルに Timestamp 列がない場合、TableAdapter 構成ウィザードはこの様な SQL を生成してくれます。)

    

  UPDATE

    Sales.Currency

  SET

    [Name] = @Name,

    [ModifiedDate] = @ModifiedDate,

    [CurrencyCode] = @CurrencyCode

  WHERE

    [Name] = @Original_Name AND

    [ModifiedDate] = @Original_ModifiedDate AND

    [CurrencyCode] = @Original_CurrencyCode

     


しかし、上記方法では image 型などの値を保持するテーブルの変更判定は出来ませんし、更新前の値を全て保持しておく必要があります。
一方、上記③の更新部分の SQL は次の様になります。(テーブルに Timestamp 列があると、TableAdapter 構成ウィザードはこの様な SQL を生成してくれます。)

     

  UPDATE

    KeyValueTable

  SET

    [KeyName] = @KeyName,

    [Value] = @Value

  WHERE

    [KeyName] = @Original_KeyName AND

    [Timestamp] = @Original_Timestamp

     


この方法は image 型を含むテーブルでも使用できますし、全項目を比較するよりもシンプルなコードになりますので、私は楽観同時実行制御実装時にはこの方法を採用しています。(その為、システムから更新する可能性のあるテーブルには必ず Timestamp 型の列を持たせる様にしています。)
 

データ アクセス ヘルパ コンポーネント

アプリケーションに複数の DALC が含まれる場合、データベース接続の管理、コマンド実行などにデータ アクセス ヘルパ コンポーネントを使用します。
このデータ アクセス ヘルパ コンポーネントには Enterprise Libaray の Data Access Application Block ( DAAB )を利用することも出来ます。 Enterprise Library とは、Microsoft 社が推進するオープンソースのライブラリです。データアクセス以外にも、ロギング、例外ハンドリングなどの各種ライブラリを取り揃えています。

一方、Enterprise Library では大袈裟だと言う場合は自前で簡単なヘルパ コンポーネントを用意することも出来ます。
以下は簡略な データ アクセス ヘルパ コンポーネント用のクラスです。

     

  - C#  -

     

  using System;

  using System.Configuration;

  using System.Data;

  using System.Data.Common;

  using System.Transactions;

     

  //抽象データベースクラス

  public class Database : IDisposable

  {

    private bool alreadyDisposed = false;

    private DbConnection dbConnection;

    private DbTransaction dbTransaction;

     

    public Database(string connectionConfigName)

    {

      if (string.IsNullOrEmpty(connectionConfigName))

      {

        throw new ArgumentNullException("connectionConfigName");

      }

     

      ConnectionStringSettings cnnConfig;

      cnnConfig = ConfigurationManager.ConnectionStrings[connectionConfigName];

     

      DbProviderFactory factory;

      factory = DbProviderFactories.GetFactory(cnnConfig.ProviderName);

     

      dbConnection = factory.CreateConnection();

      dbConnection.ConnectionString = cnnConfig.ConnectionString;

     

      dbConnection.Open();

    }

     

    public Database(DbTransaction transaction)

    {

      if (transaction == null)

      {

        throw new ArgumentNullException("transaction");

      }

     

      dbConnection = transaction.Connection;

      dbTransaction = transaction;

    }

     

    public string ConnectionString

    {

      get

      {

        return dbConnection.ConnectionString;

      }

    }

     

    public string DatabaseName

    {

      get

      {

        return dbConnection.Database;

      }

    }

     

    public string DataSource

    {

      get

      {

        return dbConnection.DataSource;

      }

    }

     

    public string ServerVersion

    {

      get

      {

        return dbConnection.ServerVersion;

      }

    }

     

    public ConnectionState State

    {

      get

      {

        return dbConnection.State;

      }

    }

     

    public DbTransaction BeginTransaction()

    {

      dbTransaction = dbConnection.BeginTransaction();

      return dbTransaction;

    }

     

    public DbTransaction BeginTransaction(

        System.Data.IsolationLevel isolationLevel)

    {

      dbTransaction = dbConnection.BeginTransaction(isolationLevel);

      return dbTransaction;

    }

     

    public void ChangeDatabase(string databaseName)

    {

      dbConnection.ChangeDatabase(databaseName);

    }

     

    public void Close()

    {

      dbConnection.Close();

    }

     

    public DbCommandWrapper CreateCommandWrapper(string commandText, CommandType type)

    {

      if (String.IsNullOrEmpty(commandText))

      {

        throw new ArgumentNullException("commandText");

      }

     

      DbCommand cmd = dbConnection.CreateCommand();

      cmd.CommandText = commandText;

      cmd.CommandType = type;

      if (dbTransaction != null)

      {

        cmd.Transaction = dbTransaction;

      }

     

      return new DbCommandWrapper(cmd);

    }

     

    void IDisposable.Dispose()

    {

      Dispose(true);

      GC.SuppressFinalize(this);

    }

     

    public void EnlistTransaction(Transaction transaction)

    {

      dbConnection.EnlistTransaction(transaction);

    }

     

    public void Open()

    {

      dbConnection.Open();

    }