作業ユニットのスコープ

architecture ninject repository-pattern service-layer unit-of-work
作業ユニットのスコープ

フロントエンドにウェブフォームを使用し、管理コンソールにmvcを使用するソリューションがあります。

両方のUIはNinjectを介してサービスレイヤーを消費し、微妙ではあるがかなり重要な問題の解決に問題があります。

文字列検索用語に基づいてコースのリストを返すCourseServiceがあるとします-サービスは検索結果を返しますが、管理情報の目的で、行われた検索とその用語に一致するコースの数も記録する必要があります。

リクエストの最後に、ボタンクリックイベントなどのページメソッドで、UIによって作業単位がコミットされるというアイデアから始めました。 同じことがコントローラーにも当てはまります。

ここでの問題は、検索をログに記録するために、UIの開発者に依存して作業単位でCommit()を呼び出すことです。 UI開発者はcommitを呼び出さなくても問題なく続行でき、結果が返されますが、検索はログに記録されません。 これにより、サービスレイヤーに作業単位のスコープを制御させる決定に至ります。 Ninjectは作業単位をサービスレイヤーとリポジトリ実装の両方に自動的に渡します。これは、リクエストスコープごとに作成するようにninjectに指示したインスタンスと事実上同じです。

レイヤーの記述方法の例を次に示します…​

public class CourseService
{
    private readonly ICourseRepository _repo;

    public CourseService(ICourseRepository repo)
    {
        _repo = repo;
    }

    public IEnumerable FindCoursesBy(string searchTerm)
    {
        var courses = _repo.FindBy(searchTerm);
        var log = string.format("search for '{1}' returned {0} courses",courses.Count(),searchTerm);
        _repo.LogCourseSearch(log);
        //IMO the service layer should be calling Commit() on IUnitOfWork here...
        return courses;
    }
}

public class EFCourseRepository : ICourseRepository
{
    private readonly ObjectContext _context;

    public EFCourseRepository(IUnitOfWork unitOfWork)
    {
        _context = (ObjectContext)unitOfWork;
    }

    public IEnumerable FindBy(string text)
    {
        var qry = from c in _context.CreateObjectSet()
            where c.CourseName.Contains(text)
            select new Course()
            {
                Id = c.CourseId,
                Name = c.CourseName
            };
        return qry.AsEnumerable();
    }

    public Course Register(string courseName)
    {
        var c = new tblCourse()
        {
            CourseName = courseName;
        };
        _context.AddObject(c);
        //the repository needs to call SaveChanges to get the primary key of the newly created entry in tblCourse...
        var createdCourse = new Course()
        {
            Id = c.CourseId,
            Name = c.CourseName;
        };
        return createdCourse;
    }
}

public class EFUnitOfWork : ObjectContext, IUnitOfWork
{
    public EFUnitOfWork(string connectionString) : base(connectionString)
    {}

    public void Commit()
    {
        SaveChanges();
    }

    public object Context
    {
        get { return this; }
    }
}

上記のコメントでは、変更をコミットする必要があると感じる場所を見ることができますが、サービス層とリポジトリ実装の両方がトランザクションの範囲を制御できるようにすることで、より大きな問題を見落としているかもしれません。

これに加えて、リポジトリが新しいオブジェクトを保存し、新しく指定されたプライマリキーをそのままにして返す必要がある場合、オブジェクトが返された後にUIからCommitを呼び出している場合、これは起こりません。 そのため、リポジトリは時々作業単位を管理する必要があります。

私のアプローチで差し迫った問題を見ることができますか?

  2  0


ベストアンサー

それがすべて、作業単位の「境界」です。 論理演算の境界とは何ですか? UI-コードビハインド/コントローラーまたはサービスレイヤーですか? 境界とは、だれが作業単位とは何かを定義するということですか? UI開発者は、単一の作業単位への複数のサービス呼び出しを振り分けるのですか、それとも単一の作業単位をラップするサービス操作を公開するのはサービス開発者の責任ですか? これらの質問は、作業ユニットの「コミット」を呼び出す必要がある場合に即座に答えを提供する必要があります。

論理操作の境界がUI開発者によって定義されている場合、この方法で境界を定義することはできません。 UI開発者は、メソッドを呼び出す前にコミットされていない変更を行うことができますが、検索をログに記録すると、これらの変更を暗黙的にコミットします! そのような場合、Log操作は独自のコンテキスト/作業単位を使用する必要があり(さらに、現在のトランザクションの外部で実行する必要があります)、呼び出しごとに新しいUoWインスタンスを作成する個別のninject設定が必要になります。 論理操作の境界がサービス内にある場合、UI開発者に作業単位を公開するべきではありません-彼はアクティブなUoWインスタンスと対話できないはずです。

私の実装で気に入らないのは、作業単位で「コミット」を呼び出す「登録」です。 再び境界はどこにありますか? リポジトリ操作は自己完結型の作業単位ですか? そのような場合、なぜサービス層があるのですか? 単一の作業単位に複数の呪いを登録したい場合、またはコース登録をより大きな作業単位の一部にしたい場合はどうなりますか? `Commit`を呼び出すのはサービス層の責任です。 この全体はおそらく、リポジトリがエンティティをカスタムタイプ/ DTOに投影するという考えに由来するものです。リポジトリに対する責任が大きすぎ、複雑すぎるように見えます。 特に、POCO(EFv4.x)を使用できる場合。

最後に言及すること-サービス操作を作業単位の境界として作成すると、リクエストごとのインスタンス化では不十分な状況を見つけることができます。 内部で複数の作業単位を実行するWeb要求を作成できます。

最後に。 あなたはUI開発者の責任を心配しています-UI開発者はあなたの実装を心配することができます-UI開発者が複数のサービス操作を並行して実行することを決定した場合どうなりますか(EFコンテキストはスレッドセーフではありませんが、あなたは1つしか持っていませんリクエスト処理全体)? ですから、それはあなたとUI開発者との間のコミュニケーション(または非常に優れたドキュメント)にすべて関係しています。

2


タイトルとURLをコピーしました