-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Expand file tree
/
Copy path.cursorrules
More file actions
270 lines (248 loc) · 14.9 KB
/
.cursorrules
File metadata and controls
270 lines (248 loc) · 14.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# 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<TKey>` 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<TEntity, TKey>` (or `IReadOnlyRepository<...>` when suitable).
- SHOULD NOT expose `IQueryable` in the public contract.
- Internal implementations MAY use `IRepository<TEntity, TKey>` 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<TKey>`, 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<TEntity>` ONLY for aggregate roots.
- Do NOT include setters in the interface.
- DbContext class:
- Inherit `AbpDbContext<TDbContext>`.
- 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<TDbContextInterface, TEntity, TKey>`.
- 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<TEntity>` 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<TMongoDbContextInterface, TEntity, TKey>`.
- 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<TResource>("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<TModule>` or `AbpApplicationTestBase<TModule>` 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.