littleshop/DEVELOPMENT_LESSONS.md
2025-08-27 18:02:39 +01:00

9.3 KiB

LittleShop Development - Technical Lessons Learned

🔑 Critical Discoveries & Solutions

1. ASP.NET Core 9.0 Authentication Architecture

  • Dual Authentication Schemes: Successfully implemented both Cookie (for MVC Admin Panel) and JWT Bearer (for API) authentication in the same application
  • Key Learning: Must specify authentication scheme explicitly when using multiple schemes
  • Implementation: Cookie auth uses [Authorize(AuthenticationSchemes = "Cookies")], JWT uses [Authorize(AuthenticationSchemes = "Bearer")]

2. Entity Framework Core with SQLite

  • Decimal Ordering Issue: SQLite cannot order by decimal columns directly
  • Solution: Load data into memory first, then apply ordering
// Won't work in SQLite:
.OrderBy(sr => sr.MinWeight)

// Solution:
var rates = await _context.ShippingRates.ToListAsync();
return rates.OrderBy(sr => sr.MinWeight);

3. Service Constructor Dependencies

  • ProductService Evolution: Initially had IMapper dependency, later changed to IWebHostEnvironment for file handling
  • CategoryService: Simplified to only require DbContext, no mapper needed
  • Lesson: Services evolved based on actual needs rather than anticipated patterns

4. Model Property Naming Conventions

  • Initial Design: Used non-standard names (BasePrice, ProductWeight, ProductWeightUnit)
  • Refactored To: Standard e-commerce names (Price, Weight, WeightUnit)
  • Impact: Required updating all DTOs, services, views, and tests
  • Lesson: Follow industry-standard naming conventions from the start

5. Test Project Configuration

  • Mock Dependencies: Use Moq for IWebHostEnvironment in unit tests
  • In-Memory Database: Works well for testing with UseInMemoryDatabase()
  • Authentication in Tests: Create JWT tokens manually for integration tests
var token = JwtTokenHelper.GenerateJwtToken();
_client.DefaultRequestHeaders.Authorization = 
    new AuthenticationHeaderValue("Bearer", token);

6. File Upload Implementation

  • LINQ Translation Issues: Complex LINQ queries with DefaultIfEmpty() don't translate to SQL
  • Solution: Use nullable casts and null coalescing
// Problematic:
.Select(pp => pp.SortOrder).DefaultIfEmpty(0).Max()

// Solution:
.Select(pp => (int?)pp.SortOrder).MaxAsync() ?? 0

7. Git Commit in Windows/WSL

  • Issue: Complex multi-line commit messages fail with standard quotes
  • Solution: Write commit message to file, use -F flag
git commit -F commit_msg.txt

8. API Security Design Decision

  • Critical Requirement: NO public endpoints - all require authentication
  • Implementation: Every API endpoint requires JWT token
  • Rationale: Client applications handle public presentation after authentication
  • Impact: Simplified security model, consistent authorization

9. WSL2 Development Environment

  • Command Execution: Must use cmd.exe /c for .NET commands in WSL
  • File Locking: Application must be stopped before rebuilding (common WSL issue)
  • Path Handling: Use /mnt/c/ for Windows paths in WSL

10. Client SDK Design Patterns

  • Retry Policies: Polly integration for transient failure handling
  • Error Handling: Middleware pattern for consistent error responses
  • DI Integration: Extension methods for easy service registration
  • Response Wrapping: ApiResponse pattern for consistent error handling

📊 Database Design Decisions

Shipping Information

  • Added to Orders: Full shipping details (Name, Address, City, PostCode, Country)
  • Separate ShippingRates Table: Weight-based calculation system
  • Lesson: E-commerce requires comprehensive shipping information upfront

Payment Status Evolution

  • Original Enum Values: Pending, Confirmed, Failed
  • Changed To: Pending, Paid, Failed, Expired, Cancelled
  • Reason: Better alignment with cryptocurrency payment lifecycle

Order Status Values

  • Original: Pending, Processing, Shipped
  • Updated: PendingPayment, PaymentReceived, Processing, Shipped, Delivered, Cancelled
  • Benefit: More granular order tracking

🏗️ Architecture Patterns That Worked

1. Service Layer Pattern

  • Clean separation between controllers and business logic
  • Each service has its interface
  • Easy to mock for testing

2. DTO Pattern

  • Separate DTOs for Create, Update, and View operations
  • Prevents over-posting attacks
  • Clear API contracts

3. Repository Pattern (via EF Core)

  • DbContext acts as Unit of Work
  • No need for additional repository layer with EF Core
  • Simplified data access

4. Areas for Admin Panel

  • /Areas/Admin/ structure keeps admin code separate
  • Own controllers, views, and routing
  • Clear separation of concerns

🐛 Common Issues & Fixes

View Compilation Issues

  • Problem: Runtime changes to views not reflected
  • Solution: Restart application in production mode
  • Better Solution: Use development mode for active development

ModelState Validation

  • Issue: Empty validation summaries appearing
  • Cause: ModelState checking even for GET requests
  • Fix: Only display validation summary on POST

Nullable Reference Warnings

  • Common in Views: Model.Property warnings
  • Solution: Use null-conditional operator Model?.Property
  • Alternative: Use new() initialization in view model

🚀 Performance Optimizations

1. Query Optimization

  • Use .Include() for eager loading
  • Use .AsNoTracking() for read-only queries
  • Project to DTOs in queries to reduce data transfer

2. Pagination Implementation

  • Always implement pagination for list endpoints
  • Return metadata (total count, page info)
  • Client-side should handle pagination UI

3. File Upload Strategy

  • Store files on disk, not in database
  • Save only file paths in database
  • Implement file size limits

🔒 Security Best Practices Implemented

1. Authentication

  • JWT tokens expire after 60 minutes
  • Refresh token mechanism available
  • No sensitive data in JWT claims

2. Password Security

  • PBKDF2 with 100,000 iterations
  • Unique salt per password
  • Never store plain text passwords

3. Input Validation

  • FluentValidation for complex validation
  • Data annotations for simple validation
  • Server-side validation always enforced

4. CORS Configuration

  • Configured for specific domains in production
  • AllowAll only in development
  • Credentials handled properly

🎯 Key Takeaways

  1. Start with Standard Naming: Use industry-standard property names from the beginning
  2. Plan Authentication Early: Decide on authentication strategy before implementation
  3. Test Continuously: Fix test issues as they arise, don't let them accumulate
  4. Document as You Go: Keep CLAUDE.md updated with decisions and patterns
  5. Consider the Database: Some features (like decimal ordering) are database-specific
  6. Mock External Dependencies: Always mock file system, email, etc. in tests
  7. Use DTOs Consistently: Don't expose entities directly through APIs
  8. Handle Errors Gracefully: Implement proper error handling at all layers
  9. Security First: Never have public endpoints if not required
  10. Client SDK Value: A well-designed client SDK greatly improves API usability

🔄 Refactoring Opportunities

Future Improvements

  1. Caching Layer: Add Redis for frequently accessed data
  2. Message Queue: Implement for order processing
  3. Event Sourcing: For order status changes
  4. API Versioning: Prepare for future API changes
  5. Health Checks: Add health check endpoints
  6. Metrics: Implement application metrics
  7. Rate Limiting: Add rate limiting to API endpoints
  8. Background Jobs: Use Hangfire or similar for async processing

📝 Development Workflow Tips

  1. Always Check CLAUDE.md: Project-specific instructions override defaults
  2. Use TodoWrite Tool: Track multi-step tasks systematically
  3. Build Frequently: Catch compilation errors early
  4. Commit Logically: Group related changes in commits
  5. Test After Major Changes: Run tests after significant refactoring
  6. Document Decisions: Record why, not just what
  7. Consider Side Effects: Model changes affect DTOs, services, views, and tests

🛠️ Tool-Specific Lessons

Visual Studio Code / WSL

  • Use cmd.exe /c wrapper for Windows commands
  • File watchers don't always work in WSL
  • Hot reload is unreliable with WSL

Entity Framework Core

  • Migrations not always needed for development
  • EnsureCreated() sufficient for prototyping
  • Use migrations for production deployments

Git in Mixed Environments

  • Line ending issues (CRLF vs LF)
  • Use .gitattributes to standardize
  • Commit from consistent environment

Final Wisdom

"All endpoints must be authenticated" - This single decision simplified the entire security model and eliminated a class of vulnerabilities. Sometimes the most restrictive choice is the best choice.

"Standard names matter" - Using Price instead of BasePrice seems trivial, but non-standard names cascade through the entire codebase, tests, and documentation.

"Test the workflow, not just the code" - Creating sample data that demonstrates the complete order workflow (pending → paid → shipped → delivered) helps validate the business logic, not just the technical implementation.