Multiple implementations of the same back-end application. The aim is to provide quick, side-by-side comparisons of different technologies (languages, frameworks, libraries) while preserving consistent business logic across all implementations.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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 expired
  • getSpec(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 ItemRepository methods
  • 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 format
  • getValue(): string - Returns string value
  • equals(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 is in future
  • getValue(): Date - Returns Date object
  • format(): string - Returns ISO string format

Place in the flow:

  • Used by ItemEntity constructor 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 specification
  • getSpec(): object - Returns specification object for repository implementation

Place in the flow:

  • Implemented by ItemExpirationSpec for 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 injection
  • execute(name: string, expirationDate: string, orderUrl: string, userId: string): Promise<string | null> - Creates item, handles expired items immediately

Flow:

  1. ItemsController.createItem() calls AddItemCommand.execute()
  2. Creates ItemEntity with validated data
  3. Calls ItemExpirationSpec.isExpired() to check if item is expired
  4. If expired:
    • calls OrderHttpService.orderItem()
    • DO NOT save to repository
    • returns null
  5. 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 injection
  • execute(): Promise<void> - Finds and processes all expired items

Flow:

  1. ExpiredItemsScheduler.handleCron() calls HandleExpiredItemsCommand.execute()
  2. Gets current time from ITimeProvider
  3. Calls ItemExpirationSpec.getSpec() to get expiration specification
  4. Calls ItemRepository.findWhere() to find expired items
  5. For each expired item: calls OrderHttpService.orderItem() then ItemRepository.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 injection
  • execute(itemId: string, userId: string): Promise<void> - Validates ownership and deletes item

Flow:

  1. ItemsController.deleteItem() calls DeleteItemCommand.execute()
  2. Calls ItemRepository.findById() to retrieve item
  3. Validates ownership by comparing user IDs
  4. 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 injection
  • execute(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 injection
  • execute(itemId: string, userId: string): Promise<ItemEntity> - Validates ownership and returns item

Flow:

  1. ItemsController.getItem() calls GetItemQuery.execute()
  2. Calls ItemRepository.findById() to retrieve item
  3. Validates ownership by comparing user IDs
  4. 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 injection
  • execute(userId: string): Promise<ItemEntity[]> - Returns user's items

Flow:

  1. ItemsController.listItems() calls ListItemsQuery.execute()
  2. Calls ItemRepository.findByUserId() to retrieve user's items
  3. 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 ID
  • name: string - Item name
  • expirationDate: string - ISO date string
  • orderUrl: string - Order URL
  • userId: string - Owner user ID
  • createdAt: string - Creation timestamp

Place in the flow:

  • Used by all item controller methods for response transformation

Infrastructure Layer

7. Repositories

File: src/infrastructure/persistence/repositories/item-repository.impl.ts Purpose: TypeORM implementation of item repository

Key Methods:

  • save(item: ItemEntity): Promise<void> - Persists item entity
  • findById(id: ItemId): Promise<ItemEntity | null> - Finds by ID
  • findByUserId(userId: UserId): Promise<ItemEntity[]> - Finds by user
  • findWhere(spec: Specification<ItemEntity>): Promise<ItemEntity[]> - Finds by specification using ItemExpirationSpec
  • delete(id: ItemId): Promise<void> - Deletes item
  • exists(id: ItemId): Promise<boolean> - Checks existence

Place in the flow:

  • Called by all commands and queries for data persistence and retrieval
  • Uses ItemExpirationSpec for 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 injection
  • orderItem(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 injection
  • authenticate(username: string, password: string): Promise<string | null> - Validates credentials and generates JWT
  • validateToken(token: string): Promise<boolean> - Validates JWT token
  • getUserIdFromToken(token: string): Promise<string | null> - Extracts user ID from token

Place in the flow:

  • Called by LoginUserCommand.execute() for user authentication
  • Used by JwtAuthGuard for 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 injection
  • createItem(@Body() dto: CreateItemDto, @Req() req: Request): Promise<ItemResponseDto> - POST /items
  • getItem(@Param('id') id: string, @Req() req: Request): Promise<ItemResponseDto> - GET /items/:id
  • listItems(@Req() req: Request): Promise<ItemResponseDto[]> - GET /items
  • deleteItem(@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 injection
  • login(@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 injection
  • canActivate(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 JwtAuthService for token validation

Background Processing

File: src/infrastructure/scheduler/expired-items.scheduler.ts Purpose: Scheduled job for processing expired items

Key Methods:

  • constructor(handleExpiredItemsCmd: HandleExpiredItemsCommand, logger: Logger): void - Dependency injection
  • handleCron(): Promise<void> - Runs every minute to check for expired items

Flow:

  1. Upon application startup: Immediately invoke HandleExpiredItemsCommand.execute()
  2. Start cron scheduler (every minute)
  3. NestJS Scheduler triggers handleCron() every minute
  4. Calls HandleExpiredItemsCommand.execute() to process expired items
  5. Logs processing results and errors

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

  1. Single Source of Truth: ItemExpirationSpec is the only component that determines expiration logic
  2. Clear Flow: Each component has a well-defined place in the execution chain
  3. Dependency Inversion: High-level modules don't depend on low-level modules
  4. Separation of Concerns: Each layer has distinct responsibilities
  5. 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.