Help us improve
Share bugs, ideas, or general feedback.
From dex-skill-clean-architecture
Clean Architecture — ловушки слоёв, зависимостей, транзакций. Активируется при clean architecture, onion, hexagonal, dependency rule, IQueryable, Domain layer, Application layer, MediatR, IUnitOfWork, IRepository, Feature Slice, God DbContext
How this skill is triggered — by the user, by Claude, or both
Slash command
/dex-skill-clean-architecture:clean-architectureThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Плохо: `Domain.csproj` содержит `PackageReference` на `EntityFrameworkCore`
Share bugs, ideas, or general feedback.
Плохо: Domain.csproj содержит PackageReference на EntityFrameworkCore
Правильно: Domain проект без внешних зависимостей, только .NET BCL
Почему: Domain становится привязан к ORM, невозможно заменить persistence без изменения бизнес-логики
Плохо: handler инжектит AppDbContext и вызывает context.Users.Where(...)
Правильно: инжектить IUserRepository / IUnitOfWork, реализация в Infrastructure
Почему: Application знает про EF — при смене ORM/хранилища меняется Application слой вместо одного Infrastructure
Плохо: Infrastructure вызывает методы из Application (не через интерфейсы) Правильно: Infrastructure реализует интерфейсы, определённые в Application Почему: Circular reference между проектами → невозможно собрать, или скрытая связность через DI
Плохо: IOrderRepository { IQueryable<Order> GetAll(); }
Правильно: IOrderRepository { Task<List<Order>> GetByCustomerAsync(int customerId); }
Почему: клиент строит SQL через LINQ → привязка к конкретному провайдеру, невозможно заменить на API/файл/кэш. Тесты не могут мокать поведение IQueryable корректно
Плохо: public virtual ICollection<OrderItem> Items { get; set; } — навигация EF в Domain
Правильно: private readonly List<OrderItem> _items; public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
Почему: virtual + setter = EF lazy loading протекает в Domain, бизнес-логика зависит от прокси
Плохо: один класс UserDto передаётся от Controller до Repository
Правильно: отдельные модели на каждый слой: CreateUserRequest → CreateUserCommand → User entity → UserResponse
Почему: изменение API контракта ломает Domain, изменение Domain ломает API. Связность между слоями через общую модель
Плохо: record UpdateOrderCommand(Order Order) : IRequest<Result> — handler получает Aggregate целиком
Правильно: record UpdateOrderCommand(Guid OrderId, string ShippingAddress, string CustomerNote) : IRequest<Result> — примитивы / специализированный record с 3-5 полями
Почему: передача Entity тянет навигации (скрытый Include без запроса handler'а, N+1), привязывает контракт Command к схеме Entity (добавили navigation — сломали всех подписчиков MediatR pipeline), ломает attach/detach состояния между scope'ами EF (InvalidOperationException при обработке в другом scope), делает unit-тест handler'а невозможным без полного билдинга Aggregate. Command/Query — это контракт намерения, не контейнер для Entity
Плохо: Task<List<Order>> GetOrdersForDashboardAsync() → handler проецирует в DTO в памяти
Правильно: Task<List<OrderSummaryDto>> GetDashboardSummariesAsync() — проекция на уровне Repository через Select в SQL
Почему: read-сценарий (dashboard / отчёт / grid) требует 3-5 полей из 20+. Возврат Entity тянет всё + тянет Change Tracker (overhead). В CQRS read-сторона оптимизируется отдельно от write, и возврат Entity из read-методов ломает эту возможность
Плохо: Controller проверяет баланс, рассчитывает скидку, сохраняет заказ
Правильно: Controller → Send(new CreateOrderCommand(...)) → Handler содержит логику
Почему: логика дублируется между контроллерами, невозможно переиспользовать из другого entry point (gRPC, CLI, message handler)
Плохо: Order.Create() проверяет что CustomerId существует в базе (делает запрос)
Правильно: Handler проверяет существование через ICustomerRepository, затем вызывает Order.Create()
Почему: Domain не должен зависеть от I/O. Domain валидирует инварианты (Amount > 0), Application — бизнес-правила с I/O
Плохо: OrderRepository.CreateOrder() содержит бизнес-расчёты и валидацию
Правильно: Repository только CRUD, бизнес-логика в Domain/Application
Почему: при смене хранилища теряется бизнес-логика. Тесты Infrastructure = тесты бизнеса
Плохо: await _unitOfWork.SaveChangesAsync() вызывается 2-3 раза в одном Handler
Правильно: один SaveChangesAsync() в конце Handler (или через pipeline behavior)
Почему: partial commit — при ошибке на втором save первый уже в БД, нет атомарности. Rollback невозможен
Плохо: CreateOrderHandler внутри вызывает _mediator.Send(new UpdateInventoryCommand(...))
Правильно: один Handler = одна транзакция. Связь между операциями через Domain Events или Outbox
Почему: вложенные Handler'ы — скрытые зависимости, неопределённый порядок выполнения, проблемы с транзакциями
Плохо: все тесты бизнес-логики через WebApplicationFactory + HTTP client
Правильно: Unit тесты Domain (чистые), Unit тесты Handler'ов (мок репозитория), Integration через HTTP — только API контракт
Почему: HTTP тесты медленные, хрупкие (ломаются при смене роутинга), не покрывают edge cases бизнес-логики
Плохо: мокается 10 интерфейсов чтобы протестировать один Handler Правильно: Handler зависит от 1-3 интерфейсов. Если больше — нарушен SRP, нужен рефакторинг Почему: тест с 10 моками — сигнал что Handler делает слишком много. Тест хрупкий и нечитаемый
Плохо: Application/Commands/, Application/Queries/, Application/Validators/ — 50 файлов в каждой папке
Правильно: Application/Orders/Create/, Application/Orders/Cancel/ — command + handler + validator рядом
Почему: при 100+ use cases навигация по типу файла невозможна. Feature slice = всё для одной фичи в одной папке
Плохо: один AppDbContext с 50+ DbSet — все Aggregate Root в одном контексте
Правильно: bounded context = свой DbContext (OrderDbContext, IdentityDbContext)
Почему: один контекст = одна огромная миграция, конфликты между командами, медленный startup (EF компилирует все модели)
Плохо: проект Shared с утилитами, на который ссылаются все слои
Правильно: каждый слой содержит свои вспомогательные классы, общие только абстракции (Domain.Primitives)
Почему: Shared становится свалкой, любое изменение в нём пересобирает всё решение, нарушает принцип минимальных зависимостей
npx claudepluginhub dex-it/claude-code-marketplace --plugin dex-skill-clean-architectureProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.