#動機付け
お勉強のために.NETでDDDっぽく開発していたらドメイン層はF#で記述したほうが(C#より)より良いんじゃないかぁと思ったので投稿してみます。とんちんかんなこと書いてたらすいません。
以下、良いところ。
#値オブジェクトの作成が便利
値オブジェクトって不変だったり等価性だったり交換可能性がないとダメじゃないですか。
C#でこれを担保しようと思うと値オブジェクトの実装に記載されているValueObjectを継承するかオレオレでEqualsをオーバーライドしないといけないから実装が億劫になるんですよね。
上記リンクに載っているAddressを例にすると
public abstract class ValueObject
{
protected static bool EqualOperator(ValueObject left, ValueObject right)
{
if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null))
{
return false;
}
return ReferenceEquals(left, null) || left.Equals(right);
}
protected static bool NotEqualOperator(ValueObject left, ValueObject right)
{
return !(EqualOperator(left, right));
}
protected abstract IEnumerable<object> GetAtomicValues();
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType())
{
return false;
}
ValueObject other = (ValueObject)obj;
IEnumerator<object> thisValues = GetAtomicValues().GetEnumerator();
IEnumerator<object> otherValues = other.GetAtomicValues().GetEnumerator();
while (thisValues.MoveNext() && otherValues.MoveNext())
{
if (ReferenceEquals(thisValues.Current, null) ^
ReferenceEquals(otherValues.Current, null))
{
return false;
}
if (thisValues.Current != null &&
!thisValues.Current.Equals(otherValues.Current))
{
return false;
}
}
return !thisValues.MoveNext() && !otherValues.MoveNext();
}
public override int GetHashCode()
{
return GetAtomicValues()
.Select(x => x != null ? x.GetHashCode() : 0)
.Aggregate((x, y) => x ^ y);
}
// Other utilility methods
}
public class Address : ValueObject
{
public String Street { get; }
public String City { get; }
public String State { get; }
public String Country { get; }
public String ZipCode { get; }
private Address() { }
public Address(string street, string city, string state, string country, string zipcode)
{
Street = street;
City = city;
State = state;
Country = country;
ZipCode = zipcode;
}
protected override IEnumerable<object> GetAtomicValues()
{
// Using a yield return statement to return each element one at a time
yield return Street;
yield return City;
yield return State;
yield return Country;
yield return ZipCode;
}
}
これがF#だとレコードで書けるんですよ。
type Address =
{
Street: string
City: string
State: string
Country: string
ZipCode: string
}
これをC#側で見てみると
[CompilationMapping(SourceConstructFlags.RecordType)]
public sealed class Address : IEquatable<Address>, IStructuralEquatable, IComparable<Address>, IComparable, IStructuralComparable
{
public Address(string street, string city, string state, string country, string zipCode);
[CompilationMapping(SourceConstructFlags.Field, 0)]
public string Street { get; }
[CompilationMapping(SourceConstructFlags.Field, 1)]
public string City { get; }
[CompilationMapping(SourceConstructFlags.Field, 2)]
public string State { get; }
[CompilationMapping(SourceConstructFlags.Field, 3)]
public string Country { get; }
[CompilationMapping(SourceConstructFlags.Field, 4)]
public string ZipCode { get; }
[CompilerGenerated]
public sealed override int CompareTo(Address obj);
[CompilerGenerated]
public sealed override int CompareTo(object obj);
[CompilerGenerated]
public sealed override int CompareTo(object obj, IComparer comp);
[CompilerGenerated]
public sealed override bool Equals(object obj, IEqualityComparer comp);
[CompilerGenerated]
public sealed override bool Equals(Address obj);
[CompilerGenerated]
public sealed override bool Equals(object obj);
[CompilerGenerated]
public sealed override int GetHashCode(IEqualityComparer comp);
[CompilerGenerated]
public sealed override int GetHashCode();
[CompilerGenerated]
public override string ToString();
}
と等価性についてがっつり実装してくれてます。コンストラクタも自動で生成してくれてます。レコード型でもメソッドやプロパティを追加できるのでクラスのように使えます。
#ドメイン層は関数型言語と相性が良い(気がする)
ドメイン層には業務知識を記述するんですけどたいてい、「~が~したら~する」ってなるはずなので入力と出力がしっかりしている関数型言語で記述したほうがより良いのかなぁと思いました。特に便利だと思ったのがunitとignoreですね。戻り値がunitだと副作用があるとわかるしignoreが記述されていると設計が臭い気がします。
実装の教本として「リーダブルコード」や「新装版 リファクタリング 既存のコードを安全に改善する」を読んでるんですが、メソッドは副作用なく簡潔で一度に一つのことをやるべき旨が書かれているので、そう書きやすい関数型言語がおすすめです。
#全てがF#でなくて良い
データの入出力部や画面の入出力部のように例外処理やnullと付き合っていかないといけない場所はF#だと面倒くさいので、そこはC#なりVBで記述したほうが良いと思います。DIP(依存性逆転の原則)で作成しておけばドメイン層にはインターフェースだけ記述すれば良いわけですし。これは.NETの良いところですね。
#所感
もちろんC#が駄目って話ではないです。より良いって感じです。
F#だとだらだらと手続き的に書きづらいところが逆に良いところかなと思いました。