菊池 Blog

移転しました 続・菊池 和彦の足跡

AILight Banner
AILight Blog

プロフィール

菊池 Blog

目次

Blog 利用状況

記事分類

過去の記事

タグ

Webアプリケーションコードの自動生成(1)

Webアプリケーションの多くは背後にバックエンドデータベースを持ち、バックエンドに格納されたエンティティをリクエストに応じてユーザに提示、更新して行くことになる。殆どのWebアプリケーションを抽象度を上げた視点で眺めれば同じものなのだ。

Visual Studio の様な開発環境は、DataSetクラス及びTableAdapterクラスをデザイナによって自動生成する能力を持っているし各種画面をデザインする為のデザイナがあるが「それだけ」を使っても完全なアプリケーションは実際には用意できない。(これはVisual Studioの問題ではない、Visual Studioと自分のもつ目的の違いに起因する必要な機能意識の違いだ)

これは当然のことで、デザイナに与えるメタ情報があくまでデータアクセスの領域に関するものであってURLとページとのマッピング(これはSiteMapを介してユーザのナビゲーションに必須である)等のアプリケーション構築に必要なメタ情報が不足しているからである。

また、データアクセスに閉じた領域であっても、エラーハンドリングに関する情報がないため、生成されたデータアクセス層の上層でエラーハンドリング等のコーディングが必要になってくる。

このような多種多様な情報を補うことと、それに対応するメタ情報を入力する方法(スキーマ&フロントエンド)を用意する事によりWebアプリケーションの自動生成範囲は非常に高まる事になる。

メタ情報を入力する方法、特にフロントエンドの構築の手間を抑えたければ DSL Tools を利用することができる。

自分はフロントエンドとしてVisual Studioを使わないという選択肢に進んだため DSL Toolsはメリットを失い利用の選択肢から外れる事となった。

  1. 完全なアプリケーションを生成できるのであれば、利用者は開発者ではなく一般のビジネスユーザやデザイナである。この層の人たちはVisual Studioを利用しない。

この為、DSL Toolsで何度かプロトタイプを行いスキーマを検討したうえで、DSL Toolsでのフロントエンド生成には進まずに別の手段へと進むことにした。

  1. 自分は慈善団体ではないので多少のお金も欲しい、自動コード生成ロジックそのものは自分のもつノウハウだしまだまだ改善の余地がある、これは手元で運用したい
  2. ビジネスユーザやデザイナに追加ソフトウェアの利用を強いたくない。コードをコンパイルする為の .NET Framework SDKの利用も同様だ
  3. 上記2つの要件から、Web上にフロントエンドを構築し、コンパイル済みバイナリを利用者に届けるという方法が現実的になる。

幸い自分は www.aspnetcomp.jp というフロントエンドを展開可能なホスティングを利用(放置)しているし、ここにフロントエンドを展開し、入力されたメタ情報をもとに手元のマシンでコード生成からコンパイルを実行して最終的な利用者に届けるというモデルが使える。(Comp* には Compiled の意味もあると元々言ってるよね)

(お金が欲しいといっても大金は要求しないよ、普通に利用する人がどの程度の規模のものを要求するのかわからんけど、所詮月数千円のホスティングと自宅Serverでのコード生成からコンパイルにも電気代しかかからない。メタ情報を入力する手間を惜しまなければ数百円で目的のものが手に入るのが目標だ。)

この一連のPOSTは ASP.NET Webアプリケーションの自動生成についての技術解説であり、また、自動生成されるコードに投入されているノウハウの一部の開示である。(何が生成されるのかわからないんじゃ困るしね。)

さて、前置きが長引いてきた、一番根っこのデータアクセスのさらに下側、データベースに対するSQL文やストアドプロシジャの自動生成について話を進めよう。

データベースのテーブル構造のメタ情報の記述例の最も簡単なものは以下の様なものだ。(かなりの属性が省略されている)

    <Entity Name="Topic">
      <Properties>
        <Property Name="Title" Type="string" Length="80"  Description="タイトル" isPrimaryKey="true"/>
        <Property Name="CreateAt" Type="dateTime" Description="作成日"/>
        <Property Name="Text" Type="string" Description="本文"/>
      </Properties>
      <Queries>
…省略…
      </Queries>
    </Entity>

この例では Topic テーブルに Title、CreateAt、Textというカラムがある。Titleは文字列型で最大長が80文字、CreateAtは日付時刻型、Textには文字列型で最大長の設定が無い。

生成されるSQL Server 2000用の CREATE TABLE のSQL文は以下の通りだ。

CREATE TABLE Topic (
 Title nchar(80) NOT NULL,
 CreateAt datetime NOT NULL,
 Text nvarchar(MAX) NOT NULL,
 CONSTRAINT PK_Topic PRIMARY KEY CLUSTERED (Title)
)

  1. 特に指定をしなければ NOT NULL である
  2. 文字列は特に指定しなければ Unicode でフィールドが確保される。
  3. 文字列は特にLengthを指定しなければ nvarchar(MAX) でフィールドが確保される。
  4. Lengthを指定した場合(デフォルトでは)100文字以下の要素では固定長でフィールドが確保される。

実際の所、ビジネスユーザやデザイナに事細かくフィールドの型の事を聞いても「敷居を上げる」事にしかならない。型としての選択肢は boolean, number, string, dateTime, currency, blob しか提供しない事にした。(フロントエンドから詳細設定をすれば完全にSQLデータ型を制御できる)

乱暴なと思うかも知れないが、メタ情報を入力して数分待てばアプリケーションが生成できるのである、生成して出来上がったアプリケーションをプロファイルしメタ情報に吸い上げる方がユーザに聞くより早いし確実だ。(プロファイルからメタ情報への反映はまだ技術的な問題があるので初期には搭載できないかもしれない)

こんな底辺レベルのメタ情報を変えてしまっては人間がプログラミングをしていたらお手上げだが、自動生成ではそんな事は何の問題も無い、与えられたメタ情報から再度生成しなおすだけだ。

さて、アプリケーションとして正しく動くという部分を考えると上記のテーブルではまだ不足する点がある。ロック制御、キャッシュ制御、パフォーマンス/スケール問題上でのパーティション分割DB化その他の要件を考えてテーブルを設計しなおす必要があるだろう。

まずロック制御という観点ではアプリケーションレベルではRDBMSの持つペシミスティックロックは余りなじまない面がある、セッションにレコードを取り込んだ間ロックする事でペシミスティックロックは実現できるが、ユーザはセッションが生きてるのに関わらずブラウザを閉じてしまうかも知れないしどこかへ行ってしまって二度と戻ってこないかも知れない。さらには ASP.NET でWebファームを構成し SQLサーバベースのステート管理を利用した場合にはそもそもセッションの終了が認識できない(Session_Endイベントが発生しない)

更新中の短期間の整合性保障の面ではRDBMSによるペシミスティックロックは重要であることは疑いはないが、短期間に限ってしか使えないのは事実だ。

ではアプリケーションでのロック制御についてのオプションを見ていこう Entity要素のLockOptionで以下のロックオプションを利用できる。

noLockControl  (規定値) 特にロック制御を行わない、ロックを考慮しない
introduceLockTable ロックテーブルを設定しロックを管理する
lockOptimistic 楽観的なロック制御によりロックを管理する

さて、最初のロックオプションである「ロックを考慮しない」の場合である(先ほどのメタ情報のままの場合が該当する)、この場合にはロック制御は行われないので単純に後勝ちで更新して良い。

INSERTのストアドプロシジャは以下のように生成される。

CREATE PROCEDURE InsertTopic
(
 @Title nchar(80),
 @CreateAt datetime,
 @Text nvarchar(MAX),
 @affectedRows int OUTPUT
)
as
 declare @err int
 SET NOCOUNT ON
 INSERT (  Title,  CreateAt,  Text )
  VALUES (  @Title,  @CreateAt,  @Text )
 SELECT @err=@@ERROR , @affectedRows =@@ROWCOUNT
 RETURN @err

1テーブルの一行しか操作しないのでこのストアドプロシジャでは一切のトランザクション制御は必要は無い、やりたければ呼び出し側がやれば良い。

主キーの重複についてはINSERTでは先勝ちという事になる。ロック制御について細かい事を言わない設計である以上はこれで十分だ。

introduceLockTable オプションの場合、実体テーブルの他にロック用テーブルが設定される。

CREATE TABLE Topic (
 Title nchar(80) NOT NULL,
 CreateAt datetime NOT NULL,
 Text nvarchar(MAX) NOT NULL,
 CONSTRAINT PK_Topic PRIMARY KEY CLUSTERED (Title)
)
CREATE TABLE Topic_Locks (
 Title nchar(80) NOT NULL,
 lockOwner nchar(128) NOT NULL,
 lockedAt datetime NOT NULL
 CONSTRAINT PK_Topic_Locks PRIMARY KEY CLUSTERED (Title)
)

ロックテーブルは実体テーブルの主キー要素を含みロック保有者、ロック設定時刻が含まれるテーブルである。

勿論Webアプリケーションではユーザーはいつどこに消えるか解らない、そもそも認証していない場合はユーザーもわからない、lockOwnerをどうすれば良いかという問題がある。しかしユーザーがわからないでもSessionは有効だ、ここにはSessionIDを格納する事で同一セッションからは更新できるというロックが実現できる。

同様にWebアプリケーションの特性として、ユーザがいつ居なくなるか解らないという面もある。

このためロックには有効期間が必ず必要となる、これのディフォルト値は適当に20分にしてある(セッションのタイムアウトのディフォルトと一緒なのは意図的でSessionIDをlockOwnerとして使う前提である)、必要であれば lockTimeoutMinutes属性で分単位で設定できる様になっている。

この場合のINSERT用ストアドは以下のコードが生成される

CREATE PROCEDURE InsertTopic
(
 @Title nchar(80),
 @CreateAt datetime,
 @Text nvarchar(MAX),
 @lockOwner nchar(128),
 @affectedRows int OUTPUT
)
as
 declare @err int
 declare @haveLock int
 SET NOCOUNT ON
 SELECT @haveLock=COUNT(*) 
  FROM Topic_Locks 
  WHERE Title=@Title 
   AND lockOwner=@lockOwner
   AND @lockTimeout>DATEADD( minute,-20,GETDATE() )
 IF @haveLock=1
 BEGIN
  INSERT (  Title,  CreateAt,  Text )
   VALUES (  @Title,  @CreateAt,  @Text )
  SELECT @err=@@ERROR , @affectedRows =@@ROWCOUNT
  RETURN @err
 END
 RETURN -1

UPDATEやDELETEも基本としては変わらない、ロックを確認したうえでデータを操作するストアドが作られる。

Lockを設定する為のストアド、解除する為のストアドも同様に生成され、各ストアドを利用する事で適切にロック制御されたデータ処理ができる様にアプリケーションの下回りとなるデータアクセスが生成できる事となる。

長くなってきたので、一旦日を改めよう、次回は楽観ロックを設定した場合のストアド生成コードについてである。

投稿日時 : 2007年12月24日 18:20


コメントを追加

タイトル
名前
URL
コメント