2008年12月30日火曜日

Repositoryで実装してみる

とりあえずデータアクセスはLINQ to SQLのみなんだけど、テストしやすくしたいし、DataContextクラスに大量のコードを書くのもいかがな物かと。で、やっぱりRepositoryパターンだよね!ってことで今更ながらコードを書いてみた次第なんです。

データストレージ層とデータアクセス層、サービス層の分離を明確にして、インターフェースベースの実装にしとくことでテストしやすくなるっていうことなんだけど、さらにデータストレージ層を置き換えも楽ちんとかいうのは、まぁ実際そんなこと滅多にするもんじゃないから普通のプロダクトにおいてそこがメリットになることはほぼなかったり...?

データモデルクラスをLINQ to SQL(L2S)のモデルの他に、純粋なデータクラスとして別途定義するのはコード量が増えるし、ちょっとイヤだな~、と思うところでそこはLINQ to SQLのモデルクラスをそのまま使うってことで勘弁してもらいます。クラスの変換とか余計な処理も必要になるしな~、と。

でね、IRepositoryでインターフェース定義して、その実装をLINQ to SQLを使って実装するんだけど、SubmitChangesってどこに書くのがいいんだか、ちょっと悩ましいところだったりしまして。 きちんとデータモデルを分けて定義しておけば、サービス層からデータアクセス層にデータクラスを渡して、その中でL2Sモデルに値をマッピングするのが王道パターンだとしても、それをやりたくない場合、サービス層でL2Sモデルに直接値を入れたりすることになるじゃないですか。わかりにくい説明でなんだかわかんなくなるな。

テーブルPeopleがあって、L2Sモデルクラスとして

public class Person
{
  public int Id {get;set;}
  public string FirstName {get;set;}
  public string LastName {get;set;}
}

っていうのがあったとしましょうよ(これがdbmlから自動生成されたとしてください)。 んで、そのDataContextクラスをTestDataContextとしましょう。 これに新しいデータを追加するなら

var db = new TestDataContext();
var person = new Person(){Id=1,FirstName="ロビン",LastName="ニコ"};
db.People.InsertOnSubmit(person);
db.SubmitChanges();

なんて書きますよね。 更新や削除なら

var db = new TestDataContext();

var person = db.People.FirstOrDefault(p=>p.Id=1);
if (person != null) {
  person.FirstName = "オルビア";
  db.SubmitChanges();  // ←これで更新確定
}
db.People.DeleteOnSubmit(person);
db.SubmitChanges();  // ←これで削除確定 

みたいな。 追加、更新、削除を持ったRepositoryにするなら

public interface IPeopleRepository {
  void Add(Person model);
  void Update();
  void Delete(Person model);
} 

とか、って書きたくなるけど、Updateは実質SubmitChangesだけじゃん!みたいな。 この例だと引数は最大3個でいいかもしれないけど、クラス次第でスゴイ引数の数になっちゃうから、もう一つ上で直接モデルに値を入れるほうがコード少なくて済むって話しです。 普通にRepositoryの関数内で都度SubmitChangesを呼び出すのが正しい実装、だと思う。

ASP.NET MVC Storefront Part 10: Shopping Cart Refactor and Authorization : The Official Microsoft ASP.NET Site

↑ここでもそういう書き方にリファクタリングしてるし。

そうすると、複数の更新をまとめてSbumitChangesしたい、なんてのはダメにするか、そういう関数をRepositoryに追加するってことになる。どっちもなんかヤダ。 んじゃ、データ操作の確定のためのSubmitChangesもサービス層で呼べばいいのかっていうと、単純にDataContextを上の層で持ちたくない(db.SubmitChanges()なんてコードをサービス層で書きたくない)。 結局、単純にSubmitChangesと同名のメソッドをRepositoryに実装して、それをサービス層で呼び出せばいいのかな?と。そしたらDataContextをサービス層が弄る必要ないし。

var repository = new PeopleRepository();
var p1 = new Person(){...};
var p2 = new Person(){...};
var p3 = new Person(){...};
repository.Add(p1);
repository.Add(p2);
repository.Add(p3);
repository.SubmitChanges(); 

↑こんな感じ?(コレクションにしてない&InsertAllSubmitにしてないっていうのは気にしないでね) で、ふと思い出した。Fluent Programming(Fluent interface - Wikipedia, the free encyclopedia)。 データ操作の戻り値として、IRepositoryを返せば(この場合ならIPeopleRepository)つなげられるな~、と。

public interface IPeopleRepository {
  IPeopleRepository Add(Person model);
  IPeopleRepository Delete(Person model);
  void SubmitChanges();
}

↑インターフェースはこう替える。 んで、実装は済んでるとして、呼び出し側では↓こう書く。

var repository = new PeopleRepository();
var p1 = new Person(){...};
var p2 = new Person(){...};
var p3 = new Person(){...};

repository.Add(p1);
         .Add(p2);
         .Add(p3);
         .SubmitChanges();

これなら間に、Deleteとかも連結出来るもんね。

他のRepositoryをどうするんだよ、って言うのは、まぁ...、そうなんだけど...。 とりあえず、同じRepositoryの場合はコレでいいですかね(結局L2Sモデルクラスをサービス層で使うんなら分離出来てないじゃないか、っていうのはその通りなんだけど...)。

2008年12月22日月曜日

Oxiteのソリューション構成

ちょいちょいソースを見ていこうと思うところで、まずはソリューションの構成を確認して、どんなコードがどのプロジェクトに含まれてるのかを把握しとこうかなと。


ソリューションを開いたところで、表示されるプロジェクトは↓。

img.aspx

それぞれのプロジェクトに何が含まれてるのかを見てみたよ。

1.Oxite


このプロジェクトにOxiteで共通のライブラリコードや、インターフェースが集中。
BackgroundServicesにBackgroundServicesで実装する各種バックグラウンドタスクのベースクラスや、インターフェース。
Configurationにはweb.configのカスタムエレメント用のクラス。クラス自体は大量にあるけど、内容は各設定値を取得するための物だからエレメントの数だけクラスがある感じ。
Dataにはデータベースのモデルクラスのインターフェース。実装はLinqToSqlProviderに入ってる。
HandlersにMetaWeblogAPIとXmlRPCのクラス。コントローラクラスだけじゃなくてこのハンドラもルーティング登録されてるからコントローラを探しても見つからず。
Routingにはそのままルーティングを登録するクラス。ルート登録の詳細がMVCのWebアプリケーションのglobal.asaxじゃなくて、ここにすべてまとめて入ってる。
Searchに検索機能のインターフェース。
あと、ルートに共通関数が入ってる。
で、 StringExtensionsクラスにStringクラスの拡張メソッドが入ってるんだけど、なんとこの中でAntiXssLibraryをラップしてる関数有り。AntiXss.HtmlEncodeとAntiXss.HtmlAttributeEncodeを使って、エンコードするようにしてる。さすが抜け目なし。ただバージョンは先日公開された3.0じゃなくて1.5を使ってる。ちょっと古いのを使ってるのが気になるところだけどリリースの関係でOxiteの先にリリースしてるし、MIXのサイトでも使ってるって事で、そこはやむなしな感じで。

2.Oxite.BackgroundServices

バックグラウンドで動作する機能の実装。
何個かクラスがあるけど、ようはメール送信(SMTP送信)とトラックバック送信(HTTP POST)の2つを処理してる(と思う)。
CreateMessagesFromSubscriptions(BackgroundService)

コメントをメールメッセージとしてMessageRepositoryに登録。

SendMessages(BackgroundService)
MessageRepositoryに登録されてるメッセージをメール送信。

CreateTrackbacks(PostBackgroundServiceAction)
トラックバック要求をBackgroundServiceActionRepositoryに登録。

SendTrackbacks(PostBackgroundServiceAction)
BackgroundServiceActionRepositoryに登録されてる要求を実行。

3.Oxite.LinqToSqlProvider


QxiteプロジェクトのData内に定義されてる、リポジトリの実装。
モデル自体はLINQ to SQL(dbml)で定義されてるのをそのまま。
あとIQueryable<T>の拡張メソッドでページングデータ(PageOfAList<T>)を取得するものや、モデルクラスの拡張機能など。
MembershipRepositoryがoxite_User/oxite_Roleなんかを見るような独自実装なのがちょっと以外。これはGravator対応とかが関係してるんだろうか?関係なさそうだけどなんでASP.NETのMembershipじゃダメなんだろうね~。

4.Oxite.LiveSearchProvider


サイト検索をWindows Live Searchで実行するための実装。
http://soap.search.live.com/webservices.asmx?wsdlへのWeb参照(SOAP)で検索要求を送信して、結果を取得するようになってる~。

5.Oxite.SearchProvider


これも検索用の実装なんだけど、こっちはLINQ to SQLで実行。
Post.SearchPost(TitleとBodyをくっつけたもの)に対してContainsで単純に検索してる。
Like検索だね。

6.Oxite.MVC


このプロジェクトに全部のコントローラクラスが入ってる。
OxiteApplication クラスがHttpApplicationを派生したクラスとして、定義されててこの中のOnStartでバックグラウンドサービスの開始や、ルーティング登録の実行。BeginRequestでリダイレクトがどうのこうのって処理があるけど、なんのタメの機能なのかよくわかんないかも。
ControllerもBaseControllerっていうクラスをベースにして実装。
ルーティングとコントローラのクラス名を見比べると、URLの設計はそれはそれだろ的な思いもちらほらと。


AccountController(Users/{userName}/~,SignIn,SignOut)
AdminController(Admin/~)
ArchiveController(Archive/{*archiveData})
AreaController({areaName}/~)
FileController
HomeController
PageController({*pagePath})
PingbackController({id}/Pingback)
SearchController(OpenSearch.xml,Search/~)
SEOController(Robots.txt,SiteMap/~)
TagController(Tags/{tagName}/~)
TrackbackController({id}/Trackback)


Viewで使うViewPageクラスの代わりにBaseViewPage/BaseViewUserControlなんかを定義。
OxiteSite(Webアプリケーション)で使う共通関数なんかもここで定義。Oxiteでも共通関数があったりするけど、Webアプリケーションでしか使わないのはたぶんここに集中してるんだろね。
ヘルパーの拡張なんかもここ。
FeedResultっていうのがなぜかViewResultから派生させて、Viewとして定義してるのはちょっとダサイ気がしなくもない。
Trackback/Pingbackを属性クラスで定義して、ViewDataにディスカバリー情報を入れるっていうのはカッコイイ。

7.OxiteSite


これがサイトの実体プロジェクト。
でも、実質コントローラもロジックも別のプロジェクトにごっそり入ってて、ここではもっぱらViewに関する物だけがある。
なので、Global.asaxのコードビハインドではOxiteApplicationクラスから派生。
ほとんどのViewにAtom/Rssそれぞれのフィード専用Viewが入ってて冗長な気が...。もう少し綺麗にできるんじゃないのかな~。ダメなのかな~。
特徴的なのはViewDataがすべてDictionaryのままで、View内キャストするようになってるとこ。
ViewPage<T>なんて定義はなく、すべてキャスト。しかもViewに結構なコードが含まれてる気がする。
あと、ルートにLiveWriterManifest.xml(Windows Live Writer Provider Customization API) があって、MetaWeblogAPIに関する設定が書かれてた。こんなファイルがあるのを初めて知りました。


とりあえず、含まれてるプロジェクトはこんな感じってことで。
中身・機能についてもちょいちょい見ていきます。

2026年06月17日の記事一覧

(全 13 件) Foundry Toolkit for Visual Studio Code 自分のPCで「とりあえず動く」ではなく「一番賢く使える」ローカルLLMを教えてくれる「whichllm」、実測ベンチでランク付け(生成AIクローズアップ) | ...