# ABP Framework – Cursor Rules # Scope: ABP Framework repository (abpframework/abp) — for developing ABP itself, not ABP-based applications. # Goal: Enforce ABP module architecture best practices (DDD, layering, DB/ORM independence), # maintain backward compatibility, ensure extensibility, and align with ABP contribution guidelines. ## Global Defaults - Follow existing patterns in this repository first. Before generating new code, search for similar implementations and mirror their structure, naming, and conventions. - Prefer minimal, focused diffs. Avoid drive-by refactors and formatting churn. - Preserve public APIs. Avoid breaking changes unless explicitly requested and justified. - Keep layers clean. Do not introduce forbidden dependencies between packages. ## Module / Package Architecture (Layering) - Use a layered module structure with explicit dependencies: - *.Domain.Shared: constants, enums, shared types safe for all layers and 3rd-party clients. MUST NOT contain entities, repositories, domain services, or business objects. - *.Domain: entities/aggregate roots, repository interfaces, domain services. - *.Application.Contracts: application service interfaces and DTOs. - *.Application: application service implementations. - *.EntityFrameworkCore / *.MongoDb: ORM integration packages depend on *.Domain only. MUST NOT depend on other layers. - *.HttpApi: REST controllers. MUST depend ONLY on *.Application.Contracts (NOT *.Application). - *.HttpApi.Client: remote client proxies. MUST depend ONLY on *.Application.Contracts. - *.Web: UI. MUST depend ONLY on *.HttpApi. - Enforce dependency direction: - Web -> HttpApi -> Application.Contracts - Application -> Domain + Application.Contracts - Domain -> Domain.Shared - ORM integration -> Domain - Do not leak web concerns into application/domain. ## Domain Layer – Entities & Aggregate Roots - Define entities in the domain layer. - Entities must be valid at creation: - Provide a primary constructor that enforces invariants. - Always include a protected parameterless constructor for ORMs. - Always initialize sub-collections in the primary constructor. - Do NOT generate Guid keys inside constructors; accept `id` and generate using `IGuidGenerator` from the calling code. - Make members `virtual` where appropriate (ORM/proxy compatibility). - Protect consistency: - Use non-public setters (private/protected/internal) when needed. - Provide meaningful domain methods for state transitions; prefer returning `this` from setters when applicable. - Aggregate roots: - Always use a single `Id` property. Do NOT use composite keys. - Prefer `Guid` keys for aggregate roots. - Inherit from `AggregateRoot` or audited base classes as required. - Aggregate boundaries: - Keep aggregates small. Avoid large sub-collections unless necessary. - References: - Reference other aggregate roots by Id only. - Do NOT add navigation properties to other aggregate roots. ## Repositories - Define repository interfaces in the domain layer. - Create one dedicated repository interface per aggregate root (e.g., `IProductRepository`). - Public repository interfaces exposed by modules: - SHOULD inherit from `IBasicRepository` (or `IReadOnlyRepository<...>` when suitable). - SHOULD NOT expose `IQueryable` in the public contract. - Internal implementations MAY use `IRepository` and `IQueryable` as needed. - Do NOT define repositories for non-aggregate-root entities. - Repository method conventions: - All methods async. - Include optional `CancellationToken cancellationToken = default` in every method. - For single-entity returning methods: include `bool includeDetails = true`. - For list returning methods: include `bool includeDetails = false`. - Do NOT return composite projection classes like `UserWithRoles`. Use `includeDetails` for eager-loading. - Avoid projection-only view models from repositories by default; only allow when performance is critical. ## Domain Services - Define domain services in the domain layer. - Default: do NOT create interfaces for domain services unless necessary (mocking/multiple implementations). - Naming: use `*Manager` suffix. - Domain service methods: - Focus on operations that enforce domain invariants and business rules. - Query methods are acceptable when they encapsulate domain-specific lookup logic (e.g., normalized lookups, caching, complex resolution). Simple queries belong in repositories. - Define methods that mutate state and enforce domain rules. - Use specific, intention-revealing names (avoid generic `UpdateXAsync`). - Accept valid domain objects as parameters; do NOT accept/return DTOs. - On rule violations, throw `BusinessException` (or custom business exceptions). - Use unique, namespaced error codes suitable for localization (e.g., `IssueTracking:ConcurrentOpenIssueLimit`). - Do NOT depend on authenticated user logic; pass required values from application layer. ## Application Services (Contracts + Implementation) ### Contracts - Define one interface per application service in *.Application.Contracts. - Interfaces must inherit from `IApplicationService`. - Naming: `I*AppService`. - Do NOT accept/return entities. Use DTOs and primitive parameters. ### Method Naming & Shapes - All service methods async and end with `Async`. - Do not repeat entity names in method names (use `GetAsync`, not `GetProductAsync`). - Standard CRUD: - `GetAsync(Guid id)` returns a detailed DTO. - `GetListAsync(QueryDto queryDto)` returns a list of detailed DTOs. - `CreateAsync(CreateDto dto)` returns detailed DTO. - `UpdateAsync(Guid id, UpdateDto dto)` returns detailed DTO (id MUST NOT be inside update DTO). - `DeleteAsync(Guid id)` returns void/Task. - `GetListAsync` query DTO: - Filtering/sorting/paging fields optional with defaults. - Enforce a maximum page size for performance. ### DTO Usage - Inputs: - Do not include unused properties. - Do NOT share input DTOs between methods. - Do NOT use inheritance between input DTOs (except rare abstract base DTO cases; be very cautious). ### Implementation - Application layer must be independent of web. - Implement interfaces in *.Application, name `ProductAppService` for `IProductAppService`. - Inherit from `ApplicationService`. - Make all public methods `virtual`. - Avoid private helper methods; prefer `protected virtual` helpers for extensibility. - Data access: - Use dedicated repositories (e.g., `IProductRepository`). - Do NOT use generic repositories. - Do NOT put LINQ/SQL queries inside application service methods; repositories perform queries. - Entity mutation: - Load required entities from repositories. - Mutate using domain methods. - Call repository `UpdateAsync` after updates (do not assume change tracking). - Extra properties: - Use `MapExtraPropertiesTo` or configure object mapper for `MapExtraProperties`. - Files: - Do NOT use web types like `IFormFile` or `Stream` in application services. - Controllers handle upload; pass `byte[]` (or similar) to application services. - Cross-application-service calls: - Do NOT call other application services within the same module. - For reuse, push logic into domain layer or extract shared helpers carefully. - You MAY call other modules’ application services only via their Application.Contracts. ## DTO Conventions - Define DTOs in *.Application.Contracts. - Prefer ABP base DTO types (`EntityDto`, audited DTOs). - For aggregate roots, prefer extensible DTO base types so extra properties can map. - DTO properties: public getters/setters. - Input DTO validation: - Use data annotations. - Reuse constants from Domain.Shared wherever possible. - Avoid logic in DTOs; only implement `IValidatableObject` when necessary. - Do NOT use `[Serializable]` attribute (BinaryFormatter is obsolete); ABP uses JSON serialization. - Output DTO strategy: - Prefer a Basic DTO and a Detailed DTO; avoid many variants. - Detailed DTOs: include reference details as nested basic DTOs; avoid duplicating raw FK ids unnecessarily. ## EF Core Integration - Define a separate DbContext interface + class per module. - Do NOT rely on lazy loading; do NOT enable lazy loading. - DbContext interface: - Inherit from `IEfCoreDbContext`. - Add `[ConnectionStringName("...")]`. - Expose `DbSet` ONLY for aggregate roots. - Do NOT include setters in the interface. - DbContext class: - Inherit `AbpDbContext`. - Add `[ConnectionStringName("...")]` and implement the interface. - Table prefix/schema: - Provide static `TablePrefix` and `Schema` defaulted from constants. - Use short prefixes; `Abp` prefix reserved for ABP core modules. - Default schema should be `null`. - Model mapping: - Do NOT configure entities directly inside `OnModelCreating`. - Create `ModelBuilder` extension method `ConfigureX()` and call it. - Call `b.ConfigureByConvention()` for each entity. - Repository implementations: - Inherit from `EfCoreRepository`. - Use DbContext interface as generic parameter. - Pass cancellation tokens using `GetCancellationToken(cancellationToken)`. - Implement `IncludeDetails(include)` extension per aggregate root with sub-collections. - Override `WithDetailsAsync()` where needed. ## MongoDB Integration - Define a separate MongoDbContext interface + class per module. - MongoDbContext interface: - Inherit from `IAbpMongoDbContext`. - Add `[ConnectionStringName("...")]`. - Expose `IMongoCollection` ONLY for aggregate roots. - MongoDbContext class: - Inherit `AbpMongoDbContext` and implement the interface. - Collection prefix: - Provide static `CollectionPrefix` defaulted from constants. - Use short prefixes; `Abp` prefix reserved for ABP core modules. - Mapping: - Do NOT configure directly inside `CreateModel`. - Create `IMongoModelBuilder` extension method `ConfigureX()` and call it. - Repository implementations: - Inherit from `MongoDbRepository`. - Pass cancellation tokens using `GetCancellationToken(cancellationToken)`. - Ignore `includeDetails` for MongoDB in most cases (documents load sub-collections). - Prefer `GetQueryableAsync()` to ensure ABP data filters are applied. ## ABP Module Classes - Every package must have exactly one `AbpModule` class. - Naming: `Abp[ModuleName][Layer]Module` (e.g., `AbpIdentityDomainModule`, `AbpIdentityApplicationModule`). - Use `[DependsOn(typeof(...))]` to declare module dependencies explicitly. - Override `ConfigureServices` for DI registration and configuration. - Override `OnApplicationInitialization` sparingly; prefer `ConfigureServices` when possible. - Each module must be usable standalone; avoid hidden cross-module coupling. ## Framework Extensibility - All public and protected members should be `virtual` for inheritance-based extensibility. - Prefer `protected virtual` over `private` for helper methods to allow overriding. - Use `[Dependency(ReplaceServices = true)]` patterns for services intended to be replaceable. - Provide extension points via interfaces and virtual methods. - Document extension points with XML comments explaining intended usage. - Consider providing `*Options` classes for configuration-based extensibility. ## Backward Compatibility - Do NOT remove or rename public API members without a deprecation cycle. - Use `[Obsolete("Message. Use X instead.")]` with clear migration guidance before removal. - Maintain binary and source compatibility within major versions. - Add new optional parameters with defaults; do not change existing method signatures. - When adding new abstract members to base classes, provide default implementations if possible. - Prefer adding new interfaces over modifying existing ones. ## Localization Resources - Define localization resources in Domain.Shared. - Resource class naming: `[ModuleName]Resource` (e.g., `IdentityResource`, `PermissionManagementResource`). - JSON files under `/Localization/[ModuleName]/` directory. - Use `LocalizableString.Create("Key")` for localizable exceptions and messages. - All user-facing strings must be localized; no hardcoded English text in code. - Error codes should be namespaced: `ModuleName:ErrorCode` (e.g., `Identity:UserNameAlreadyExists`). ## Settings & Features - Define settings in `*SettingDefinitionProvider` in Domain.Shared or Domain. - Setting names must follow `Abp.[ModuleName].[SettingName]` convention. - Define features in `*FeatureDefinitionProvider` in Domain.Shared. - Feature names must follow `[ModuleName].[FeatureName]` convention. - Use constants for setting/feature names; never hardcode strings. ## Permissions - Define permissions in `*PermissionDefinitionProvider` in Application.Contracts. - Permission names must follow `[ModuleName].[Permission]` convention. - Use constants for permission names (e.g., `IdentityPermissions.Users.Create`). - Group related permissions logically. ## Event Bus & Distributed Events - Use `ILocalEventBus` for intra-module communication within the same process. - Use `IDistributedEventBus` for cross-module or cross-service communication. - Define Event Transfer Objects (ETOs) in Domain.Shared for distributed events. - ETO naming: `[EntityName][Action]Eto` (e.g., `UserCreatedEto`, `OrderCompletedEto`). - Event handlers belong in the Application layer. - ETOs should be simple, serializable, and contain only primitive types or nested ETOs. ## Testing - Unit tests: `*.Tests` projects for isolated logic testing with mocked dependencies. - Integration tests: `*.EntityFrameworkCore.Tests` / `*.MongoDB.Tests` for repository and DB tests. - Use `AbpIntegratedTest` or `AbpApplicationTestBase` base classes. - Test modules should use `[DependsOn]` on the module under test. - Use `Shouldly` assertions (ABP convention). - Test both EF Core and MongoDB implementations when the module supports both. - Include tests for permission checks, validation, and edge cases. - Name test methods: `MethodName_Scenario_ExpectedResult` or `Should_ExpectedBehavior_When_Condition`. ## Contribution Discipline (PR / Issues / Tests) - Before significant changes, align via GitHub issue/discussion. - PRs: - Keep changes scoped and reviewable. - Add/update unit/integration tests relevant to the change. - Build and run tests for the impacted area when possible. - Localization: - Prefer the `abp translate` workflow for adding missing translations (generate `abp-translation.json`, fill, apply, then PR). ## Review Checklist - Layer dependencies respected (no forbidden references). - No `IQueryable` or generic repository usage leaking into application/domain. - Entities maintain invariants; Guid id generation not inside constructors. - Repositories follow async + CancellationToken + includeDetails conventions. - No web types in application services. - DTOs in contracts, serializable, validated, minimal, no logic. - EF/Mongo integration follows context + mapping + repository patterns. - Minimal diff; no unnecessary API surface expansion.