Files
TimeTracker/docs/implementation-notes/ARCHITECTURE_MIGRATION_GUIDE.md
Dries Peeters 29f7186ee8 docs: Reorganize documentation structure for better navigation
Complete reorganization of project documentation to improve discoverability,
navigation, and maintainability. All documentation has been restructured into
a clear, role-based hierarchy.

## Major Changes

### New Directory Structure
- Created `docs/api/` for API documentation
- Created `docs/admin/` with subdirectories:
  - `admin/configuration/` - Configuration guides
  - `admin/deployment/` - Deployment guides
  - `admin/security/` - Security documentation
  - `admin/monitoring/` - Monitoring and analytics
- Created `docs/development/` for developer documentation
- Created `docs/guides/` for user-facing guides
- Created `docs/reports/` for analysis reports and summaries
- Created `docs/changelog/` for detailed changelog entries (ready for future use)

### File Organization

#### Moved from Root Directory (40+ files)
- Implementation notes → `docs/implementation-notes/`
- Test reports → `docs/testing/`
- Analysis reports → `docs/reports/`
- User guides → `docs/guides/`

#### Reorganized within docs/
- API documentation → `docs/api/`
- Administrator documentation → `docs/admin/` (with subdirectories)
- Developer documentation → `docs/development/`
- Security documentation → `docs/admin/security/`
- Telemetry documentation → `docs/admin/monitoring/`

### Documentation Updates

#### docs/README.md
- Complete rewrite with improved navigation
- Added visual documentation map
- Organized by role (Users, Administrators, Developers)
- Better categorization and quick links
- Updated all internal links to new structure

#### README.md (root)
- Updated all documentation links to reflect new structure
- Fixed 8 broken links

#### app/templates/main/help.html
- Enhanced "Where can I get additional help?" section
- Added links to new documentation structure
- Added documentation index link
- Added admin documentation link for administrators
- Improved footer with organized documentation links
- Added "Complete Documentation" section with role-based links

### New Index Files
- Created README.md files for all new directories:
  - `docs/api/README.md`
  - `docs/guides/README.md`
  - `docs/reports/README.md`
  - `docs/development/README.md`
  - `docs/admin/README.md`

### Cleanup
- Removed empty `docs/security/` directory (moved to `admin/security/`)
- Removed empty `docs/telemetry/` directory (moved to `admin/monitoring/`)
- Root directory now only contains: README.md, CHANGELOG.md, LICENSE

## Results

**Before:**
- 45+ markdown files cluttering root directory
- Documentation scattered across root and docs/
- Difficult to find relevant documentation
- No clear organization structure

**After:**
- 3 files in root directory (README, CHANGELOG, LICENSE)
- Clear directory structure organized by purpose and audience
- Easy navigation with role-based organization
- All documentation properly categorized
- Improved discoverability

## Benefits

1. Better Organization - Documentation grouped by purpose and audience
2. Easier Navigation - Role-based sections (Users, Admins, Developers)
3. Improved Discoverability - Clear structure with README files in each directory
4. Cleaner Root - Only essential files at project root
5. Maintainability - Easier to add and organize new documentation

## Files Changed

- 40+ files moved from root to appropriate docs/ subdirectories
- 15+ files reorganized within docs/
- 3 major documentation files updated (docs/README.md, README.md, help.html)
- 5 new README index files created
- 2 empty directories removed

All internal links have been updated to reflect the new structure.
2025-12-14 07:56:07 +01:00

10 KiB

Architecture Migration Guide

Complete guide for migrating existing code to the new architecture


🎯 Overview

This guide helps you migrate existing routes and code to use the new service layer, repository pattern, and other improvements.


📋 Migration Checklist

Step 1: Identify Code to Migrate

  • Routes with business logic
  • Direct model queries
  • Manual validation
  • Inconsistent error handling
  • N+1 query problems

Step 2: Create/Use Services

  • Identify business logic
  • Extract to service methods
  • Use existing services or create new ones

Step 3: Use Repositories

  • Replace direct queries with repository calls
  • Use eager loading to prevent N+1 queries
  • Leverage repository methods

Step 4: Add Validation

  • Use schemas for API endpoints
  • Use validation utilities for forms
  • Add proper error handling

Step 5: Update Tests

  • Mock repositories in unit tests
  • Test services independently
  • Add integration tests

🔄 Migration Examples

Example 1: Timer Route

Before:

@route('/timer/start')
def start_timer():
    project = Project.query.get(project_id)
    if not project:
        return error
    timer = TimeEntry(...)
    db.session.add(timer)
    db.session.commit()

After:

@route('/timer/start')
def start_timer():
    service = TimeTrackingService()
    result = service.start_timer(user_id, project_id)
    if result['success']:
        return success_response(result['timer'])
    return error_response(result['message'])

Example 2: Project List

Before:

@route('/projects')
def list_projects():
    projects = Project.query.filter_by(status='active').all()
    # N+1 query when accessing project.client
    return render_template('projects/list.html', projects=projects)

After:

@route('/projects')
def list_projects():
    repo = ProjectRepository()
    projects = repo.get_active_projects(include_relations=True)
    # Client eagerly loaded - no N+1 queries
    return render_template('projects/list.html', projects=projects)

Example 3: API Endpoint

Before:

@api.route('/projects', methods=['POST'])
def create_project():
    data = request.get_json()
    if not data.get('name'):
        return jsonify({'error': 'Name required'}), 400
    project = Project(name=data['name'], ...)
    db.session.add(project)
    db.session.commit()
    return jsonify(project.to_dict()), 201

After:

@api.route('/projects', methods=['POST'])
def create_project():
    from app.schemas import ProjectCreateSchema
    from app.utils.api_responses import created_response, validation_error_response
    
    schema = ProjectCreateSchema()
    try:
        data = schema.load(request.get_json())
    except ValidationError as err:
        return validation_error_response(err.messages)
    
    service = ProjectService()
    result = service.create_project(
        name=data['name'],
        client_id=data['client_id'],
        created_by=current_user.id
    )
    
    if result['success']:
        return created_response(result['project'].to_dict())
    return error_response(result['message'])

🛠️ Available Services

TimeTrackingService

  • start_timer() - Start a timer
  • stop_timer() - Stop active timer
  • create_manual_entry() - Create manual entry
  • get_user_entries() - Get user's entries
  • delete_entry() - Delete entry

ProjectService

  • create_project() - Create project
  • update_project() - Update project
  • archive_project() - Archive project
  • get_active_projects() - Get active projects

InvoiceService

  • create_invoice_from_time_entries() - Create invoice from entries
  • mark_as_sent() - Mark invoice as sent
  • mark_as_paid() - Mark invoice as paid

TaskService

  • create_task() - Create task
  • update_task() - Update task
  • get_project_tasks() - Get project tasks

ExpenseService

  • create_expense() - Create expense
  • get_project_expenses() - Get project expenses
  • get_total_expenses() - Get total expenses

ClientService

  • create_client() - Create client
  • update_client() - Update client
  • get_active_clients() - Get active clients

ReportingService

  • get_time_summary() - Get time summary
  • get_project_summary() - Get project summary
  • get_user_productivity() - Get user productivity

AnalyticsService

  • get_dashboard_stats() - Get dashboard stats
  • get_trends() - Get time trends

📚 Available Repositories

All repositories extend BaseRepository with common methods:

  • get_by_id() - Get by ID
  • get_all() - Get all with pagination
  • find_by() - Find by criteria
  • create() - Create new
  • update() - Update existing
  • delete() - Delete
  • count() - Count records
  • exists() - Check existence

Specialized Methods

TimeEntryRepository:

  • get_active_timer() - Get active timer
  • get_by_user() - Get user entries
  • get_by_project() - Get project entries
  • get_by_date_range() - Get by date range
  • get_billable_entries() - Get billable entries
  • create_timer() - Create timer
  • create_manual_entry() - Create manual entry
  • get_total_duration() - Get total duration

ProjectRepository:

  • get_active_projects() - Get active projects
  • get_by_client() - Get client projects
  • get_with_stats() - Get with statistics
  • archive() - Archive project
  • unarchive() - Unarchive project

InvoiceRepository:

  • get_by_project() - Get project invoices
  • get_by_client() - Get client invoices
  • get_by_status() - Get by status
  • get_overdue() - Get overdue invoices
  • generate_invoice_number() - Generate number
  • mark_as_sent() - Mark as sent
  • mark_as_paid() - Mark as paid

TaskRepository:

  • get_by_project() - Get project tasks
  • get_by_assignee() - Get assigned tasks
  • get_by_status() - Get by status
  • get_overdue() - Get overdue tasks

ExpenseRepository:

  • get_by_project() - Get project expenses
  • get_billable() - Get billable expenses
  • get_total_amount() - Get total amount

🎨 Using Schemas

For API Validation

from app.schemas import ProjectCreateSchema
from app.utils.api_responses import validation_error_response

@api.route('/projects', methods=['POST'])
def create_project():
    schema = ProjectCreateSchema()
    try:
        data = schema.load(request.get_json())
    except ValidationError as err:
        return validation_error_response(err.messages)
    
    # Use validated data...

For Serialization

from app.schemas import ProjectSchema

schema = ProjectSchema()
return schema.dump(project)

🔔 Using Event Bus

Emit Events

from app.utils.event_bus import emit_event
from app.constants import WebhookEvent

emit_event(WebhookEvent.TIME_ENTRY_CREATED.value, {
    'entry_id': entry.id,
    'user_id': user_id
})

Subscribe to Events

from app.utils.event_bus import subscribe_to_event

@subscribe_to_event('time_entry.created')
def handle_time_entry_created(event_type, data):
    # Handle event
    pass

🔄 Using Transactions

Decorator

from app.utils.transactions import transactional

@transactional
def create_something():
    # Auto-commits on success, rolls back on exception
    pass

Context Manager

from app.utils.transactions import Transaction

with Transaction():
    # Database operations
    # Auto-commits on success, rolls back on exception
    pass

Performance Tips

1. Use Eager Loading

# Bad - N+1 queries
projects = Project.query.all()
for p in projects:
    print(p.client.name)  # N+1 query

# Good - Eager loading
from app.utils.query_optimization import eager_load_relations
query = Project.query
query = eager_load_relations(query, Project, ['client'])
projects = query.all()

2. Use Repository Methods

# Repository methods already use eager loading
repo = ProjectRepository()
projects = repo.get_active_projects(include_relations=True)

3. Use Caching

from app.utils.cache import cached

@cached(ttl=3600)
def expensive_operation():
    # Result cached for 1 hour
    pass

🧪 Testing Patterns

Unit Test Service

def test_service():
    service = TimeTrackingService()
    service.time_entry_repo = Mock()
    service.project_repo = Mock()
    
    result = service.start_timer(user_id=1, project_id=1)
    assert result['success'] == True

Integration Test Repository

def test_repository(db_session):
    repo = TimeEntryRepository()
    timer = repo.create_timer(user_id=1, project_id=1)
    db_session.commit()
    
    active = repo.get_active_timer(1)
    assert active.id == timer.id

📝 Common Patterns

Pattern 1: Create Resource

service = ResourceService()
result = service.create_resource(**data)
if result['success']:
    return success_response(result['resource'])
return error_response(result['message'])

Pattern 2: List Resources

repo = ResourceRepository()
resources = repo.get_all(limit=50, offset=0, include_relations=True)
return paginated_response(resources, page=1, per_page=50, total=100)

Pattern 3: Update Resource

service = ResourceService()
result = service.update_resource(resource_id, user_id, **updates)
if result['success']:
    return success_response(result['resource'])
return error_response(result['message'])

Migration Priority

High Priority (Do First)

  1. Timer routes - Core functionality
  2. Invoice routes - Business critical
  3. Project routes - Frequently used
  4. API endpoints - External integration

Medium Priority

  1. Task routes
  2. Expense routes
  3. Client routes
  4. Report routes

Low Priority

  1. Admin routes
  2. Settings routes
  3. User routes

🎓 Best Practices

  1. Always use services for business logic
  2. Always use repositories for data access
  3. Always use schemas for API validation
  4. Always use response helpers for API responses
  5. Always use constants instead of magic strings
  6. Always eager load relations to prevent N+1
  7. Always emit domain events for side effects
  8. Always handle errors consistently

📚 Reference

  • Quick Start: QUICK_START_ARCHITECTURE.md
  • Full Analysis: PROJECT_ANALYSIS_AND_IMPROVEMENTS.md
  • Implementation: IMPLEMENTATION_SUMMARY.md
  • Examples: Check *_refactored.py files

Happy migrating! 🚀