{"record":{"id":"acf70a260cba56b1","repo":"kgrzybek/modular-monolith-with-ddd","slug":"meeting-comment-for-removing-must-exist","errorCode":null,"errorMessage":"Meeting comment for removing must exist.","messagePattern":"Meeting comment for removing must exist\\.","errorType":"validation","errorClass":"InvalidCommandException","httpStatus":400,"severity":"error","filePath":"src/Modules/Meetings/Application/MeetingComments/RemoveMeetingComment/RemoveMeetingCommentCommandHandler.cs","lineNumber":30,"sourceCode":"        private readonly IMeetingCommentRepository _meetingCommentRepository;\r\n        private readonly IMeetingRepository _meetingRepository;\r\n        private readonly IMeetingGroupRepository _meetingGroupRepository;\r\n        private readonly IMemberContext _memberContext;\r\n\r\n        internal RemoveMeetingCommentCommandHandler(IMeetingCommentRepository meetingCommentRepository, IMeetingRepository meetingRepository, IMeetingGroupRepository meetingGroupRepository, IMemberContext memberContext)\r\n        {\r\n            _meetingCommentRepository = meetingCommentRepository;\r\n            _meetingRepository = meetingRepository;\r\n            _meetingGroupRepository = meetingGroupRepository;\r\n            _memberContext = memberContext;\r\n        }\r\n\r\n        public async Task Handle(RemoveMeetingCommentCommand command, CancellationToken cancellationToken)\r\n        {\r\n            var meetingComment = await _meetingCommentRepository.GetByIdAsync(new MeetingCommentId(command.MeetingCommentId));\r\n            if (meetingComment == null)\r\n            {\r\n                throw new InvalidCommandException([\"Meeting comment for removing must exist.\"]);\r\n            }\r\n\r\n            var meeting = await _meetingRepository.GetByIdAsync(meetingComment.GetMeetingId());\r\n            var meetingGroup = await _meetingGroupRepository.GetByIdAsync(meeting.GetMeetingGroupId());\r\n\r\n            meetingComment.Remove(_memberContext.MemberId, meetingGroup, command.Reason);\r\n        }\r\n    }\r\n}","sourceCodeStart":12,"sourceCodeEnd":39,"githubUrl":"https://github.com/kgrzybek/modular-monolith-with-ddd/blob/91c8ef24b4cb6ef558c95d8267fa07d68c7059f8/src/Modules/Meetings/Application/MeetingComments/RemoveMeetingComment/RemoveMeetingCommentCommandHandler.cs#L12-L39","documentation":"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).","triggerScenarios":"Dispatching RemoveMeetingCommentCommand with a comment id that does not exist: already removed, wrong id, or duplicate removal request.","commonSituations":"User double-submits a remove action; background job retries removal of an already-deleted comment; client shows a ghost comment.","solutions":["Make remove idempotent on the client side: treat 'not found' as already removed.","Pre-check existence and return 404/204 instead of dispatching a doomed command.","Debounce/dedupe the remove button to avoid duplicate dispatches.","Map InvalidCommandException to 400/404 at the API boundary."],"exampleFix":"// before\nawait _commandDispatcher.SendAsync(new RemoveMeetingCommentCommand(commentId, reason));\n\n// after\nvar comment = await _meetingQueries.GetCommentAsync(commentId);\nif (comment is null) return NoContent(); // already removed\nawait _commandDispatcher.SendAsync(new RemoveMeetingCommentCommand(commentId, reason));","handlingStrategy":"validation","validationCode":"var comment = await _meetingQueries.GetCommentAsync(commentId);\nif (comment is null) return NoContent(); // already removed\nawait _commandDispatcher.SendAsync(new RemoveMeetingCommentCommand(commentId, reason));","typeGuard":"public static bool IsValidRemoveCommand(RemoveMeetingCommentCommand c) =>\n    c.MeetingCommentId != Guid.Empty && !string.IsNullOrWhiteSpace(c.Reason);","tryCatchPattern":"try { await _commandDispatcher.SendAsync(cmd); }\ncatch (InvalidCommandException ex) when (ex.Errors.Any(m => m.Contains(\"must exist\")))\n{ return NoContent(); } // idempotent remove","preventionTips":["Treat a missing comment on remove as success (idempotent).","Dedupe the remove button to avoid duplicate dispatches.","Require a non-empty reason client-side."],"tags":["meetings","comments","not-found","invalid-command-exception","command-handler"],"backgroundTag":null,"analyzedSha":"91c8ef24b4cb6ef558c95d8267fa07d68c7059f8","analyzedAt":"2026-08-13T17:18:16.571Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}