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

6

我的想法是,这是关系数据库理论中被称为“弱实体”的一个例子。AFriendship可以仅通过友谊中涉及的两个 s 的标识符来标识User,但可以具有自己的属性,例如它是何时创建的以及它是什么类型的关系。

我会将其设为自己的实体,并可能将其隐藏在User对象暴露的外观后面:

class User {
    protected List<Friendship> _friendships { get; private set; }

    public IEnumerable<User> Friends {
        get { return _friendships.Select( /* get other user */ ); }
    }

    public void AddFriend(User otherUser) {
        // check to see if friendship exists
        // if not, create friendship
        // do other friendshippy things

        // make sure the other user knows about our friendship 
        // and gets to do its friendshippy things
        otherUser.AddFriend(this);
    }
}
于 2013-04-02T23:55:25.870 回答
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 法则,用户不会公开任何集合。如果您真的需要它们,我会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

我认为; 友谊本身就是聚合根。它可以直接在应用程序服务中创建,也可以委托给域服务创建。

如果用户特定的验证需要,域服务可以要求用户聚合进行验证。或者将两个用户聚合分派给友谊聚合创建者/构造者。

然后,友谊存储库可以轻松地返回给定用户的朋友列表。

即使是友谊聚合也没有丰富的模型或行为;它有一个单独的一致性边界。

此外,如果我们使用事件源;可以收听友谊创建的事件;并通知两个用户有关情况。

于 2015-07-14T10:43:45.957 回答
2

transferFunds()这与银行域中的问题类似——您应该在源帐户还是目标帐户上调用该方法?并且应该Account能够首先操纵另一个AccountBalance

当某些行为似乎不适合现有实体或操纵多个实体时,DDD 域服务会派上用场。这不会使您的域贫血,因为服务域的一部分,并且拥有有限数量的没有行为的实体并不意味着您的整个模型无论如何都是贫血的。

User如果友谊关系从一开始就是互惠的,那么将友谊创建委托给域服务而不是让一个人修改另一个User人的朋友列表可能更合乎逻辑。

否则,一种addFriend()方法将正常工作。

于 2013-04-04T07:47:58.777 回答
2

弱或贫血的域模型仅意味着您的“域对象”是 DTO,没有任何行为。您基本上得到的是事务脚本模式,其中 DTO 被加载、修改和再次保存。CRUD,换句话说。这对于很多应用程序来说都很好,因为它们没有足够复杂的规则来从 DDD 方法中受益。

域对象应该封装行为。这就是他们的本质。任何公共状态(可能没有公共 getter 或 setter)都应该是只读的,如果你想改变状态,你可以调用一个与用例/业务需求相关的方法。

您所有的“域逻辑”都应该在这些类中。域逻辑只是一个花哨的名称,用于描述您选择的域的规则和操作参数。无论是银行、零售、人力资源等。当您的领域专家解释“通过卡付款”用户案例并告诉您“在 PDC 机器与银行联系之前,我们无法打开收银台。”,这是一个业务规则/不变量/域逻辑。

您通常会将域对象(由实体和值对象组成)组装成聚合,聚合定义了必须满足给定规则集的边界。作为该域对象图的根的实体称为聚合根,只有聚合根才能让其他对象持有引用。在您的情况下,User是一个实体,因为它是聚合中的唯一对象,所以它也是聚合根。例如:

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;
        }
    }
}

这真的不是一个很好的例子,因为理论上你需要为一对中的两个朋友中的每一个调用这个,但是任何交易都应该只执行一个操作,而不是两个。在这种情况下,您将引入流程管理器的概念。这些还有更多的对象处理本质上是长期运行的事务(其中两个朋友成为彼此的朋友)的协调。您可能会创建一个友谊(作为聚合根),它的创建会产生某种事件驱动的过程,其中涉及的朋友被加载、结交和保存。

于 2014-03-19T13:44:03.007 回答
1

两个用户之间的友谊应该是一个单独的聚合(AR)。作为该 AR 的 ID,我建议使用用户的 ID。这样,您就可以拥有可以扩展/分区的低压小骨料。用户之间发送的消息也是如此。每条消息都是一个聚合。

这就是我们设计它的方式并且它是成功的。

祝你好运

于 2017-09-06T23:56:31.643 回答
0

考虑到一个用户可以拥有无​​限数量的友谊,我认为它不应该被建模为用户 AR 中的价值对象。AR 应该尽可能小,每次你想改变状态时加载 10k 的朋友并不理想。

我可以选择拥有FriendshipFriendRequest作为单独的根。AFriendRequest是一个单一方向的东西,包含一个 from_user_id 和一个 to_user_id。它可以被忽略、拒绝或接受。接受后,就建立了双向友谊。友谊可以通过物化视图(例如弹性搜索)搜索,并通过最终由用户配置文件更新事件更新的用户名进行增强。对于用户 UI,如果需要有条件的好友按钮或好友请求指示符,可以使用Friendship和/或.id 的 ID 维护链接表/搜索视图FriendRequest

于 2018-04-13T22:46:42.263 回答