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

Meeting comment for removing must exist.

Error message

Meeting comment for removing must exist.

What it means

Thrown by RemoveMeetingCommentCommandHandler when GetByIdAsync returns null for the MeetingCommentId. Removal needs an existing comment (and uses it to resolve meeting and group), so a missing comment aborts with InvalidCommandException (HTTP 400).

Source

Thrown at src/Modules/Meetings/Application/MeetingComments/RemoveMeetingComment/RemoveMeetingCommentCommandHandler.cs:30

        private readonly IMeetingCommentRepository _meetingCommentRepository;
        private readonly IMeetingRepository _meetingRepository;
        private readonly IMeetingGroupRepository _meetingGroupRepository;
        private readonly IMemberContext _memberContext;

        internal RemoveMeetingCommentCommandHandler(IMeetingCommentRepository meetingCommentRepository, IMeetingRepository meetingRepository, IMeetingGroupRepository meetingGroupRepository, IMemberContext memberContext)
        {
            _meetingCommentRepository = meetingCommentRepository;
            _meetingRepository = meetingRepository;
            _meetingGroupRepository = meetingGroupRepository;
            _memberContext = memberContext;
        }

        public async Task Handle(RemoveMeetingCommentCommand command, CancellationToken cancellationToken)
        {
            var meetingComment = await _meetingCommentRepository.GetByIdAsync(new MeetingCommentId(command.MeetingCommentId));
            if (meetingComment == null)
            {
                throw new InvalidCommandException(["Meeting comment for removing must exist."]);
            }

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

            meetingComment.Remove(_memberContext.MemberId, meetingGroup, command.Reason);
        }
    }
}

View on GitHub (pinned to 91c8ef24b4)

Solutions

  1. Make remove idempotent on the client side: treat 'not found' as already removed.
  2. Pre-check existence and return 404/204 instead of dispatching a doomed command.
  3. Debounce/dedupe the remove button to avoid duplicate dispatches.
  4. Map InvalidCommandException to 400/404 at the API boundary.

Example fix

// before
await _commandDispatcher.SendAsync(new RemoveMeetingCommentCommand(commentId, reason));

// after
var comment = await _meetingQueries.GetCommentAsync(commentId);
if (comment is null) return NoContent(); // already removed
await _commandDispatcher.SendAsync(new RemoveMeetingCommentCommand(commentId, reason));
Defensive patterns

Strategy: validation

Validate before calling

var comment = await _meetingQueries.GetCommentAsync(commentId);
if (comment is null) return NoContent(); // already removed
await _commandDispatcher.SendAsync(new RemoveMeetingCommentCommand(commentId, reason));

Type guard

public static bool IsValidRemoveCommand(RemoveMeetingCommentCommand c) =>
    c.MeetingCommentId != Guid.Empty && !string.IsNullOrWhiteSpace(c.Reason);

Try / catch

try { await _commandDispatcher.SendAsync(cmd); }
catch (InvalidCommandException ex) when (ex.Errors.Any(m => m.Contains("must exist")))
{ return NoContent(); } // idempotent remove

Prevention

When it happens

Trigger: Dispatching RemoveMeetingCommentCommand with a comment id that does not exist: already removed, wrong id, or duplicate removal request.

Common situations: User double-submits a remove action; background job retries removal of an already-deleted comment; client shows a ghost comment.

Related errors


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