---
description: Protocol-based service design for maintainable architecture for src/services/**/* and src/repositories/**/*
alwaysApply: false
---
# Protocol-Based Service Design Rule

## Core Principles
- **Interface First**: Define protocols before implementations
- **Explicit Contracts**: Make expected behavior clear through protocols
- **Implementation Swapping**: Enable easy substitution of implementations
- **Testability**: Facilitate mocking and testing through protocols
- **Dependency Injection**: Use protocols for loose coupling

## Implementation Patterns

### Protocol Definition
```python
# ✅ DO: Define service interfaces using Protocol
from typing import Protocol, runtime_checkable, Optional, List, Dict, Any
from uuid import UUID
from pathlib import Path

@runtime_checkable
class TranscriptionServiceProtocol(Protocol):
    """Protocol for transcription services."""
    
    async def transcribe_file(
        self, 
        media_file: Path, 
        config: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """Transcribe a media file."""
        ...
    
    async def get_transcription(self, transcription_id: UUID) -> Optional[Dict[str, Any]]:
        """Retrieve a transcription by ID."""
        ...
```

### Implementation
```python
# ✅ DO: Implement protocols with concrete classes
class WhisperTranscriptionService:
    """Whisper-based implementation of TranscriptionServiceProtocol."""
    
    async def transcribe_file(
        self, 
        media_file: Path, 
        config: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """Transcribe using Whisper model."""
        # Implementation details here
        return {"text": "transcription content", "confidence": 0.95}
    
    async def get_transcription(self, transcription_id: UUID) -> Optional[Dict[str, Any]]:
        """Retrieve a transcription by ID."""
        # Implementation details here
        return {"id": transcription_id, "text": "transcription content"}
```

### Dependency Injection
```python
# ✅ DO: Use dependency injection with protocols
class TranscriptionController:
    def __init__(self, service: TranscriptionServiceProtocol):
        self.service = service
    
    async def process_file(self, file_path: Path) -> Dict[str, Any]:
        """Process a file using the injected service."""
        return await self.service.transcribe_file(file_path)

# Usage
service = WhisperTranscriptionService()
controller = TranscriptionController(service)
```

### Testing with Protocols
```python
# ✅ DO: Create test doubles that implement protocols
class MockTranscriptionService:
    """Mock implementation for testing."""
    
    async def transcribe_file(
        self, 
        media_file: Path, 
        config: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """Return predictable test data."""
        return {"text": "mock transcription", "confidence": 1.0}
    
    async def get_transcription(self, transcription_id: UUID) -> Optional[Dict[str, Any]]:
        """Return predictable test data."""
        return {"id": transcription_id, "text": "mock transcription"}

# In tests
async def test_controller():
    mock_service = MockTranscriptionService()
    controller = TranscriptionController(mock_service)
    result = await controller.process_file(Path("test.wav"))
    assert result["text"] == "mock transcription"
```

### Anti-Patterns
```python
# ❌ DON'T: Create implementations without protocols
class TranscriptionService:  # No protocol defined!
    def transcribe_file(self, file_path):
        # Implementation without a clear contract
        pass

# ❌ DON'T: Tightly couple components
class Controller:
    def __init__(self):
        # Directly instantiating dependency - tightly coupled!
        self.service = TranscriptionService()
```

When designing services, always define service interfaces using `typing.Protocol`. This ensures clear, explicit contracts, facilitates swapping implementations, and greatly improves testability. Before writing concrete classes, specify the expected behavior with protocols. Leverage dependency injection with these protocols to achieve loose coupling and flexible architecture.
