web-dev-qa-db-ja.com

DDD:単純なサンプルアプリケーションドメインで集約ルートを識別する

私はDDDについて読み込もうとしていますが、集約されたルートを特定するために少し苦労しています。ゲーム内でプレイヤーを異なるチームに分割するための本当にシンプルなアプリを書きました。

だから私のエンティティは次のようなものです:

ゲームエンティティ:

public class Game : DomainEntityBase, IDomainEntity
{
    private List<Team> teams = new List<Team>();

    private List<Player> players = new List<Player>();

    private int teamSize;

    public Game(
        string gameName,
        int teamSize,
        IEnumerable<Player> players) : base(Guid.NewGuid())
    {
        this.teamSize = teamSize;

        this.players = players.ToList();
    }

    public string GameName { get; private set; }

    public ReadOnlyCollection<Team> Teams => teams.AsReadOnly();

    public void SplitPlayersToTeams()
    {
        if (players.Count() % 2 != 0)
        {
            throw new NotSupportedException("Only equally dividable teams are supported");
        }

        var teamCount = players.Count / teamSize;

        var playersPerTeam = players.Count / teamCount;

        SetPlayersToTeam(teamCount, playersPerTeam);
    }

    private void SetPlayersToTeam(int teamCount, int playersPerTeam)
    {
        var rnd = new Random();

        for (var i = 0; i < teamCount; i++)
        {
            var team = new Team(i.ToString());

            while (team.Players.Count != playersPerTeam)
            {
                var randomIndex = rnd.Next(players.Count);

                var player = players[randomIndex];

                if (!team.Players.Contains(player))
                {
                    player.SetTeam(team);
                    team.AddPlayer(player);
                }
            }

            teams.Add(team);
        }
    }
}

チームエンティティ:

public class Team : DomainEntityBase, IDomainEntity
{
    private List<Player> players = new List<Player>();

    public Team(
        string teamIdentifier) : base(Guid.NewGuid())
    {
        TeamIdentifier = teamIdentifier;
    }

    public string TeamIdentifier { get; }

    public ReadOnlyCollection<Player> Players => players.AsReadOnly();

    public void AddPlayer(Player player)
    {
        players.Add(player);
    }
}

プレーヤーエンティティ:

public class Player : DomainEntityBase, IDomainEntity
{
    public Player(
        string nickName) : base(Guid.NewGuid())
    {
        Nickname = nickName;
    }

    public string Nickname { get; private set; }

    public Team Team { get; private set; }

    public void SetTeam(Team team)
    {
        Team = team;
    }
}

今、私は最初に、ゲームが全体的な根になると考えていました。それはある意味で理にかなっています。しかしその後、すべてのゲームに新しいプレーヤーを追加する必要がないように、プレーヤーを個別に永続化したい場合はどうなるのでしょうか。後で再利用できるチームがある場合に、チームを個別に保持したい場合はどうなりますか?私がゲームを永続化するのは、たとえば永続化からゲームの履歴をロードするためです。

問題は、すべての集約ルートには独自のリポジトリがあるはずなので、私が集約ルートの上にリストしたすべてのオブジェクトには独自のリポジトリがあるのでしょうか。

前もって感謝します。

6
tjugg

集約ルートとして何を選択すべきかは、アプリケーションのビジネスルールに大きく依存するという決定。たとえば、2つのエンティティがあり、AとBが非常に依存している場合、つまり、エンティティBの一部の操作では、エンティティAの変更が必要であり、AとBは同じ集約ルートの下にある必要があります。つまり、2つのエンティティ間で一貫性が必要な場合、それらは同じ集約ルートを持つことができます。

あなたの場合、何を集合体として選択するかはビジネスルールにも依存します。

ビジネスルールに従うことを検討する場合:

ビジネスルールの前提1

  1. ゲームでは、チームは実行時に作成され、ゲーム終了後はチームの存在はありません。
  2. ゲームでは、システム内のすべてのユーザーに対してランタイムでプレーヤーも作成され、ゲーム終了後もプレーヤーは存在しません
  3. ここでは、実行時にプレーヤーとチームが作成されるオンラインLUDOゲームの例を検討できます。

ここで上記のルールを考慮すると、ここではゲームのルートのみがOKであるように、プレーヤーとチームは高度に相関/結合しているようです。

ビジネスルールに従うことを検討する場合:

ビジネスルールの前提条件2

  1. チームは、より現実的なシナリオであるGAMEから独立して存在できます。
  2. プレイヤーも独立して存在します。
  3. プレイヤーは異なるチームに割り当てることができます。
  4. チームはさまざまなゲームをプレイできます。

上記のルールを考えると、ゲーム、プレーヤー、チームは別々の集合ルートとして存在する必要があるようです。

集計を決定するための1つの異なるアプローチは、イベントストーミングであり、最初に真の単一のソース、つまり発生したことを記述します。

  1. GameStarted
  2. TeamCreated
  3. PlayerAddedToTeamなど。

これらのイベントを定義すると、ビジネスも明確になります。

次に、コマンドがこれらのイベントを引き起こす可能性のあるものを記述し、フローの記述を続けます。

しばらくすると、どのコマンドとイベントが単一のポイントから発生し、集約として選択できるかが明らかになります。

ただし、この後、さらに集計を解除する必要があるかどうかについては、ビジネスルールと照合する必要があることに注意してください。

これでいくつかの疑問が解消されることを願っています。

疑問がある場合はコメントしてください。

よろしくお願いします。

8
Sahil Aggarwal

すべての集約ルートは独自のリポジトリを持つ必要があるため、私がリストしたすべてのオブジェクトは集約ルートであり、独自のリポジトリがありますか?

これは、より一般的なケースの特定のケースです。集計の境界は、最初に一貫性の境界です。すべての不変条件が常に当てはまる境界。サンプルアプリには多くの動作がないため、正確に伝えるのは困難ですが、各チームおよび各プレーヤー内のすべての不変条件がすぐに一貫している必要があるとは思いません。

したがって、これらの3つのオブジェクトを集約ルートとして持っている場合は可能性がありますが妥当です。

3
Zapadlo

現時点では、ゲームにプレーヤーとチームが含まれているようです。したがって、これは集約ルートです。

おそらくこれは一貫性を維持します。つまり、プレイヤーをチームから削除すると、そのプレイヤーはそのチームのゲームに参加しなくなりますか?

ありそうなことですが、そのような整合性ルールを適用する必要がない場合。オブジェクトの代わりにゲームにチームIDとプレーヤーIDを含めることができます。 GetPlayersByTeamIdAndDate()などのメソッドをリポジトリに追加します。

または、おそらくより大きな集約ルートが必要です。シーズンまたはリーグ。複数のゲームがあり、一貫性のルールがより意味があり、実施できます。つまり、プレーヤーは1シーズンに1つのチームにのみ参加できます。

2
Ewan

上記の良い答えが多すぎます。ただのメモ、そして私が間違っているなら私を訂正してください、しかしあなたはそれらをどのように保存するかに基づいて集約を定義しているようです。 DDDでは反対のことを行い、使用方法に基づいて集計を定義します。他の多くの人が言うように、あなたのビジネスルールはあなたのデザインを導きます。

1
Alex Favieres