Use Case Documentation
Overview
Architecture Overview
Command Use Cases
1. Create Expense Use Case
import { Inject, Injectable } from '@nestjs/common';
import { ICreateExpenseUseCase } from '../interfaces/i.create-expense.use-case';
import { ExpenseDto } from '../dtos/expense.dto';
import { IExpenseRepository } from '../../domain/interfaces/i.expense.repository';
import { EXPENSE_REPOSITORY_TOKEN } from '../tokens/expense.repository.token';
import { ExpenseMapperToDomain } from '../mappers/expense.mapper-to-domain';
import { ExpenseMapperToDto } from '../mappers/expense.mapper-to-dto';
@Injectable()
export class CreateExpenseUseCase implements ICreateExpenseUseCase {
constructor(
@Inject(EXPENSE_REPOSITORY_TOKEN)
private readonly expenseRepository: IExpenseRepository
) {}
/**
* Execute expense creation with comprehensive validation and business rule enforcement
* @param expenseDto - Expense data to create
* @returns Promise resolving to created expense DTO
*/
async execute(expenseDto: ExpenseDto): Promise<ExpenseDto> {
// Convert DTO to domain entity
const expenseDomain = ExpenseMapperToDomain.map(expenseDto);
// Validate business rules
this.validateExpenseBusinessRules(expenseDomain);
// Calculate and validate total amount
this.validateAndCalculateTotalAmount(expenseDomain);
// Validate expense items
this.validateExpenseItems(expenseDomain);
// Persist the expense
const createdExpense = await this.expenseRepository.create(expenseDomain);
// Convert back to DTO and return
return ExpenseMapperToDto.map(createdExpense);
}
/**
* Validate core business rules for expense creation
* @param expense - Domain expense entity
*/
private validateExpenseBusinessRules(expense: IExpense): void {
// Validate expense date is not in the future beyond reasonable limits
const maxFutureDate = new Date();
maxFutureDate.setDate(maxFutureDate.getDate() + 30); // Allow 30 days in future
if (expense.expensedAt > maxFutureDate) {
throw new BadRequestException('Expense date cannot be more than 30 days in the future');
}
// Validate minimum expense amount
if (expense.totalValue.amount <= 0) {
throw new BadRequestException('Expense amount must be greater than zero');
}
// Validate maximum expense amount for fraud prevention
const maxAllowedAmount = 100000; // $100,000 limit
if (expense.totalValue.amount > maxAllowedAmount) {
throw new BadRequestException(`Expense amount exceeds maximum allowed limit of ${maxAllowedAmount}`);
}
}
/**
* Validate and calculate total amount from expense items
* @param expense - Domain expense entity
*/
private validateAndCalculateTotalAmount(expense: IExpense): void {
if (!expense.items || expense.items.length === 0) {
return; // Simple expense without line items
}
// Calculate total from line items
const calculatedTotal = expense.items.reduce(
(sum, item) => sum + item.itemValue.amount,
0
);
// Validate currency consistency across all items
const baseCurrency = expense.totalValue.currency;
const invalidCurrencyItems = expense.items.filter(
item => item.itemValue.currency !== baseCurrency
);
if (invalidCurrencyItems.length > 0) {
throw new BadRequestException(
`All expense items must use the same currency: ${baseCurrency}`
);
}
// Validate total amount matches sum of line items (with small tolerance for rounding)
const tolerance = 0.01; // 1 cent tolerance
const difference = Math.abs(calculatedTotal - expense.totalValue.amount);
if (difference > tolerance) {
throw new BadRequestException(
`Total expense amount (${expense.totalValue.amount}) does not match sum of line items (${calculatedTotal})`
);
}
}
/**
* Validate individual expense items
* @param expense - Domain expense entity
*/
private validateExpenseItems(expense: IExpense): void {
if (!expense.items) {
return;
}
expense.items.forEach((item, index) => {
// Validate item amount
if (item.itemValue.amount <= 0) {
throw new BadRequestException(`Expense item ${index + 1} amount must be greater than zero`);
}
// Validate item description
if (!item.description || item.description.trim().length === 0) {
throw new BadRequestException(`Expense item ${index + 1} must have a description`);
}
// Validate categories
if (!item.categories || item.categories.length === 0) {
throw new BadRequestException(`Expense item ${index + 1} must have at least one category`);
}
// Validate source information
if (!item.source || !item.source.sourceId) {
throw new BadRequestException(`Expense item ${index + 1} must have a valid source`);
}
});
}
}Query Use Cases
1. Get Paged Expenses Use Case
2. Get Expense by ID Use Case
Category Management Use Cases
1. Create Expense Category Use Case
2. Create Multiple Expense Categories Use Case
3. Get Paged Expense Categories Use Case
Use Case Dependencies and Integration
Dependency Injection Tokens
Module Configuration
Use Case Interfaces
Error Handling Patterns
Common Error Types
Error Response Format
Transaction Management
Database Transactions
Performance Considerations
Caching Strategies
Batch Operations
Testing Patterns
Use Case Testing
Integration Testing
Monitoring and Observability
Use Case Metrics
Business Event Logging
Last updated