Проект написан на Asp.Net Core Minimal API, C# 10 и EF NET Core 10. СУБД: SQLite (InMemory)
Для запуска необходима .NET SDK 10
- Проверяем, что .NET SDK установлен
dotnet --version - Переходим в папку проекта
cd путь\к\папке\PTMK-Test - Восстанавливаем зависимости
dotnet restore - Запускаем приложение
dotnet run --project PTMK-Test -c Release - Открываем в браузере
http://localhost:5153/swagger/index.html(может быть понадобится время для создания БД)
Получение всех заявок (GET /api/applications)
- Назначение: Постраничный вывод общего списка заявок организации.
Фильтрация и поиск заявок (GET /api/applications/filter)
- Назначение: Гибкий динамический поиск по заданным критериям.
Поиск просроченных заявок исполнителя (GET /api/applications/overdue-in-progress/{executorId})
- Назначение: Оперативный вывод проблемных (просроченных) задач конкретного сотрудника, которые прямо сейчас находятся у него в работе.
Аналитический отчет по миллиону строк (GET /api/applications/analytics-report)
- Назначение: Генерация глобальной сводной статистики
Принятие заявки в работу (PATCH /api/applications/{id}/set-in-progress)
- Назначение: Перевод обращения на этап выполнения (смена статуса на
InProgress).
Завершение выполнения заявки (`PATCH /api/applications/{id}/set-completed)
- Назначение: Фиксация выполнения работ по заявке и перевод её в статус
Completed.
Переназначение исполнителя заявки (PATCH /api/applications/{id}/change-executor)
- Назначение: Смена ответственного сотрудника за выполнение задачи.
Регистрация (создание) новой заявки (POST /api/applications)
- Назначение: Создание нового обращения в системе.
Удаление заявки (DELETE /api/applications/{id})
- Назначение: Удаляет заявку из системы.
Анализ плана выполнения запроса (GET /api/debug/debug-index-check)
- Назначение: Проверка корректности использования индексов ядром СУБД SQLite без выполнения самого запроса.
Стресс-тест скорости выполнения SQL (GET /api/debug/benchmark-indexes-speed)
- Назначение: Чистый математический замер влияния индекса
IX_Applications_ExecutorIdна скорость выполнения агрегационных функций СУБД (COUNT).
Сквозной интеграционный бенчмарк API-эндпоинта (GET /api/debug/benchmark-filter-endpoint)
- Назначение: Реальный замер скорости прохождения полноценного HTTP-запроса через весь конвейер приложения (включая работу Kestrel, маршрутизацию Minimal APIs, пайплайны MediatR, трансляцию выражений спецификаций EF Core и сериализацию данных).
Получение всех сотрудников (GET /api/employees)
- Назначение: Постраничный вывод общего списка сотрудников (персонала) организации.
База данных находится в Третьей нормальной форме (3НФ)
- Отсутствие дублирования: Персональные и организационные данные сотрудников (ФИО, Департамент, Должность) изолированы в таблице
Employees. В таблицеApplicationsхранятся только легковесные идентификаторыAuthorIdиExecutorId(Guid). - Атомарность данных: Комплексный тип ФИО (
ComplexPropertyв EF Core 10) разбит в физической таблице на три неделимые колонки:FirstName,Surname,MiddleName. Это исключает использование ресурсоемких текстовых поисков при фильтрациях. - Использование и контроль связей: Таблицы жестко связаны внешними ключами (Foreign Keys). Настроено ограничение ссылочной целостности
DeleteBehavior.Restrict, предотвращающее удаление сотрудников, за которыми числятся исторические или активные заявки.
Архитектура приложения спроектирована по принципам Clean Architecture с разделением ответственности и использованием паттернов CQRS (через MediatR).
Основные компоненты решения, зафиксированные в репозитории:
Core: Содержит инкапсулированные сущностиApplicationиEmployee, а также логику переходов конечного автомата состояний и структурные Value Objects (ApplicationStatus,Name).Application: Реализация бизнес-логики. Включает в себя интерфейс контекстаIDbContext, раздельные асинхронные Query/Command-хэндлеры (включая логику созданияCreateApplicationHandlerи построения аналитикиGetApplicationsReportHandler), а также спецификации фильтрацииSpecifications, конфигураторы таблиц:ApplicationConfiguratorиEmployeeConfigurator, и общие структуры -PaginationParametersиRequestResult.Infrastructure: Слой данных. Содержит реализацию контекстаAppDbContextна базе Entity Framework Core 10 и маппинги конфигураторов таблиц.Web: Слой API. Легковесные Minimal APIs (ApplicationEndpointsMapper,EmployeeEndpointsMapper,DebugEndpointsMapper), защищенные встроенными механизмами валидации параметров и пагинации, возвращают ответ клиенту с помощью преобразованияRequestResultreadonly structвAspNetCore.Http.Resultобъект.
В системе реализован сквозной жизненный цикл обработки внутренних обращений:
- Регистрация заявки (
POST /api/applications): Автор (сотрудник) создает заявку. Система фиксирует дату создания, описание, назначает дедлайн и ответственного исполнителя. Заявка получает первоначальное состояниеNew. - Принятие в работу: Ответственный исполнитель переводит обращение на этап активного выполнения. Состояние изменяется на
InProgress. - Завершение работы: После выполнения задач исполнитель закрывает заявку, переводя её в финальный статус
Completed. - Удаление заявки: В любой момент, в независимоти от состояния заявки, ее можно удалить.
Вся бизнес-логика строго инкапсулирована внутри слоев домена, предотвращая появление некорректных данных:
- Защита дедлайнов: Конструктор
Applicationнакладывает жесткое ограничение: дата дедлайна (Deadline) обязана быть строго больше даты создания (CreatedAt). Нарушение вызываетArgumentException, которое глобальный Middleware перехватывает и превращает в аккуратный ответ400 Bad Request. - Конечный автомат статусов: Переходы состояний инкапсулированы внутри структуры
ApplicationStatusи могут двигаться только вперед по цепочке:New -> InProgress -> Completed. Любые некорректные перескоки или возврат закрытых заявок назад в работу заблокированы на уровне C#-логики. - Инкапсуляция изменений: Прямой доступ к изменению полей извне закрыт (все свойства get-only). Мутации (смена исполнителя или статуса) происходят строго через специализированные методы сущности (
TrySetInProgress(),TrySetCompleted(),ChangeExecutor()). - Контроль ссылочной целостности: СУБД SQLite защищена внешними ключами (Foreign Keys). Вы не можете создать заявку на фейковый или несуществующий
Guidсотрудника (база вернет ошибку целостности, которую API отдаст в виде400 Bad Request). Удаление сотрудников, за которыми числятся заявки, аппаратно заблокировано правиломDeleteBehavior.Restrict.
- Персональные данные сотрудников не дублируются в миллионе строк таблицы
Applications. Они вынесены вEmployees, а связи строятся по легковесным индексамGuid. - На уровне базы данных развернуты B-Tree индексы на следующие поля:
IX_Applications_Number,IX_Applications_AuthorId,IX_Applications_ExecutorId,IX_Employees_Department,IX_Employees_Position
{
"message": "Сравнительный анализ скорости HTTP-эндпоинта фильтрации на 1 000 000 строк",
"testedUrl": "http://localhost:5153/api/applications/filter?status=InProgress&executorId=00531fae-5f73-41ae-83d8-2f6caccc0b33&isOverdue=true&pageNumber=12&pageSize=1024",
"requestedPageNumber": 12,
"requestedPageSize": 1024,
"httpResponseStatusWithoutIndex": "OK",
"httpResponseStatusWithIndex": "OK",
"endpointTimeWithoutIndex": "921,24 мс (СУБД перебирает миллион строк)",
"endpointTimeWithIndex": "4,25 мс (СУБД использует B-Tree индекс)",
"performanceGain": "Эндпоинт фильтрации с индексом стал быстрее в 216,9 раз(а)!"
}При изменении бизнес-требований систему потребуется модернизировать следующим образом:
- Заявка должна проходить согласование руководителем
(
Core): Добавить в перечислениеApplicationStatusTypeновые этапы:PendingApproval(Ожидает согласования) иRejected(Отклонена). В сущностиApplication: Добавить полеGuid? ApproverIdи доменный методApprove(). Логику конечного автомата вApplicationStatusобновить так, чтобы перевести заявку в статусInProgressможно было строго только после успешного согласования руководителем. - У заявки может быть несколько исполнителей
В
Application/Implementation/Configurators: Удалить одиночную колонкуExecutorIdиз таблицыApplications. Создать промежуточную таблицу связейApplicationExecutorsс составным первичным ключом{ ApplicationId, EmployeeId }и настроить два внешних ключа. В классеApplicationзаменить полеGuid ExecutorIdна навигационную коллекциюIReadOnlyCollection<Guid> ExecutorIdsи добавить доменные методы управления командойAddExecutor() / RemoveExecutor(). - Необходимо хранить историю изменения статусов
Создать историческую таблицу
ApplicationStatusHistoryс полями:Id (PK),ApplicationId (FK),OldStatus,NewStatus,ChangedAt (DateTime),ChangedById (Guid)Внутри методов смены статуса сущностиApplicationвнедрить генерацию доменных событий (Domain Events).Handlerэтих событий будет автоматически записывать новую строчку лога в историческую таблицу при каждом изменении статуса. - Сроки выполнения зависят от типа заявки
Чтобы поддержать НФ нужно создать еще одно отношение
ApplicationTypesсо столбцамиId (PK),NameиSlaHours(время на выполнение). В таблицуApplicationsдобавить внешний ключApplicationTypeIdПри регистрации заявки в конструктор будет передаваться выбранный тип. Конструктор автоматически рассчитает дедлайн без участия пользователя:_deadline = createdAt.AddHours(selectedType.SlaHours) - Требуется разграничение прав доступа (сотрудник, руководитель, администратор)
Добавить в модель в слое
Coreк Employee поле-перечислениеRole-UserRole { Employee, Leader, Admin }Добавить в таблицуEmployeesколонкуRole, хранящую перечисление -UserRole { Employee, Leader, Admin }Подключить JWT-авторизацию