Попытался реализовать все дополнительные задания по мере возможности, большие трудности были с тем, что большую требований я видел впервые, поэтому пришлось обучаться по мере написания.
Сделал на PostgreSQL, Swagger UI у меня работает.
Миграции для БД, Использование ORM EF Core, Использование системы контроля версий я сделал полностью (на сколько позволило моё понимание того, что является для них "полностью").
Event Sourcing и CQRS сделал на столько, на сколько сам понял, либо позволили возможности (подробности будут ниже).
Тестирование кода, UML-диаграмма сущностей и Диаграмма таблиц БД в процессе, было решено перенести их реализацию на потом, поскольку по многим предеметам по учёбе поджимают сроки сдачи лабораторных, вернусь к их реализации, когда сдам все лабы.
В планах сначала тесты, затем диаграмма таблиц, и в конце диаграмма классов, как самое сложное
Теперь по поводу решений: Event Sourcing я сделал просто в виде дополнительной таблицы, запись в неё идёт при каждой комманде, но возможности считать данные или как-то изменить/воспользоваться ими (по крайней мере снаружи) пока нет.
Если это необходимо могу реализовать, возможности есть, сделать ещё один репозиторий (если нужно как-то эти данные изменять) и один (или больше, опять же, если данные ещё нужно не только считывать) сервис. Комманды и запросы реализовать вообще не проблема. Т.е. возможности для расширения присутствуют.
На счёт CQRS: Реализовал отдельно запросы и комманды, хотел полностью отделить, но в сервисе обработки команд, для валидации, пришлось делать запрос данных задачи в репозиторий, я не нашёл особо инфы правильно ли это в рамках CQRS (справедливости ради я не особо тщательно искал). Учитывая, что сервис в любом случае работает с этим репозиторием, а снаружи класса (и методов) сервиса это решение никак не отсвечивает, т.е. об обращении только валидирующий метод и вкурсе, я решил это решение оставить.
Сервис запросов репозиторий минует и обращается к базе напримую, т.к. данные он не изменяет и на момент единственной проблемой было, что метод GetTaskById два раза реализуется, один раз в сервисе, а второй в репозитории, чтобы для валидации запрос сделать можно было. Но при попытке реализовать тесты вскрылось, что проверять сервис запросов в таком виде нереально, поэтому в планах переделать, переместив работу с данными в репозиторий.
На счёт общей структуры, распишу тут, т.к. по обозревателю может быть не очень понятно. Я конечно попытался напихать xml комментари где только можно, но в обозревателе их не почитаешь + возможно я где-то плохо объяснил.
Если в кратце, контроллер принимает запрос, и через IMediator отправляет его по хандлерам команд и запросов в контролере логики никакой нет (разве что упаковка, если та потребуется).
Хандлеры нужны чисто для перенаправления данных от IMediator до сервиса логики никакой нет, в теории можно что-то придумать, вроде базовой валидации на длину и т.д., но лучше не надо. По сути чисто структурный элемент (и ещё возможно IMediator без них нормально не работает, но я не проверял).
Сервисы (пока их 2: сервис комманд и сервис запросов) реализованы через интерфейс, сожержат бизнес логику и можно прикрутить полноценную умную валидацию.
Если валидация находит ошибку он вызывает DomainException, он наследуется от обычных исключений и по сути указывает своим форматом, что это ошибка валидации. Если вызывается DomainException или обычное исключение (по какой-то причине) оно попадает в обработчик.
Обработчик прописан в Programm, по этому, вроде как, все исключения (кроме тех что происходят в момент инициализации приложения) попадают туда. Там они делятся: если это DomainException, то отправитель сразу видит, что он сделал не так.
Если это внутреннее исключение, то пользователю этот массив текста всё-равно ничего не даст, да и не стоит ему много знать, поэтому такие ошибки отправляются в логи.
Их я сделал не по доброте душевной, а потому что мне самому надо было узнать, что я не так сделал (обработчик, то в таком случае ничего кроме "Внутренняя ошибка сервера" не пишет).
Если после сервиса всё успешно в дело вступает репозиторий, обоим репозиториям (для задач и для истории действий) отправляется команда и они там уже выполняют необходимые действия с базами данных.
Они оба тоже сделаны через интерфейсы, поэтому: первое их легко заменить (мало ли вдруг) и второе сервису не нужно знать с чем он работает, за счёт чего сломать код сложнее. Это же кстати относится и к сервисам, они тоже через интерфейс, поэтому ещё ожна причина разделения команд с запросами от сервисов.
Три категории типов данных для общения комманды с запросами - основной тип данных для общения до сервисов, сервисы формируют уже эвенты и передают их в репозитории.
Смысл в том, что у эвенты это уже преобразованные данные, например у TaskStartedEvent уже есть StartedAt, заполняемый в сервисе, а в команде StartTask времени нет, т.к. это сырые данные прямиком (если не учитывать маршрут) из контроллера.
Вообще кроме добавления текущего времени, обработка не предусмотрена, но можно, например, вместо того чтобы запрещать невалидные данные, просто заменять их валидные, но целом основной идеей разделения были разные поля и чтобы [JsonIgnore] каждый раз не указывать.
Третий тип TaskDto (Data Transfer Object), чтобы при возвращении результатов в запросы TaskEntity не кидать, а то мало ли, всё-же поле таблицы. Плюс разделение ответственности и всё такое.
Есть ещё TaskEntity и TaskEventEntity, но это чисто для хранения.
На счёт структуры папок мало что могу сказать, делал по опыту (наитию), теории в эту сторону я не особо знаю.
По сути Model чисто данные хранения и Task эвенты, поскольку больше не нашёл куда запихать, Infrastructure - базы и всё что с ними напрямую взаимодействует, App - внешняя логика и вся прямая обработка комманд/запросов (т.е то что не касается непосредственно записи в базу).
Interfaces решил запихать в App, т.к. именно там к ним обращаются, но из-за этого репозитории, внутри проекта в разных каталогах со своими интерфейсами, в отличае от сервисов, которые по сути в соседних папках. Возможно стоит Interfaces перенести в Model, но сейчас уже, наверное, нет смысла ради этого структуру менять.