私はあなたの2番目の質問にまず答えるつもりです。
はい、viewmodelsはコントローラと緊密に結合する必要があります。しかし、ViewModelFactoryは必要ありません。 AutoMapperやValueInjecterのようなものは、ドメインProductをProductViewModelに変換するのに十分なはずです。
あなたの最初の質問については、あなたのドメインのProduct Factoryをあなたのコントローラと分離しておくべきです。あなたが使用できるアプローチはいくつかあります。メソッドの引数としてスカラー値(例えば、string、boolなど)、その他のプリミティブ、純粋な.NETタイプのみをとるファクトリメソッドを作成する方法があります。
その後、コントローラがviewmodelからファクトリメソッドにスカラーを渡すことができます。これは疎結合しており、非常に粘着的です。
例えば:
[HttpPost]
public ActionResult CreateProduct(ProductViewModel model)
{
if (ModelState.IsValid)
{
//assuming product factory is constructor-injected
var domainProduct = _productFactory.BuildProduct(
model.Name, model.Price, model.Description);
//... eventually return a result
}
return View(model);
}
別のアプローチは、ドメインオブジェクトにviewmodelプロパティを直接渡すためのメソッドを配置することですが、この方法では、プロパティセッターを非公開にすることをお勧めします。
[HttpPost]
public ActionResult CreateProduct(ProductViewModel model)
{
if (ModelState.IsValid)
{
//assuming no product factory
var domainProduct = new Domain.Product();
domainProduct.SetName(model.Name);
domainProduct.SetPrice(model.Price);
domainProduct.SetDescription(model.Description);
//... eventually return a result
}
return View(model);
}
あまり冗長ではなく、ドメイン層にオブジェクトを作成するので、最初のオプションを優先します。ただし、MVCレイヤとドメインレイヤの間でビューモデルタイプを共有していないため、両方が緩やかに結合されています。代わりに上位レイヤー(MVC)がドメインレイヤーに依存していますが、ドメインレイヤーにはすべてのMVCに関する懸念がありません。
最初の2つのコメントへの返信
第2のコメントの最初の再検証:それは必ずしも検証を強制する製品工場の責任である必要はありませんが、ドメインでビジネスルールを実施する場合は、工場で以下 。例えば、製品ファクトリは、上記のSetXyzPropertyメソッドと同様に、プロダクトをインスタンス化し、次にビルド操作をエンティティ上のメソッドに委譲することができます(違いは、これらのメソッドは、内部 code> public )。この場合、それ自体の検証を強制するのは製品エンティティの責任です。
バリデーションを強制するために例外をスローすると、それらはファクトリとコントローラにバブルアップします。これは私が一般的にやろうとしていることです。ビジネスルールがコントローラーにバブルすると、MVCに検証ルールがないことを意味し、ModelState.IsValidはfalseでなければなりません。また、ファクトリからメッセージを戻すことを心配する必要もなくなり、ビジネスルール違反は例外の形で発生します。
あなたの最初のコメントについては、はい:MVCはドメインに依存しますが、逆もありません。あなたがファクトモードにビューモデルを渡したければ、あなたのドメインはviewmodelクラスがあるlib(MVCでなければならない)に依存しているでしょう。ファクトリメソッドの引数やファクトリメソッドのオーバーロードが爆発する可能性があります。これが起きた場合は、工場に頼るよりも、より細かい方法をエンティティ自体に公開するほうが良いかもしれません。
たとえば、フォーム全体を通過することなく、ユーザーがすばやくクリックして製品の名前または価格だけを変更できるフォームがあるとします。そのアクションは、フルブラウザのPOSTではなく、JSONを使用してajaxで実行することさえできます。コントローラがそれを処理するとき、 productFactory.RePriceOrRename(int productId、object priceOrName)
の代わりに myProduct.SetPriceOrName(object priceOrName)
を呼び出す方が簡単かもしれません。
質問の回答への返信
他の人は意見が異なるかもしれませんが、私の場合、ビジネスドメインは検証APIを公開すべきではありません。これは、エンティティに対してIsValidPriceメソッドを持つことができないと言っているわけではありません。しかし、私は公開APIの一部として公開されるべきではないと思います。次の点を考慮してください。
namespace NinetyNineCentStore.Domain
{
public class Product
{
public decimal Price { get; protected set; }
public void SetPrice(decimal price)
{
ValidatePrice(price);
Price = price;
}
internal static bool IsPriceValid(decimal price)
{
return IsPriceAtLeast99Cents(price)
&& IsPriceAtMostNineteen99(price)
&& DoesPriceEndIn99Cents(price);
}
private static bool IsPriceAtLeast99Cents(decimal price)
{
return (price >= 0.99m);
}
private static bool IsPriceAtMostNineteen99(decimal price)
{
return (price <= 19.99m);
}
private static bool DoesPriceEndIn99Cents(decimal price)
{
return (price % 1 == 99);
}
private static void ValidatePrice(decimal price)
{
if (!IsPriceAtLeast99Cents(price))
throw new InvalidOperationException(
"Product price must be at least 99 cents.");
if (!IsPriceAtMostNineteen99(price))
throw new InvalidOperationException(
"Product price must be no greater than 19.99.");
if (!DoesPriceEndIn99Cents(price))
throw new InvalidOperationException(
"Product price must end with 99 cents.");
}
}
}
上記は、APIに公開することなく、エンティティに対する検証をカプセル化します。あなたの工場は依然として内部IsPriceValid
を呼び出すことができますが、小さなビジネスルールのすべての順列に関心を持つ必要はありません。クライアント(内部またはパブリック)がルールに違反しようとすると、例外がスローされます。
このパターンは過度のように思えるかもしれませんが、エンティティに複数のプロパティを含むビジネスルールを検討してください。たとえば、 Product.IsOnSale == true
のときに DoesPriceEndIn99Cents
ルールを破ることができます。すでにValidatePriceがカプセル化されているため、新しい検証APIメソッドを公開することなくそのルールに対応できます。