# Inquiries Module - Complete Exploration **GoodGo Platform Backend API** **Module Location:** `apps/api/src/modules/inquiries/` **Total Lines of Code:** 1,212 lines **Date Explored:** April 11, 2026 --- ## πŸ“‹ TABLE OF CONTENTS 1. [Directory Structure](#directory-structure) 2. [Complete File Listing](#complete-file-listing) 3. [Module Architecture](#module-architecture) 4. [Key Classes & Handlers](#key-classes--handlers) 5. [DDD Layer Analysis](#ddd-layer-analysis) 6. [Test Files Summary](#test-files-summary) --- ## πŸ“ DIRECTORY STRUCTURE ``` apps/api/src/modules/inquiries/ β”œβ”€β”€ application/ β”‚ β”œβ”€β”€ __tests__/ β”‚ β”‚ β”œβ”€β”€ create-inquiry.handler.spec.ts β”‚ β”‚ β”œβ”€β”€ get-inquiries-by-agent.handler.spec.ts β”‚ β”‚ β”œβ”€β”€ get-inquiries-by-listing.handler.spec.ts β”‚ β”‚ └── mark-inquiry-read.handler.spec.ts β”‚ β”œβ”€β”€ commands/ β”‚ β”‚ β”œβ”€β”€ create-inquiry/ β”‚ β”‚ β”‚ β”œβ”€β”€ create-inquiry.command.ts β”‚ β”‚ β”‚ └── create-inquiry.handler.ts β”‚ β”‚ └── mark-inquiry-read/ β”‚ β”‚ β”œβ”€β”€ mark-inquiry-read.command.ts β”‚ β”‚ └── mark-inquiry-read.handler.ts β”‚ └── queries/ β”‚ β”œβ”€β”€ get-inquiries-by-agent/ β”‚ β”‚ β”œβ”€β”€ get-inquiries-by-agent.handler.ts β”‚ β”‚ └── get-inquiries-by-agent.query.ts β”‚ └── get-inquiries-by-listing/ β”‚ β”œβ”€β”€ get-inquiries-by-listing.handler.ts β”‚ └── get-inquiries-by-listing.query.ts β”œβ”€β”€ domain/ β”‚ β”œβ”€β”€ __tests__/ β”‚ β”‚ └── inquiry-domain.spec.ts β”‚ β”œβ”€β”€ entities/ β”‚ β”‚ └── inquiry.entity.ts β”‚ β”œβ”€β”€ events/ β”‚ β”‚ β”œβ”€β”€ inquiry-created.event.ts β”‚ β”‚ └── inquiry-read.event.ts β”‚ └── repositories/ β”‚ β”œβ”€β”€ inquiry.repository.ts β”‚ └── inquiry-read.dto.ts β”œβ”€β”€ infrastructure/ β”‚ └── repositories/ β”‚ └── prisma-inquiry.repository.ts β”œβ”€β”€ presentation/ β”‚ β”œβ”€β”€ __tests__/ β”‚ β”‚ └── inquiries.controller.spec.ts β”‚ β”œβ”€β”€ controllers/ β”‚ β”‚ └── inquiries.controller.ts β”‚ └── dto/ β”‚ β”œβ”€β”€ create-inquiry.dto.ts β”‚ └── list-inquiries.dto.ts β”œβ”€β”€ index.ts └── inquiries.module.ts ``` --- ## πŸ“„ COMPLETE FILE LISTING ### **APPLICATION LAYER** (8 files) #### Commands (4 files) | File Path | Type | Description | |-----------|------|-------------| | `application/commands/create-inquiry/create-inquiry.command.ts` | Command | DTO for creating inquiry - contains userId, listingId, message, phone | | `application/commands/create-inquiry/create-inquiry.handler.ts` | Command Handler | Executes CreateInquiryCommand; validates listing exists, creates entity, publishes event | | `application/commands/mark-inquiry-read/mark-inquiry-read.command.ts` | Command | DTO for marking inquiry as read - contains inquiryId, agentUserId | | `application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts` | Command Handler | Executes MarkInquiryReadCommand; validates permissions, marks inquiry read, publishes event | #### Queries (4 files) | File Path | Type | Description | |-----------|------|-------------| | `application/queries/get-inquiries-by-agent/get-inquiries-by-agent.query.ts` | Query | DTO for listing agent's inquiries - contains agentUserId, page, limit | | `application/queries/get-inquiries-by-agent/get-inquiries-by-agent.handler.ts` | Query Handler | Resolves agent by userId, delegates to repository findByAgent | | `application/queries/get-inquiries-by-listing/get-inquiries-by-listing.query.ts` | Query | DTO for listing inquiries by listing - contains listingId, page, limit | | `application/queries/get-inquiries-by-listing/get-inquiries-by-listing.handler.ts` | Query Handler | Delegates directly to repository findByListing | #### Application Tests (4 files) | File Path | Type | Test Count | Coverage | |-----------|------|-----------|----------| | `application/__tests__/create-inquiry.handler.spec.ts` | Jest | 4 tests | Happy path, missing listing, domain event publishing | | `application/__tests__/mark-inquiry-read.handler.spec.ts` | Jest | 5 tests | Happy path, missing inquiry, missing listing, forbidden access, agent not found | | `application/__tests__/get-inquiries-by-listing.handler.spec.ts` | Jest | 2 tests | Paginated results, empty results | | `application/__tests__/get-inquiries-by-agent.handler.spec.ts` | Jest | 2 tests | Paginated results, agent not found | --- ### **DOMAIN LAYER** (6 files) #### Entities (1 file) | File Path | Type | Description | |-----------|------|-------------| | `domain/entities/inquiry.entity.ts` | Aggregate Root | Core domain entity with id, listingId, userId, message, phone, isRead; methods: createNew(), markAsRead() | #### Events (2 files) | File Path | Type | Description | |-----------|------|-------------| | `domain/events/inquiry-created.event.ts` | Domain Event | Published when inquiry is created - contains aggregateId, listingId, userId | | `domain/events/inquiry-read.event.ts` | Domain Event | Published when inquiry is marked read - contains aggregateId, listingId, userId | #### Repositories (2 files) | File Path | Type | Description | |-----------|------|-------------| | `domain/repositories/inquiry.repository.ts` | Interface + Symbol | IInquiryRepository interface with 6 methods; INQUIRY_REPOSITORY symbol for DI; PaginatedResult interface | | `domain/repositories/inquiry-read.dto.ts` | Interface | Read DTO for queries - includes listing title, user details, timestamps | #### Domain Tests (1 file) | File Path | Type | Test Count | Coverage | |-----------|------|-----------|----------| | `domain/__tests__/inquiry-domain.spec.ts` | Jest | 5 tests | Entity creation, null phone, domain events, markAsRead | --- ### **INFRASTRUCTURE LAYER** (1 file) #### Repository Implementation | File Path | Type | Description | |-----------|------|-------------| | `infrastructure/repositories/prisma-inquiry.repository.ts` | Service | Implements IInquiryRepository using Prisma; 6 methods: findById, save, markAsRead, findByListing, findByAgent, countUnreadByAgent | --- ### **PRESENTATION LAYER** (5 files) #### Controller (1 file) | File Path | Type | Routes | Auth | |-----------|------|--------|------| | `presentation/controllers/inquiries.controller.ts` | NestJS Controller | POST /, GET /listing/:id, GET /agent/me, PATCH /:id/read | JWT + RBAC | **Endpoints:** - `POST /inquiries` - Create inquiry (BUYER) - `GET /inquiries/listing/:listingId` - Get inquiries by listing - `GET /inquiries/agent/me` - Get inquiries for logged-in agent (AGENT only) - `PATCH /inquiries/:id/read` - Mark inquiry as read (AGENT only) #### Data Transfer Objects (2 files) | File Path | Type | Properties | Validation | |-----------|------|-----------|------------| | `presentation/dto/create-inquiry.dto.ts` | DTO | listingId, message, phone? | Required string, max 2000 char message | | `presentation/dto/list-inquiries.dto.ts` | DTO | page?, limit? | Int, min 1, max 100, defaults 1/20 | #### Presentation Tests (1 file) | File Path | Type | Test Count | Coverage | |-----------|------|-----------|----------| | `presentation/__tests__/inquiries.controller.spec.ts` | Jest | 6 tests | All 4 endpoints, null phone handling, pagination defaults | --- ### **MODULE FILES** (2 files) | File Path | Type | Description | |-----------|------|-------------| | `inquiries.module.ts` | NestJS Module | Exports: InquiriesController; Providers: PrismaInquiryRepository, 2 command handlers, 2 query handlers | | `index.ts` | Barrel Export | Exports: InquiriesModule, INQUIRY_REPOSITORY symbol, IInquiryRepository interface, InquiryEntity | --- ## πŸ—οΈ MODULE ARCHITECTURE ### **Design Pattern: CQRS + Event Sourcing + DDD** ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PRESENTATION LAYER β”‚ β”‚ Controllers + DTOs (inquiries.controller.ts) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” ↓ ↓ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Commandsβ”‚ β”‚ Queries β”‚ β”‚ Validationβ”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ APPLICATION LAYER β”‚ β”‚ Handlers + Services β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β” ↓ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Commands β”‚ β”‚ Queries β”‚ β”‚ Handlers β”‚ β”‚ Handlers β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ DOMAIN LAYER β”‚ β”‚ Entities + Events β”‚ β”‚ Repository Interface β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ INFRASTRUCTURE LAYER β”‚ β”‚ Prisma Repository β”‚ β”‚ Database Queries β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### **Data Flow** **Creating Inquiry:** ``` POST /inquiries (CreateInquiryDto) β†’ InquiriesController.createInquiry() β†’ CommandBus.execute(CreateInquiryCommand) β†’ CreateInquiryHandler.execute() - Validate listing exists (Prisma) - Create InquiryEntity - Save to repository (Prisma) - Publish InquiryCreatedEvent via EventBus β†’ Return { id, listingId, createdAt } ``` **Marking as Read:** ``` PATCH /inquiries/:id/read (agent only) β†’ InquiriesController.markAsRead() β†’ CommandBus.execute(MarkInquiryReadCommand) β†’ MarkInquiryReadHandler.execute() - Find inquiry entity - Verify agent is listing owner - Call entity.markAsRead() - Update in repository - Publish InquiryReadEvent via EventBus β†’ Return { success: true } ``` **Getting Inquiries:** ``` GET /inquiries/listing/:id or /agent/me (with pagination) β†’ InquiriesController.getByListing() or getMyInquiries() β†’ QueryBus.execute(GetInquiriesByListingQuery or GetInquiriesByAgentQuery) β†’ Handler delegates to repository β†’ Repository.findByListing() or findByAgent() - Queries Prisma with joins - Maps to InquiryReadDto - Returns PaginatedResult β†’ Return paginated data ``` --- ## πŸ”‘ KEY CLASSES & HANDLERS ### **Domain Entity: InquiryEntity** ```typescript export class InquiryEntity extends AggregateRoot { // Properties private _listingId: string; private _userId: string; private _message: string; private _phone: string | null; private _isRead: boolean; // Factory Method static createNew(id, listingId, userId, message, phone): InquiryEntity β†’ Creates new inquiry with isRead=false β†’ Emits InquiryCreatedEvent // Business Logic markAsRead(): void β†’ Sets isRead to true β†’ Emits InquiryReadEvent } ``` ### **Command Handlers** #### CreateInquiryHandler ```typescript class CreateInquiryHandler implements ICommandHandler { async execute(command: CreateInquiryCommand): Promise { // 1. Validate listing exists const listing = await prisma.listing.findUnique(...) if (!listing) throw NotFoundException // 2. Create entity with factory const inquiry = InquiryEntity.createNew(...) // 3. Persist to database await inquiryRepo.save(inquiry) // 4. Publish domain events const events = inquiry.clearDomainEvents() events.forEach(e => eventBus.publish(e)) return { id, listingId, createdAt } } } ``` #### MarkInquiryReadHandler ```typescript class MarkInquiryReadHandler implements ICommandHandler { async execute(command: MarkInquiryReadCommand): Promise { // 1. Load aggregate root const inquiry = await inquiryRepo.findById(...) if (!inquiry) throw NotFoundException // 2. Verify authorization const listing = await prisma.listing.findUnique(...) const agent = await prisma.agent.findUnique(...) if (agent.id !== listing.agentId) throw ForbiddenException // 3. Update domain state inquiry.markAsRead() // 4. Persist state await inquiryRepo.markAsRead(...) // 5. Publish events const events = inquiry.clearDomainEvents() events.forEach(e => eventBus.publish(e)) } } ``` ### **Query Handlers** #### GetInquiriesByListingHandler ```typescript class GetInquiriesByListingHandler implements IQueryHandler { async execute(query: GetInquiriesByListingQuery): Promise> { return this.inquiryRepo.findByListing( query.listingId, query.page, query.limit ) } } ``` #### GetInquiriesByAgentHandler ```typescript class GetInquiriesByAgentHandler implements IQueryHandler { async execute(query: GetInquiriesByAgentQuery): Promise> { // 1. Resolve agent ID from user ID const agent = await prisma.agent.findUnique({ where: { userId } }) if (!agent) throw NotFoundException // 2. Delegate to repository return this.inquiryRepo.findByAgent(agent.id, page, limit) } } ``` ### **Repository Implementation** #### PrismaInquiryRepository **Methods:** 1. `findById(id)` - Single inquiry lookup 2. `save(entity)` - Create inquiry 3. `markAsRead(id)` - Update isRead flag 4. `findByListing(listingId, page, limit)` - Paginated search with joins 5. `findByAgent(agentId, page, limit)` - Paginated search via listing agent 6. `countUnreadByAgent(agentId)` - Unread count aggregation **Prisma Relations Used:** - `inquiry.listing` β†’ property (for title) - `inquiry.user` β†’ fullName, phone - `listing.agentId` β†’ agent filter - Pagination: skip/take with orderBy descending --- ## 🎯 DDD LAYER STRUCTURE ### **DOMAIN LAYER** (`domain/`) **Purpose:** Pure business logic, independent of frameworks **Contains:** - **Entities** - `inquiry.entity.ts` (Aggregate Root) - Pure TypeScript class - Encapsulates business rules (isRead flag, field validation) - Factory method for creation - Methods for state transitions - Domain events collection - **Events** - `inquiry-created.event.ts`, `inquiry-read.event.ts` - Record significant business occurrences - Used for event sourcing & audit trails - Plain data classes - **Repositories** - `inquiry.repository.ts`, `inquiry-read.dto.ts` - Interface defines contract (dependency inversion) - Symbol for DI token - Read DTO separate from write entity - Pagination result interface **Isolation:** - Zero NestJS dependencies - Zero database dependencies - Zero external service dependencies --- ### **APPLICATION LAYER** (`application/`) **Purpose:** Use cases & coordination **Contains:** - **Commands** - Mutable operations - `CreateInquiryCommand` - Input DTO - `MarkInquiryReadCommand` - Input DTO - Handlers orchestrate domain operations - **Queries** - Immutable reads - `GetInquiriesByListingQuery` - `GetInquiriesByAgentQuery` - Handlers delegate to repository **Responsibilities:** - Validate preconditions (listing exists, agent authorized) - Coordinate domain entity operations - Publish domain events - Handle cross-cutting concerns (logging, etc.) **NestJS Integration:** - `@CommandHandler()` decorator - `@QueryHandler()` decorator - Dependency injection via constructor --- ### **INFRASTRUCTURE LAYER** (`infrastructure/`) **Purpose:** Database & persistence details **Contains:** - **Repositories** - `prisma-inquiry.repository.ts` - Implements domain repository interface - Uses Prisma client for queries - Maps database records ↔ domain entities - Handles pagination logic **Responsibilities:** - Query building - Result mapping - Pagination calculation - Join relationships - Database-specific optimizations **Isolation:** - Swappable implementations (could use TypeORM, MongoDB, etc.) - Domain code unaffected by database changes --- ### **PRESENTATION LAYER** (`presentation/`) **Purpose:** HTTP interface & I/O **Contains:** - **Controllers** - `inquiries.controller.ts` - NestJS `@Controller` decorator - HTTP route handlers - Dispatch to CQRS bus - Return HTTP responses - **DTOs** - `create-inquiry.dto.ts`, `list-inquiries.dto.ts` - Input validation (class-validator) - Swagger documentation (@ApiProperty) - Separate from domain entities **Responsibilities:** - Route handling - Request validation - Authentication/Authorization - Response formatting - API documentation **NestJS Integration:** - Decorators: `@Post`, `@Get`, `@Patch` - Guards: `JwtAuthGuard`, `RolesGuard` - Middleware: `@CurrentUser`, `@Roles` --- ## πŸ“Š TEST FILES SUMMARY ### **Test Statistics** | Layer | File | Tests | Type | |-------|------|-------|------| | Domain | `domain/__tests__/inquiry-domain.spec.ts` | 5 | Jest | | Application | `application/__tests__/create-inquiry.handler.spec.ts` | 4 | Jest | | Application | `application/__tests__/mark-inquiry-read.handler.spec.ts` | 5 | Jest | | Application | `application/__tests__/get-inquiries-by-listing.handler.spec.ts` | 2 | Jest | | Application | `application/__tests__/get-inquiries-by-agent.handler.spec.ts` | 2 | Jest | | Presentation | `presentation/__tests__/inquiries.controller.spec.ts` | 6 | Jest | | **TOTAL** | **6 files** | **24 tests** | Jest + Vitest | ### **Test Coverage by File** #### Domain Tests (`inquiry-domain.spec.ts`) - 5 tests ``` βœ“ InquiryEntity.createNew() with phone βœ“ InquiryEntity.createNew() with null phone βœ“ createNew() emits InquiryCreatedEvent βœ“ markAsRead() sets flag to true βœ“ markAsRead() emits InquiryReadEvent ``` #### CreateInquiryHandler Tests - 4 tests ``` βœ“ Creates inquiry successfully βœ“ Throws NotFoundException for missing listing βœ“ Publishes domain events after saving ``` #### MarkInquiryReadHandler Tests - 5 tests ``` βœ“ Marks inquiry as read successfully βœ“ Throws NotFoundException when inquiry not found βœ“ Throws NotFoundException when listing not found βœ“ Throws ForbiddenException when user not agent βœ“ Throws ForbiddenException when agent not found ``` #### GetInquiriesByListingHandler Tests - 2 tests ``` βœ“ Returns paginated results βœ“ Returns empty data when no inquiries ``` #### GetInquiriesByAgentHandler Tests - 2 tests ``` βœ“ Returns paginated results βœ“ Throws NotFoundException for non-agent user ``` #### InquiriesController Tests - 6 tests ``` βœ“ POST creates inquiry with command dispatch βœ“ POST passes null phone when not provided βœ“ GET /listing dispatches query with defaults βœ“ GET /listing passes custom pagination βœ“ GET /agent/me dispatches agent query βœ“ PATCH marks inquiry and returns success ``` --- ## πŸ” SUMMARY STATISTICS | Metric | Count | |--------|-------| | **Total Files** | 25 | | **Source Files (.ts, excluding tests)** | 19 | | **Test Files** | 6 | | **Total Lines of Code** | 1,212 | | **Commands** | 2 | | **Queries** | 2 | | **Command Handlers** | 2 | | **Query Handlers** | 2 | | **Domain Events** | 2 | | **HTTP Endpoints** | 4 | | **DTOs** | 2 | | **Interfaces** | 3 (IInquiryRepository, PaginatedResult, InquiryReadDto) | | **Test Suites** | 6 | | **Test Cases** | 24 | --- ## πŸ› οΈ KEY DEPENDENCIES **External Packages:** - `@nestjs/common` - Framework - `@nestjs/cqrs` - CQRS bus - `@paralleldrive/cuid2` - ID generation - `@prisma/client` - ORM - `class-validator` - DTO validation - `@nestjs/swagger` - API documentation **Internal Modules:** - `@modules/shared` - AggregateRoot, DomainEvent, exceptions - `@modules/auth` - JwtPayload, JwtAuthGuard, RolesGuard --- ## πŸ” Security & Authorization **Authentication:** - All endpoints require `@UseGuards(JwtAuthGuard)` - JWT token extracted via `@CurrentUser()` decorator **Authorization:** - `GET /agent/me` β†’ `@Roles('AGENT')` enforced - `PATCH /:id/read` β†’ `@Roles('AGENT')` enforced - **MarkInquiryReadHandler** verifies: - Inquiry exists - Listing exists - User is registered as agent - Agent owns the listing --- ## πŸ“ API CONTRACTS ### POST /inquiries ``` Request: { listingId, message, phone? } Response: { id, listingId, createdAt } Status: 201 Created | 400 Bad Request | 401 Unauthorized | 404 Not Found ``` ### GET /inquiries/listing/:listingId ``` Request: Query: page?, limit? Response: PaginatedResult Status: 200 OK | 401 Unauthorized ``` ### GET /inquiries/agent/me ``` Request: Query: page?, limit? Response: PaginatedResult Status: 200 OK | 401 Unauthorized | 403 Forbidden ``` ### PATCH /inquiries/:id/read ``` Request: (no body) Response: { success: boolean } Status: 200 OK | 401 Unauthorized | 403 Forbidden | 404 Not Found ``` --- ## πŸŽ“ ARCHITECTURAL INSIGHTS ### **Strengths** 1. **Clean Architecture** - Clear separation of concerns across layers 2. **CQRS Pattern** - Separate read/write paths enable scalability 3. **Domain-Driven Design** - Business logic in entities, not anemic models 4. **Event-Driven** - Domain events enable audit trails and event sourcing 5. **Testability** - Each layer independently testable with mocks 6. **Type Safety** - Full TypeScript with interfaces and strict types 7. **DI Framework** - NestJS provides dependency injection out of box ### **Design Decisions** 1. **InquiryEntity as Aggregate Root** - Encapsulates inquiry business rules - Controls state transitions via methods (createNew, markAsRead) - Collects domain events 2. **Repository Pattern** - Interface in domain, implementation in infrastructure - Allows swapping data sources without affecting business logic 3. **Separate Read/Write DTOs** - `CreateInquiryDto` (input) vs `InquiryReadDto` (output) - Enables flexible API evolution 4. **CQRS Handlers** - Commands handle mutations with authorization - Queries handle reads with filtering - Both independent, can be optimized separately 5. **Pagination Interface** - Consistent pagination across all list endpoints - Page + limit model with calculated totalPages --- **End of Exploration Report**