制約の抽象化(.NET、ASP.NET MVC)



リポジトリなどの抽象化の拒否について最初に書いてからほぼ3年が経ちました。 それ以来、私は開発中のシステムでリポジトリの概念を実際に使用していません。 私はプロジェクトから既存のリポジトリを削除しませんでしたが、今では抽象化としてそれらの値を見つけることができません。



開発者が作成するリポジトリには、主に2つのタイプがあります。



最初のケースの例は、次のようなものです。

public interface IConferenceRepository { IRavenQueryable<Conference> Query(); Conference Load(Guid id); }
      
      





通常、リクエストのカプセル化にはさらに数行かかります。

 public interface IConferenceRepository { IEnumerable<Conference> FindAll(); IEnumerable<Conference> FindFuture(); IEnumerable<Conference> FindFree(); IEnumerable<Conference> FindPaid(); }
      
      





ここでは、各メソッドが1つのリクエストをカプセル化します。 両方のケースは、特定のシナリオで価値があります。 私の目標がORMから離脱することである場合、私は最初の方法に進み、多分2番目の方法もオンにします。



しかし、ORMは抽象化を必要とするものですか? 私はそうは思いません-ORMのようなものから抽象化すると、その強力な機能を積極的に使用できなくなります。 ORMはすでに抽象化されています。本当に自問しなければなりません。 抽象化の抽象化が必要ですか?



より深く掘る



まず、質問に戻る必要があります。なぜリポジトリテンプレートを使用し始めたのですか? 確かにこれは「 テスト容易性 」の名の下に行われました。 次に、次のようなものから始めましょう。

 public ActionResult Index() { RavenQueryStatistics stats; var posts = RavenSession.Query<Post>() .Include(x => x.AuthorId) .Statistics(out stats) .WhereIsPublicPost() .OrderByDescending(post => post.PublishAt) .Paging(CurrentPage, DefaultPage, PageSize) .ToList(); return ListView(stats.TotalResults, posts); }
      
      





複雑そうですか? いや 複雑さが増した場合でも、その範囲をこの1つの方法だけに制限します。 このリクエストを別のクラス、リポジトリ、または拡張メソッドに出力しても、リクエスト自体は1つのメソッドに残ります。 コントローラーメソッドの観点から、このコードがコントローラーまたは別のクラスのどこにあるかは重要ですか?



もっと複雑な例はどうですか:

 public ActionResult Archive(int year, int? month, int? day) { RavenQueryStatistics stats; var postsQuery = RavenSession.Query<Post>() .Include(x => x.AuthorId) .Statistics(out stats) .WhereIsPublicPost() .Where(post => post.PublishAt.Year == year); if (month != null) postsQuery = postsQuery.Where(post => post.PublishAt.Month == month.Value); if (day != null) postsQuery = postsQuery.Where(post => post.PublishAt.Day == day.Value); var posts = postsQuery.OrderByDescending(post => post.PublishAt) .Paging(CurrentPage, DefaultPage, PageSize) .ToList(); return ListView(stats.TotalResults, posts); }
      
      





繰り返しますが、これは単なるクエリのコレクションです。 これを1つの場所にカプセル化したいのですが、このコードを現在の場所から移動する理由はありません。 リクエストが変更された場合、コードを1か所で変更するだけです。 この場合の追加の抽象化は、混乱を招くだけです。



コントローラーメソッドで使用するいくつかの概念がある場合、ニュアンスが生じます。 いくつかのことを行う必要があるコントローラーメソッドを見てみましょう。

 [ValidateInput(false)] [HttpPost] public ActionResult Comment(CommentInput input, int id, Guid key) { var post = RavenSession .Include<Post>(x => x.CommentsId) .Load(id); if (post == null || post.IsPublicPost(key) == false) return HttpNotFound(); var comments = RavenSession.Load<PostComments>(post.CommentsId); if (comments == null) return HttpNotFound(); var commenter = RavenSession.GetCommenter(input.CommenterKey); if (commenter == null) { input.CommenterKey = Guid.NewGuid(); } ValidateCommentsAllowed(post, comments); ValidateCaptcha(input, commenter); if (ModelState.IsValid == false) return PostingCommentFailed(post, input, key); TaskExecutor.ExcuteLater(new AddCommentTask(input, Request.MapTo<AddCommentTask.RequestValues>() ,id)); CommenterUtil.SetCommenterCookie(Response, input.CommenterKey.MapTo<string>()); return PostingCommentSucceeded(post, input); }
      
      





この場合、多くの検証がありますが、実際の作業はAddCommentTask



オブジェクトに与えられます。 これは、MVC、検証、ActionResultなどの外部でのタスク実行を処理するコマンドオブジェクトです。



抽象化( AddCommentTask



ようなタスク)からいくつかの概念を作成しました。その場合、クエリでも同じことができます。



テスト戦略



今日の私のテスト戦略は:



オートモーキングにはコンテナを使用しません。 制御できないスタブコンポーネントを配置します。 そうでなければ、それはロジックをますます深くする戦略に変わります。



データベースのようなものの場合、私のテストは遅くなります。 また、リファクタリングが容易になるため、これを受け入れることを好みます。 何らかの種類のスタブをやり直す必要があるからといって、私のテストは壊れません。



私のコントローラーでは、テスト用のインターフェース( seam、seam-およそEd。 )が必要です。 RaccoonBlogプロジェクトではこれは、RavenDBストレージエンジンをメモリ内に置き換えるだけで、テストがはるかに高速になることを意味します。



しかし、そうでない場合でも-リポジトリを追加することは心配していません。 私の経験では、何かを引き出すためだけにリポジトリを導入するのは時間の無駄です。 これにより、ある種の概念で十分な場所(たとえば、要求オブジェクトのカプセル化)に不要な抽象化が追加されます。



抽象化に焦点を合わせるのではなく、概念に焦点を当て、テストを可能な限り落とします。 結局、私のコントローラーはオブジェクト指向ではありません-それらは手続き型です(彼らによって提示された要件がこれを確認するように)。



Jimmy BogardはHeadsrpingのアーキテクトであり、AutoMapperの作成者であり、ASP.NET MVC in Actionの共著者です。 彼のブログでは、DDD、CQRS、分散システム、および関連するアーキテクチャと方法論に焦点を当てています。



All Articles