Make Cursor respect domain-driven design boundaries by mapping your bounded contexts in .cursorrules, creating per-domain rules in nested .cursor/rules/ directories, and using @folder context to scope AI suggestions to specific domains. Without boundary awareness, Cursor freely imports across domains and breaks DDD encapsulation.
Teaching Cursor About Domain Boundaries
Cursor treats your entire codebase as one flat context by default. It will import a payment utility into an authentication module or reference user types from an order service without hesitation. Domain-driven design requires strict boundaries between bounded contexts, and this tutorial shows you how to encode those boundaries into rules that Cursor respects. You will create domain maps, set up per-domain rules, and learn prompt patterns that keep AI suggestions within the correct domain.
Prerequisites
- Cursor installed (Pro recommended for larger projects)
- A project organized by domain or bounded context
- Basic understanding of DDD concepts (bounded contexts, aggregates)
Step-by-step guide
Map your bounded contexts in .cursorrules
Map your bounded contexts in .cursorrules
Document your domain boundaries in .cursorrules so Cursor understands which modules belong to which domain and what cross-domain communication is allowed. List each domain, its directory, and its public API surface.
1# .cursorrules23## Domain Boundaries (DDD)4### Bounded Contexts5- Auth domain: src/domains/auth/ — handles authentication, sessions, tokens6- Orders domain: src/domains/orders/ — handles order lifecycle, cart, checkout7- Payments domain: src/domains/payments/ — handles payment processing, refunds8- Users domain: src/domains/users/ — handles user profiles, preferences910### Cross-Domain Rules11- Domains communicate ONLY through their public API (index.ts exports)12- NEVER import internal files across domains (no deep imports)13- Shared types go in src/shared/types/ — not duplicated per domain14- Events between domains use src/shared/events/ event bus15- Each domain has its own repository — never query another domain's tables directlyPro tip: Add a visual directory map to .cursorrules. Cursor reads it and understands the hierarchy better than prose descriptions.
Expected result: Cursor understands your domain boundaries and avoids cross-domain imports in generated code.
Create nested per-domain rules
Create nested per-domain rules
Place .cursor/rules/ directories inside each domain folder. These rules auto-attach when Cursor is editing files within that domain, reinforcing the boundary without you needing to mention it in every prompt.
1---2description: Orders domain boundary rules3globs: "src/domains/orders/**"4alwaysApply: false5---67- This is the Orders bounded context8- Only import from: src/domains/orders/**, src/shared/**9- NEVER import from: src/domains/auth/**, src/domains/payments/**, src/domains/users/**10- Communicate with other domains through the event bus: src/shared/events/11- Repository: src/domains/orders/repositories/ — only access orders tables12- Public API: src/domains/orders/index.ts — only these exports are visible to other domains13- Types: use OrderAggregate, OrderItem, OrderStatus from this domain's types/Pro tip: Nested rules directories auto-attach for files in their parent directory. You do not need glob patterns when using this approach.
Expected result: When editing any file in the orders domain, Cursor automatically enforces boundary rules.
Scope Composer sessions to a single domain
Scope Composer sessions to a single domain
When working within one domain, use @folder to scope Cursor's context. This prevents it from pulling in code from other domains as context. Open Composer with Cmd+I and reference only the relevant domain folder.
1// Prompt to type in Cursor Composer (Cmd+I):2// @src/domains/orders/ @src/shared/types/3// Add a cancelOrder function to the OrderService.4// Requirements:5// - Only use types and repositories from the orders domain6// - Emit an OrderCancelled event through the shared event bus7// - Do NOT import anything from payments or users domains8// - Update order status to 'cancelled' and record cancellation reasonPro tip: Start each Composer session by referencing the domain folder and shared types. This sets the context boundary for the entire session.
Expected result: Cursor generates code within the orders domain boundary, using only allowed imports.
Generate domain events for cross-domain communication
Generate domain events for cross-domain communication
When domains need to communicate, use Cursor to generate event-based integration rather than direct imports. Reference both domains' public APIs and the shared event bus to generate proper event publishing and subscription code.
1// Prompt to type in Cursor Chat (Cmd+L):2// @src/shared/events/eventBus.ts3// @src/domains/orders/index.ts @src/domains/payments/index.ts4// Generate an event flow for order payment:5// 1. Orders domain publishes OrderCreated event with orderId and totalCents6// 2. Payments domain subscribes and initiates payment processing7// 3. Payments domain publishes PaymentCompleted or PaymentFailed event8// 4. Orders domain subscribes and updates order status9// Keep all domain internals encapsulated — only use public API exports.Expected result: Cursor generates event definitions, publishers, and subscribers that respect domain boundaries.
Complete working example
1---2description: Orders bounded context rules3globs: "src/domains/orders/**"4alwaysApply: false5---67# Orders Domain Boundary89## Allowed Imports10- `src/domains/orders/**` — all internal modules11- `src/shared/types/**` — shared type definitions12- `src/shared/events/**` — event bus for cross-domain communication13- `src/shared/utils/**` — shared utilities (logging, validation)1415## Forbidden Imports16- `src/domains/auth/**` — use events for auth-related flows17- `src/domains/payments/**` — use events for payment flows18- `src/domains/users/**` — use events or shared types for user data1920## Public API21Only these exports are visible to other domains:22- `OrderService` — the main service class23- `OrderAggregate` — the order entity type24- `OrderStatus` — the status enum25- `OrderCreated`, `OrderCancelled` — domain events2627## Repository Rules28- Only access: orders, order_items, order_history tables29- Never JOIN with tables from other domains30- Use the shared event bus for data from other domains3132## Domain Event Patterns33- Publish events for state changes: OrderCreated, OrderUpdated, OrderCancelled34- Subscribe to external events: PaymentCompleted, PaymentFailed35- Events are defined in src/shared/events/orderEvents.tsCommon mistakes when making Cursor Respect Domain Boundaries
Why it's a problem: Not listing forbidden imports explicitly per domain
How to avoid: Create per-domain .mdc files with explicit Allowed Imports and Forbidden Imports sections.
Why it's a problem: Using @codebase instead of @folder for domain-scoped work
How to avoid: Use @folder references scoped to the specific domain directory and shared types only.
Why it's a problem: Sharing database repositories across domains
How to avoid: Add a rule requiring each domain to own its own tables. Cross-domain data flows through events or API calls, never shared database queries.
Best practices
- Map all bounded contexts in .cursorrules with directories, allowed imports, and public APIs
- Use nested .cursor/rules/ in each domain directory for auto-attaching boundary enforcement
- Scope Composer sessions with @folder to prevent cross-domain context pollution
- Generate domain events for cross-domain communication instead of direct imports
- Define public API surfaces (index.ts exports) per domain and reference them in rules
- Keep shared types in src/shared/types/ rather than duplicating across domains
- Start new Composer sessions when switching between domains to reset context
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a DDD project with these bounded contexts: Auth, Orders, Payments, Users. Each is in src/domains/{name}/. Generate a cross-domain event system where orders publishes OrderCreated, payments subscribes and processes payment, then publishes PaymentCompleted. No direct imports between domains.
@src/domains/orders/ @src/shared/types/ Add a cancelOrder function to OrderService. Only use imports from the orders domain and shared types. Emit an OrderCancelled event through the shared event bus. Do NOT import from auth, payments, or users domains. Update order status and record cancellation reason.
Frequently asked questions
How does Cursor handle domain boundaries by default?
It does not. Cursor treats your entire codebase as flat context and will freely import across any directory. You must explicitly define boundaries in .cursorrules and per-domain rule files.
Can Cursor enforce DDD aggregate rules?
Yes, with explicit rules. Define your aggregates, their boundaries, and what can reference them in .cursor/rules/ files. Cursor will follow these constraints when generating code within the domain.
Should I use one .cursorrules file or per-domain rules?
Both. Use .cursorrules for the global domain map and cross-cutting rules. Use nested .cursor/rules/ directories in each domain for domain-specific constraints. The nested rules auto-attach when editing files in that domain.
How do I handle shared code between domains?
Create a src/shared/ directory for types, utilities, and the event bus. List it as an allowed import in every domain's rules. Never put domain-specific logic in shared — only truly cross-cutting concerns.
What if Cursor still imports across domain boundaries?
Rules can drift in long sessions. Start a new chat with Cmd+N and explicitly reference the domain folder with @folder. If the problem persists, copy the boundary rules directly into your prompt.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation