DDDProjector - Home
フレームワークと言っても、クラスライブラリとしてではなく、C# ソリューション (を作成するツール) として提供します。なので、フレームワークのソースコードを直接カスタマイズすることが可能です。
サンプルアプリではデータストアに SQL CE 4.0、プレゼンテーション技術に Windows フォームを使用していましたが、このフレームワークでは特定のデータストア・プレゼンテーション技術には依存していません。
ツールを起動すると、ソリューション名や各プロジェクト名・名前空間の入力フォームが表示されます。
各フィールドを入力後、作成ボタンを押すと保存先選択ダイアログが出てきます。保存先を決定するとそこにソリューションが作成されます。
なお、入力値の妥当性検証は一切行っていないので注意してください。
これから少しずつ解説記事を公開していこうと思います。
ただ、プロジェクトのテンプレートは作成できてもソリューションのテンプレートは作成できないっぽいので、別の形を考えています。(複数プロジェクトを一つのテンプレートにすることはできるみたいですが、ソリューションフォルダの自由度が低いので。)
Sandcastle で作った、アプリケーションアーキテクチャに関する説明ドキュメントも同梱してあります。
# ユニットテストは用意していません。
DDD サンプル - ToDoList - Home
- 列挙型のプロパティをマッピングできない
- クエリー式で、列挙値が扱えない (int として扱わなければいけない)
- クエリー式で、エンティティを直接比較できない (主キー等のプロパティを比較しなければいけない)
- コレクションの順序を保存できない (順序を自分で管理する必要がある)
- 関連オブジェクトの読み込みが非透過的
- 関連オブジェクトの遅延読み込みが使いづらい (コレクションだけ遅延読み込みにするとかできない。そのためプロキシ作成を容認しなければいけない)
- NHibernate の cascade=all-delete-orphan に相当する機能がない (集約内の子エンティティの削除はパーシステンスレイヤへ明示的に指示せずにできるべき)
Entity Framework 4.1 は試してないけど、この辺はそのままっぽい感じ
2番目のはロジックで弾きだした値を列挙型で扱おうとしてダメだった
最後のだけは何とかならんものか…
ファクトリだと、集約ルートの生成とそれ以外のエンティティの生成はアトミックに扱えないけどビルダならアトミックに扱えますし、構築中は不正な状態も扱えます。
また、構築に関するロジックがエンティティに一切なくなります。
ビルダは特に、UI への入力直後に入力チェックする場合や、ウィザード形式で入力を進めていく場合なんかに本領発揮します。
Person.cs
public sealed partial class Person
{
private Person(string name, IList<Address> addresses)
{
_name = name;
_addresses = addresses;
}
private string _name;
private readonly IList<Address> _addresses;
public string Name
{
get { return _name; }
}
public IEnumerable<Address> Addresses
{
get { return _addresses; }
}
}
Address.cs
public sealed class partial Address
{
...
}
Person.Builder.cs
partial class Person
{
public sealed class Builder
{
[Flags]
public enum States
{
Valid = 0,
NameIsEmpty = 1,
NameIsTooLong = 2,
AddressListIsEmpty = 4,
AddressListHasError = 8,
}
public static readonly int MaxNameLength = 20;
public Builder()
{
Name = null;
AddressBuilders = new List<Address.Builder>();
}
public Builder(Person target)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
Name = target.Name;
AddressBuilders = target._addresses.Select(address => new Address.Builder(address)).ToList();
}
private readonly Person _target;
public string Name;
public readonly List<Address.Builder> AddressBuilders;
public Address.Builder AddAddress()
{
var addressBuilder = new Address.Builder();
AddressBuilders.Add(addressBuilder);
return addressBuilder;
}
public void RemoveAddress(int index)
{
AddressBuilders.RemoveAt(index);
}
public States Validate()
{
return (ValidateName() | ValidateAddresses());
}
public States ValidateName()
{
if (string.IsNullOrWhitespace(Name))
{
return States.NameIsEmpty;
}
if (MaxNameLength < Name.Length)
{
return States.NameIsTooLong;
}
return States.Valid;
}
public States ValidateAddresses()
{
States result = States.Valid;
if (AddressBuilders.Length == 0)
{
result |= States.AddressListIsEmpty;
}
foreach (var addressBuilder in AddressBuilders)
{
if (addressBuilder.Validate() != Address.Builder.States.Valid)
{
result |= States.AddressListHasError;
break;
}
}
return result;
}
public Person Build()
{
if (Validate() != States.Valid)
{
throw new InvalidOperationException();
}
var addresses = AddressBuilders.Select(builder => builder.Build()).ToList();
if (_target == null)
{
return new Person(Name, addresses);
}
else
{
_target._name = Name;
_target._addresses.Clear();
foreach (var address in addresses)
{
_target._addresses.Add(address);
}
return _target;
}
}
}
}
Address.Builder.cs
partial class Address
{
public sealed class Builder
{
[Flags]
public enum States
{
...
}
internal AddressBuilder() { ... }
public States Validate() { ... }
internal Address Build() { ... }
}
}
まだ色々思案中でこれが本当に良い案なのかどうか確信は持ててないのですが。。。
[関連書籍]
ついにエリック・エヴァンスの "Domain-Driven Design" の日本語版が出版されますよ