8

私はドメイン駆動設計に不慣れで、いくつかの概念について疑問があります (これが質問するのに適切な場所であることを願っています)。DDD では貧血モデルを
避けるべきであることを私は知っています。 ユーザーを表すクラスを持つような状況を想像します (Java のような構文を使用):

class User{
    String username
    List<User> friends
}

では、友達を追加するメソッドが必要でしょうか?

class User{
    void friendship(User friend)
}

または、それを行うためにサービスを使用する必要がありますか?

class UserService{
     void friendship(User user, User user2)
}
4

7 に答える 7

5

私は次のようなものを使用します

public sealed class Username : IEquatable<Username> { /* string wrap here */ }
public class User
{
    private readonly Username _username;
    private readonly HashSet<Username> _friends;
    public User(Username username)
    {
        if (null == username) throw new ArgumentNullException("username");
        _username = username;
        _friends = new HashSet<Username>();
    }

    public Username Name { get {return _username; } }
    public void Befriend(User user)
    {
        if (null == user) throw new ArgumentNullException("user");
        _friends.Add(user.Name);
    }

    public bool IsFriendsOf(User user)
    {
        if (null == user) throw new ArgumentNullException("user");
        return _friends.Contains(user.Name);
    }
}

Demeterの法則に従って、コレクションは User によって公開されないことに注意してください。あなたが本当にそれらを必要とする場合に備えて、私はIEnumerable<Username>友人のために公開します.

さらに、DDD では、すべてのクエリとコマンドをユビキタス言語の一部にする必要があります(これが、Befriend代わりに を使用した理由ですAddFriend)。

ただし、これは少しCRUDに見えて DDD を必要としません。ドメインを理解するのに(少なくとも)ドメインの専門家が必要ない場合は、DDD はまったく必要ありません。DDD が必要ない場合、プロジェクトで最もコストのかかるエラーになります。

編集
ドメインの専門家が「友情は常に相互的である」と述べているとしましょう ( guillaume31の提案によると):冪等コマンドをモデル化することで、そのようなビジネス ルールを非常に簡単に保証できます。Befriendコマンドは次のようになります。

public void Befriend(User user)
{
    if (null == user) throw new ArgumentNullException("user");
    if(_friends.Add(user.Name))
    {
        user.Befriend(this);
    }
}

このようなプロパティを使用して冪等コマンドをいつでもモデル化できますが、引数と内部状態が必要なすべてを提供することを確認するために、もう少し分析が必要になる場合があります。

于 2013-04-03T08:19:12.643 に答える
4

おもう; 友情は集約ルートそのものです。アプリケーション サービス内で直接作成することも、作成をドメイン サービスに委任することもできます。

ユーザー固有の検証が必要な場合、ドメイン サービスはユーザー集計に検証を求めることができます。または、両方のユーザー集約をフレンドシップ集約クリエーター/コンストラクターにディスパッチします。

次に、フレンドリ リポジトリは、特定のユーザーのフレンドのリストを簡単に返すことができます。

友情の集合体でさえ、豊富なモデルや動作を持っていません。個別の整合性境界があります。

さらに、イベントソーシングを使用する場合。friendsCreated イベントをリッスンできます。両方のユーザーに状況を通知します。

于 2015-07-14T10:43:45.957 に答える
2

A weak or anaemic domain model just means your "domain objects" are DTOs, with no behaviour. What you've basically got is the Transaction Script pattern, where DTOs are loaded, modified and saved again. CRUD, to put it another way. This is fine for a lot of apps, which don't have complex enough rules to benefit from a DDD-approach.

Domain objects should encapsulate behaviour. That is their essence. Any public state (it is possible to have no public getters or setters) should be readonly, and if you want to mutate state, you call a method that relates to a use-case/business requirement.

All your "domain logic" should be in these classes. Domain logic is just a fancy name to describe the rules and operating parameters of your chosen domain. Be it banking, retail, HR etc. When your domain experts explain the "Pay By Card" user-case and tell you "We can't open the till until the PDC machine has contacted the bank.", that's a business rule/invariant/piece of domain logic.

You would normally assemble you domain objects (made up of Entities and Value Objects) into Aggregates, which define a boundary within which a given set of rules must be satisfied. The Entity that is the root of this domain object graph is known as the Aggregate Root, and it is only to Aggregate Roots that other objects may hold references. In your case, User is an Entity, and as it's the only object in the Aggregate it is also the Aggregate Root. So for example:

public class User // Entity and also the Aggregate Root 
{
    private readonly IList<Friendship> _friends = new List<Friendship>();

    public void Befriend(User user)
    {
        _friends.Add(new Friendship(/* Date? */, user));
    }

    public class Friendship // Entity
    {
        // ... Date?
        private User _friend { get; private set; }

        public Friendship(/* Date? */, User friend)
        {
            _friend = friend;
        }
    }
}

This isn't a great example really because in theory you'd need to call this for each of two friends in a pair, but any transaction should only carry out one operation, not two. In this case, you introduce the concept of Process Managers. These are yet more objects that deal with the coordination of what is essentially a long-running transaction (where two friends become friends with each other). You'd probably create a Friendship (as an Aggregate Root) and it's creation would spawn some sort of event-driven process where the friends involved are loaded, befriended, and saved.

于 2014-03-19T13:44:03.007 に答える