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
- Make unlike idempotent: treat a missing like as success rather than an error.
- Pre-check the like exists via a query before dispatching remove.
- Synchronize client state so a second unlike is not sent.
- 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
- Make unlike idempotent: missing like is success, not an error.
- Keep client like-state synchronized to avoid sending duplicate unlikes.
- Guard empty Guid at the controller.
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
- To add like the comment must exist.
- Meeting for adding comment must exist.
- To create reply the comment must exist.
- Meeting comment for editing must exist.
- Meeting comment for removing must exist.
AI-assisted analysis of kgrzybek/modular-monolith-with-ddd@91c8ef24b4 (2026-08-13).
Data as JSON: /api/errors/9373e17709c76cfa.
Report an issue: GitHub.