{"record":{"id":"c7ea118bb1c625d6","repo":"kgrzybek/modular-monolith-with-ddd","slug":"to-create-reply-the-comment-must-exist","errorCode":null,"errorMessage":"To create reply the comment must exist.","messagePattern":"To create reply the comment must exist\\.","errorType":"validation","errorClass":"InvalidCommandException","httpStatus":400,"severity":"error","filePath":"src/Modules/Meetings/Application/MeetingComments/AddMeetingCommentReply/AddReplyToMeetingCommentCommandHandler.cs","lineNumber":33,"sourceCode":"        private readonly IMeetingGroupRepository _meetingGroupRepository;\n        private readonly IMeetingCommentingConfigurationRepository _meetingCommentingConfigurationRepository;\n        private readonly IMemberContext _memberContext;\n\n        internal AddReplyToMeetingCommentCommandHandler(IMeetingCommentRepository meetingCommentRepository, IMeetingRepository meetingRepository, IMeetingGroupRepository meetingGroupRepository, IMeetingCommentingConfigurationRepository meetingCommentingConfigurationRepository, IMemberContext memberContext)\n        {\n            _meetingCommentRepository = meetingCommentRepository;\n            _meetingRepository = meetingRepository;\n            _meetingGroupRepository = meetingGroupRepository;\n            _meetingCommentingConfigurationRepository = meetingCommentingConfigurationRepository;\n            _memberContext = memberContext;\n        }\n\n        public async Task<Guid> Handle(AddReplyToMeetingCommentCommand command, CancellationToken cancellationToken)\n        {\n            var meetingComment = await _meetingCommentRepository.GetByIdAsync(new MeetingCommentId(command.InReplyToCommentId));\n            if (meetingComment == null)\n            {\n                throw new InvalidCommandException([\"To create reply the comment must exist.\"]);\n            }\n\n            var meeting = await _meetingRepository.GetByIdAsync(meetingComment.GetMeetingId());\n\n            var meetingGroup = await _meetingGroupRepository.GetByIdAsync(meeting.GetMeetingGroupId());\n\n            var meetingCommentingConfiguration = await _meetingCommentingConfigurationRepository.GetByMeetingIdAsync(meetingComment.GetMeetingId());\n\n            var replyToComment = meetingComment.Reply(_memberContext.MemberId, command.Reply, meetingGroup, meetingCommentingConfiguration);\n            await _meetingCommentRepository.AddAsync(replyToComment);\n\n            return replyToComment.Id.Value;\n        }\n    }\n}","sourceCodeStart":15,"sourceCodeEnd":48,"githubUrl":"https://github.com/kgrzybek/modular-monolith-with-ddd/blob/91c8ef24b4cb6ef558c95d8267fa07d68c7059f8/src/Modules/Meetings/Application/MeetingComments/AddMeetingCommentReply/AddReplyToMeetingCommentCommandHandler.cs#L15-L48","documentation":"Thrown by AddReplyToMeetingCommentCommandHandler when GetByIdAsync for the InReplyToCommentId returns null. Replies require a parent comment; a missing parent rejects the command with InvalidCommandException (HTTP 400). The parent comment is also used to resolve the meeting, group, and commenting configuration.","triggerScenarios":"Dispatching AddReplyToMeetingCommentCommand with an InReplyToCommentId that has no comment: deleted parent, wrong id, or passing a reply id whose own thread was removed.","commonSituations":"Threaded UI renders a cached parent that was since deleted; client posts a reply to a comment id pulled from a stale notification.","solutions":["Verify InReplyToCommentId exists and is still available before allowing reply.","Refresh the thread on the client so deleted parents are removed.","Pre-check parent existence in the controller and return 404.","Catch InvalidCommandException at the API boundary and map to 400/404."],"exampleFix":"// before\nvar replyId = await _commandDispatcher.SendAsync(new AddReplyToMeetingCommentCommand(parentCommentId, text));\n\n// after\nvar parent = await _meetingQueries.GetCommentAsync(parentCommentId);\nif (parent is null) return NotFound(\"Parent comment not found.\");\nvar replyId = await _commandDispatcher.SendAsync(new AddReplyToMeetingCommentCommand(parentCommentId, text));","handlingStrategy":"validation","validationCode":"var parent = await _meetingQueries.GetCommentAsync(parentCommentId);\nif (parent is null) return NotFound(\"Parent comment not found.\");\nvar replyId = await _commandDispatcher.SendAsync(new AddReplyToMeetingCommentCommand(parentCommentId, text));","typeGuard":"public static bool IsValidReplyCommand(AddReplyToMeetingCommentCommand c) =>\n    c.InReplyToCommentId != Guid.Empty && !string.IsNullOrWhiteSpace(c.Reply);","tryCatchPattern":"try { await _commandDispatcher.SendAsync(cmd); }\ncatch (InvalidCommandException ex) when (ex.Errors.Any(m => m.Contains(\"must exist\")))\n{ return NotFound(new { errors = ex.Errors }); }","preventionTips":["Re-fetch the thread before allowing a reply to drop stale parents.","Validate a non-empty parent id and non-empty reply text client-side.","Disable the reply box when the parent is no longer in the rendered thread."],"tags":["meetings","comments","replies","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"}