14 KiB
NestJS Implementation Plan for AutoStore
Overview
Implementation of AutoStore system using NestJS with TypeScript, following Clean Architecture principles. The system stores items with expiration dates and automatically orders new items when they expire.
Architecture Approach
- Clean Architecture with clear separation of concerns
- Domain-Driven Design with rich domain models
- Hexagonal Architecture with dependency inversion
- Repository Pattern for data persistence
- CQRS-like command/query separation
- Dependency Injection leveraging NestJS IoC container
Core Domain Logic
ItemExpirationSpec - Single Source of Truth for Expiration
File: src/domain/specifications/item-expiration.spec.ts
Purpose: Centralized expiration checking logic - the single source of truth for determining if items are expired
Key Methods:
isExpired(item: ItemEntity, currentTime: Date): boolean- Checks if item expiredgetSpec(currentTime: Date): Specification<ItemEntity>- Returns specification for repository queries
Place in the flow:
- Called by
AddItemCommand.execute()to check newly created items for immediate expiration - Called by
HandleExpiredItemsCommand.execute()to find expired items for processing - Used by
ItemRepository.findWhere()to query database for expired items
Detailed Implementation Plan
Domain Layer
1. Entities
File: src/domain/entities/item.entity.ts
Purpose: Core business entity representing an item
Key Methods:
constructor(id: ItemId, name: string, expirationDate: ExpirationDate, orderUrl: string, userId: UserId): void- Creates item with validation- Getters for all properties
Place in the flow:
- Created by
AddItemCommand.execute() - Retrieved by
ItemRepositorymethods - Passed to
ItemExpirationSpec.isExpired()for expiration checking
File: src/domain/entities/user.entity.ts
Purpose: User entity for item ownership and authentication purposes
Key Methods:
constructor(id: UserId, username: string, passwordHash: string): void- Creates user with validation- Getters for all properties
2. Value Objects
File: src/domain/value-objects/item-id.vo.ts
Purpose: Strong typing for item identifiers
Key Methods:
constructor(value: string): void- Validates UUID formatgetValue(): string- Returns string valueequals(other: ItemId): boolean- Compares with another ItemId
File: src/domain/value-objects/expiration-date.vo.ts
Purpose: Immutable expiration date with validation
Key Methods:
constructor(value: Date): void- Validates date format (allows past dates per business rules)getValue(): Date- Returns Date objectformat(): string- Returns ISO string format
Place in the flow:
- Used by
ItemEntityconstructor for type-safe date handling - Validated by
ItemExpirationSpec.isExpired()for expiration logic
3. Specifications
File: src/domain/specifications/specification.interface.ts
Purpose: Generic specification pattern interface
Key Methods:
isSatisfiedBy(candidate: T): boolean- Evaluates specificationgetSpec(): object- Returns specification object for repository implementation
Place in the flow:
- Implemented by
ItemExpirationSpecfor type-safe specifications - Used by
ItemRepository.findWhere()for database queries
Application Layer
4. Commands
File: src/application/commands/add-item.command.ts
Purpose: Use case for creating new items with expiration handling
Key Methods:
constructor(itemRepo: IItemRepository, orderService: IOrderService, timeProvider: ITimeProvider, expirationSpec: ItemExpirationSpec, logger: Logger): void- Dependency injectionexecute(name: string, expirationDate: string, orderUrl: string, userId: string): Promise<string | null>- Creates item, handles expired items immediately
Flow:
ItemsController.createItem()callsAddItemCommand.execute()- Creates
ItemEntitywith validated data - Calls
ItemExpirationSpec.isExpired()to check if item is expired - If expired:
- calls
OrderHttpService.orderItem() - returns item ID (business rule: expired items trigger ordering but still return ID field that might be empty or invalid)
- calls
- If not expired: calls
ItemRepository.save()and returns item ID
File: src/application/commands/handle-expired-items.command.ts
Purpose: Background command to process expired items
Key Methods:
constructor(itemRepo: IItemRepository, orderService: IOrderService, timeProvider: ITimeProvider, expirationSpec: ItemExpirationSpec, logger: Logger): void- Dependency injectionexecute(): Promise<void>- Finds and processes all expired items
Flow:
ExpiredItemsScheduler.handleCron()callsHandleExpiredItemsCommand.execute()- Gets current time from
ITimeProvider - Calls
ItemExpirationSpec.getSpec()to get expiration specification - Calls
ItemRepository.findWhere()to find expired items - For each expired item: calls
OrderHttpService.orderItem()thenItemRepository.delete()
File: src/application/commands/delete-item.command.ts
Purpose: Use case for deleting user items
Key Methods:
constructor(itemRepo: IItemRepository, logger: Logger): void- Dependency injectionexecute(itemId: string, userId: string): Promise<void>- Validates ownership and deletes item
Flow:
ItemsController.deleteItem()callsDeleteItemCommand.execute()- Calls
ItemRepository.findById()to retrieve item - Validates ownership by comparing user IDs
- Calls
ItemRepository.delete()to remove item
File: src/application/commands/login-user.command.ts
Purpose: User authentication use case
Key Methods:
constructor(authService: IAuthService, logger: Logger): void- Dependency injectionexecute(username: string, password: string): Promise<string>- Authenticates and returns JWT token
5. Queries
File: src/application/queries/get-item.query.ts
Purpose: Retrieves single item by ID with authorization
Key Methods:
constructor(itemRepo: IItemRepository, logger: Logger): void- Dependency injectionexecute(itemId: string, userId: string): Promise<ItemEntity>- Validates ownership and returns item
Flow:
ItemsController.getItem()callsGetItemQuery.execute()- Calls
ItemRepository.findById()to retrieve item - Validates ownership by comparing user IDs
- Returns item entity
File: src/application/queries/list-items.query.ts
Purpose: Retrieves all items for authenticated user
Key Methods:
constructor(itemRepo: IItemRepository, logger: Logger): void- Dependency injectionexecute(userId: string): Promise<ItemEntity[]>- Returns user's items
Flow:
ItemsController.listItems()callsListItemsQuery.execute()- Calls
ItemRepository.findByUserId()to retrieve user's items - Returns array of item entities
6. DTOs
File: src/application/dto/create-item.dto.ts
Purpose: Request validation for item creation
Key Properties:
name: string- Item name (min: 1, max: 255)expirationDate: string- ISO date string (future date validation)orderUrl: string- Valid URL format
Place in the flow:
- Used by
ItemsController.createItem()for request body validation
File: src/application/dto/item-response.dto.ts
Purpose: Standardized item response format
Key Properties:
id: string- Item IDname: string- Item nameexpirationDate: string- ISO date stringorderUrl: string- Order URLuserId: string- Owner user IDcreatedAt: string- Creation timestamp
Place in the flow:
- Used by all item controller methods for response transformation
Infrastructure Layer
7. Repositories
File: src/infrastructure/repositories/file-item-repository.ts
Purpose: File-based implementation of item repository using JSON files
Key Methods:
save(item: ItemEntity): Promise<void>- Persists item entityfindById(id: ItemId): Promise<ItemEntity | null>- Finds by IDfindByUserId(userId: UserId): Promise<ItemEntity[]>- Finds by userfindWhere(spec: Specification<ItemEntity>): Promise<ItemEntity[]>- Finds by specification usingItemExpirationSpecdelete(id: ItemId): Promise<void>- Deletes itemexists(id: ItemId): Promise<boolean>- Checks existence
Place in the flow:
- Called by all commands and queries for data persistence and retrieval
- Uses
ItemExpirationSpecfor finding expired items
8. HTTP Services
File: src/infrastructure/http/order-http.service.ts
Purpose: HTTP implementation of order service
Key Methods:
constructor(httpService: HttpService, logger: Logger): void- Dependency injectionorderItem(item: ItemEntity): Promise<void>- Sends POST request to order URL
Place in the flow:
- Called by
AddItemCommand.execute()for expired items - Called by
HandleExpiredItemsCommand.execute()for batch processing
9. Authentication
File: src/infrastructure/auth/jwt-auth.service.ts
Purpose: JWT implementation of authentication service
Key Methods:
constructor(userRepo: IUserRepository, jwtService: JwtService, configService: ConfigService, logger: Logger): void- Dependency injectionauthenticate(username: string, password: string): Promise<string | null>- Validates credentials and generates JWTvalidateToken(token: string): Promise<boolean>- Validates JWT tokengetUserIdFromToken(token: string): Promise<string | null>- Extracts user ID from token
Place in the flow:
- Called by
LoginUserCommand.execute()for user authentication - Used by
JwtAuthGuardfor route protection
Presentation Layer
10. Controllers
File: src/presentation/controllers/items.controller.ts
Purpose: REST API endpoints for item management
Key Methods:
constructor(addItemCmd: AddItemCommand, getItemQry: GetItemQuery, listItemsQry: ListItemsQuery, deleteItemCmd: DeleteItemCommand): void- Dependency injectioncreateItem(@Body() dto: CreateItemDto, @Req() req: Request): Promise<ItemResponseDto>- POST /itemsgetItem(@Param('id') id: string, @Req() req: Request): Promise<ItemResponseDto>- GET /items/:idlistItems(@Req() req: Request): Promise<ItemResponseDto[]>- GET /itemsdeleteItem(@Param('id') id: string, @Req() req: Request): Promise<void>- DELETE /items/:id
Flow:
- Receives HTTP requests and validates input
- Calls appropriate commands/queries based on HTTP method
- Returns standardized responses with DTOs
File: src/presentation/controllers/auth.controller.ts
Purpose: Authentication endpoints
Key Methods:
constructor(loginUserCmd: LoginUserCommand): void- Dependency injectionlogin(@Body() dto: LoginDto): Promise<{ token: string }>- POST /login
11. Guards
File: src/presentation/guards/jwt-auth.guard.ts
Purpose: JWT authentication route protection
Key Methods:
constructor(jwtAuthService: IJwtAuthService, logger: Logger): void- Dependency injectioncanActivate(context: ExecutionContext): Promise<boolean>- Validates JWT and attaches user to request
Place in the flow:
- Applied to all protected routes by NestJS Guard System
- Uses
JwtAuthServicefor token validation
Background Processing
File: src/infrastructure/services/expired-items-scheduler.service.ts
Purpose: Scheduled job for processing expired items using NestJS scheduler
Key Methods:
constructor(handleExpiredItemsCmd: HandleExpiredItemsCommand): void- Dependency injectiononModuleInit(): Promise<void>- Processes expired items on application startuphandleExpiredItemsCron(): Promise<void>- Runs every minute (@Cron(CronExpression.EVERY_MINUTE))handleExpiredItemsDaily(): Promise<void>- Runs every day at midnight (@Cron('0 0 * * *'))
Flow:
- On startup:
onModuleInit()immediately callsHandleExpiredItemsCommand.execute() - Every minute:
handleExpiredItemsCron()processes expired items - Every midnight:
handleExpiredItemsDaily()processes expired items - All methods use try-catch to continue operation despite errors
- Comprehensive logging for monitoring and debugging
Configuration:
- Requires
ScheduleModule.forRoot()in AppModule imports - Uses
@nestjs/schedulepackage for cron expressions - Implements
OnModuleInitfor startup processing
Complete Flow Summary
Item Creation Flow
POST /items
├── JwtAuthGuard (authentication)
├── CreateItemDto validation
├── ItemsController.createItem()
│ ├── AddItemCommand.execute()
│ │ ├── ItemEntity constructor (validation)
│ │ ├── ItemExpirationSpec.isExpired() ← SINGLE SOURCE OF TRUTH
│ │ ├── If expired: OrderHttpService.orderItem()
│ │ └── If not expired: ItemRepository.save()
│ └── ItemResponseDto transformation
└── HTTP response
Expired Items Processing Flow
Cron Job (every minute)
└── ExpiredItemsScheduler.handleCron()
└── HandleExpiredItemsCommand.execute()
├── ITimeProvider.now()
├── ItemExpirationSpec.getSpec() ← SINGLE SOURCE OF TRUTH
├── ItemRepository.findWhere() (using spec)
├── For each expired item:
│ ├── OrderHttpService.orderItem()
│ └── ItemRepository.delete()
└── Logging
Item Retrieval Flow
GET /items/:id
├── JwtAuthGuard (authentication)
├── ItemsController.getItem()
│ ├── GetItemQuery.execute()
│ │ ├── ItemRepository.findById()
│ │ ├── Ownership validation
│ │ └── Return ItemEntity
│ └── ItemResponseDto transformation
└── HTTP response
Key Design Principles
- Single Source of Truth:
ItemExpirationSpecis the only component that determines expiration logic - Clear Flow: Each component has a well-defined place in the execution chain
- Dependency Inversion: High-level modules don't depend on low-level modules
- Separation of Concerns: Each layer has distinct responsibilities
- Testability: All components can be tested in isolation
This implementation plan ensures consistent development regardless of the implementer, providing clear flow definitions and emphasizing ItemExpirationSpec as the centralized source for expiration logic.