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

Meeting comment like for removing must exist.

Error message

Meeting comment like for removing must exist.

What it means

Thrown by RemoveMeetingCommentLikeCommandHandler when _meetingMemberCommentLikesRepository.GetAsync(memberId, commentId) returns null. A like can only be removed if it exists; a missing like record aborts the command with InvalidCommandException (HTTP 400).

Source

Thrown at src/Modules/Meetings/Application/MeetingComments/RemoveMeetingCommentLike/RemoveMeetingCommentLikeCommandHandler.cs:25

namespace CompanyName.MyMeetings.Modules.Meetings.Application.MeetingComments.RemoveMeetingCommentLike
{
    internal class RemoveMeetingCommentLikeCommandHandler : ICommandHandler<RemoveMeetingCommentLikeCommand>
    {
        private readonly IMeetingMemberCommentLikesRepository _meetingMemberCommentLikesRepository;
        private readonly IMemberContext _memberContext;

        internal RemoveMeetingCommentLikeCommandHandler(IMeetingMemberCommentLikesRepository meetingMemberCommentLikesRepository, IMemberContext memberContext)
        {
            _meetingMemberCommentLikesRepository = meetingMemberCommentLikesRepository;
            _memberContext = memberContext;
        }

        public async Task Handle(RemoveMeetingCommentLikeCommand command, CancellationToken cancellationToken)
        {
            var commentLike = await _meetingMemberCommentLikesRepository.GetAsync(_memberContext.MemberId, new MeetingCommentId(command.MeetingCommentId));
            if (commentLike == null)
            {
                throw new InvalidCommandException(["Meeting comment like for removing must exist."]);
            }

            commentLike.Remove();

            _meetingMemberCommentLikesRepository.Remove(commentLike);
        }
    }
}

View on GitHub (pinned to 91c8ef24b4)

Solutions

  1. Make unlike idempotent: treat a missing like as success rather than an error.
  2. Pre-check the like exists via a query before dispatching remove.
  3. Synchronize client state so a second unlike is not sent.
  4. Map InvalidCommandException to 400/404 at the API boundary.

Example fix

// before
await _commandDispatcher.SendAsync(new RemoveMeetingCommentLikeCommand(commentId));

// after
var liked = await _meetingQueries.IsLikedByMemberAsync(commentId, memberId);
if (!liked) return NoContent(); // nothing to remove
await _commandDispatcher.SendAsync(new RemoveMeetingCommentLikeCommand(commentId));
Defensive patterns

Strategy: validation

Validate before calling

var liked = await _meetingQueries.IsLikedByMemberAsync(commentId, memberId);
if (!liked) return NoContent(); // nothing to remove
await _commandDispatcher.SendAsync(new RemoveMeetingCommentLikeCommand(commentId));

Type guard

public static bool IsValidUnlikeCommand(RemoveMeetingCommentLikeCommand c) =>
    c.MeetingCommentId != Guid.Empty;

Try / catch

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

Prevention

When it happens

Trigger: Dispatching RemoveMeetingCommentLikeCommand for a like that does not exist: the member never liked the comment, already unliked it, or the comment/like was removed.

Common situations: User toggles like off twice rapidly; client optimistic state out of sync; unlike after a server-side cascade removed likes.

Related errors


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