kgrzybek/modular-monolith-with-ddd · error · InvalidCommandException

Meeting for adding comment must exist.

Error message

Meeting for adding comment must exist.

What it means

Thrown by AddMeetingCommentCommandHandler when _meetingRepository.GetByIdAsync returns null for the supplied MeetingId. Commenting requires a target meeting aggregate; if none is found the command is rejected as InvalidCommandException (HTTP 400). The meeting is also used downstream to resolve the meeting group and commenting configuration.

Solutions

  1. Validate the MeetingId is a non-empty, existing meeting before allowing the user to comment.
  2. Refresh the client's meeting list so stale ids are not submitted.
  3. In the controller, check meeting existence via a query and return 404 instead of dispatching.
  4. Handle InvalidCommandException at the API boundary and return 400/404.

Example fix

// before
var commentId = await _commandDispatcher.SendAsync(new AddMeetingCommentCommand(meetingId, text));

// after
var meeting = await _meetingQueries.GetMeetingAsync(meetingId);
if (meeting is null) return NotFound("Meeting not found.");
var commentId = await _commandDispatcher.SendAsync(new AddMeetingCommentCommand(meetingId, text));
Defensive patterns

Strategy: validation

Validate before calling

var meeting = await _meetingQueries.GetMeetingAsync(meetingId);
if (meeting is null) return NotFound("Meeting not found.");
var id = await _commandDispatcher.SendAsync(new AddMeetingCommentCommand(meetingId, text));

Type guard

public static bool IsValidMeetingId(AddMeetingCommentCommand c) =>
    c.MeetingId != Guid.Empty && !string.IsNullOrWhiteSpace(c.Comment);

Try / catch

try { await _commandDispatcher.SendAsync(cmd); }
catch (InvalidCommandException ex) when (ex.Errors.Any(m => m.Contains("must exist")))
{ return NotFound(new { errors = ex.Errors }); }

Prevention

When it happens

Trigger: Posting AddMeetingCommentCommand with a MeetingId that does not correspond to any meeting: wrong/empty Guid, meeting not yet created, meeting in a different tenant/partition, or a stale client reference.

Common situations: Mobile/web client retains a meeting link after the meeting was deleted; concurrency where the comment is submitted during creation; tests using a fabricated id.

Related errors


AI-assisted analysis of kgrzybek/modular-monolith-with-ddd@91c8ef24b4 (2026-08-13). Data as JSON: /api/errors/9f8b9f42392e9b44. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Meetings/Application/MeetingComments/AddMeetingComment/AddMeetingCommentCommandHandler.cs:38

            IMeetingRepository meetingRepository,
            IMeetingCommentRepository meetingCommentRepository,
            IMeetingGroupRepository meetingGroupRepository,
            IMeetingCommentingConfigurationRepository meetingCommentingConfigurationRepository,
            IMemberContext memberContext)
        {
            _meetingGroupRepository = meetingGroupRepository;
            _meetingRepository = meetingRepository;
            _meetingCommentingConfigurationRepository = meetingCommentingConfigurationRepository;
            _meetingCommentRepository = meetingCommentRepository;
            _memberContext = memberContext;
        }

        public async Task<Guid> Handle(AddMeetingCommentCommand command, CancellationToken cancellationToken)
        {
            var meeting = await _meetingRepository.GetByIdAsync(new MeetingId(command.MeetingId));
            if (meeting == null)
            {
                throw new InvalidCommandException(["Meeting for adding comment must exist."]);
            }

            var meetingGroup = await _meetingGroupRepository.GetByIdAsync(meeting.GetMeetingGroupId());

            var meetingCommentingConfiguration = await _meetingCommentingConfigurationRepository.GetByMeetingIdAsync(new MeetingId(command.MeetingId));

            var meetingComment = meeting.AddComment(_memberContext.MemberId, command.Comment, meetingGroup, meetingCommentingConfiguration);

            await _meetingCommentRepository.AddAsync(meetingComment);

            return meetingComment.Id.Value;
        }
    }
}

View on GitHub (pinned to 91c8ef24b4)