259 lines
9.4 KiB
Markdown
259 lines
9.4 KiB
Markdown
# Implementation Philosophy
|
|
|
|
This document outlines the core implementation philosophy and guidelines for software development projects. It serves as a central reference for decision-making and development approach throughout the project.
|
|
|
|
## Core Philosophy
|
|
|
|
Embodies a Zen-like minimalism that values simplicity and clarity above all. This approach reflects:
|
|
|
|
- **Wabi-sabi philosophy**: Embracing simplicity and the essential. Each line serves a clear purpose without unnecessary embellishment.
|
|
- **Occam's Razor thinking**: The solution should be as simple as possible, but no simpler.
|
|
- **Trust in emergence**: Complex systems work best when built from simple, well-defined components that do one thing well.
|
|
- **Present-moment focus**: The code handles what's needed now rather than anticipating every possible future scenario.
|
|
- **Pragmatic trust**: The developer trusts external systems enough to interact with them directly, handling failures as they occur rather than assuming they'll happen.
|
|
|
|
This development philosophy values clear documentation, readable code, and belief that good architecture emerges from simplicity rather than being imposed through complexity.
|
|
|
|
## Core Design Principles
|
|
|
|
### 1. Ruthless Simplicity
|
|
|
|
- **KISS principle taken to heart**: Keep everything as simple as possible, but no simpler
|
|
- **Minimize abstractions**: Every layer of abstraction must justify its existence
|
|
- **Start minimal, grow as needed**: Begin with the simplest implementation that meets current needs
|
|
- **Avoid future-proofing**: Don't build for hypothetical future requirements
|
|
- **Question everything**: Regularly challenge complexity in the codebase
|
|
|
|
### 2. Architectural Integrity with Minimal Implementation
|
|
|
|
- **Preserve key architectural patterns**: MCP for service communication, SSE for events, separate I/O channels, etc.
|
|
- **Simplify implementations**: Maintain pattern benefits with dramatically simpler code
|
|
- **Scrappy but structured**: Lightweight implementations of solid architectural foundations
|
|
- **End-to-end thinking**: Focus on complete flows rather than perfect components
|
|
|
|
### 3. Library Usage Philosophy
|
|
|
|
- **Use libraries as intended**: Minimal wrappers around external libraries
|
|
- **Direct integration**: Avoid unnecessary adapter layers
|
|
- **Selective dependency**: Add dependencies only when they provide substantial value
|
|
- **Understand what you import**: No black-box dependencies
|
|
|
|
## Technical Implementation Guidelines
|
|
|
|
### API Layer
|
|
|
|
- Implement only essential endpoints
|
|
- Minimal middleware with focused validation
|
|
- Clear error responses with useful messages
|
|
- Consistent patterns across endpoints
|
|
|
|
### Database & Storage
|
|
|
|
- Simple schema focused on current needs
|
|
- Use TEXT/JSON fields to avoid excessive normalization early
|
|
- Add indexes only when needed for performance
|
|
- Delay complex database features until required
|
|
|
|
### MCP Implementation
|
|
|
|
- Streamlined MCP client with minimal error handling
|
|
- Utilize FastMCP when possible, falling back to lower-level only when necessary
|
|
- Focus on core functionality without elaborate state management
|
|
- Simplified connection lifecycle with basic error recovery
|
|
- Implement only essential health checks
|
|
|
|
### SSE & Real-time Updates
|
|
|
|
- Basic SSE connection management
|
|
- Simple resource-based subscriptions
|
|
- Direct event delivery without complex routing
|
|
- Minimal state tracking for connections
|
|
|
|
### Event System
|
|
|
|
- Simple topic-based publisher/subscriber
|
|
- Direct event delivery without complex pattern matching
|
|
- Clear, minimal event payloads
|
|
- Basic error handling for subscribers
|
|
|
|
### LLM Integration
|
|
|
|
- Direct integration with PydanticAI
|
|
- Minimal transformation of responses
|
|
- Handle common error cases only
|
|
- Skip elaborate caching initially
|
|
|
|
### Message Routing
|
|
|
|
- Simplified queue-based processing
|
|
- Direct, focused routing logic
|
|
- Basic routing decisions without excessive action types
|
|
- Simple integration with other components
|
|
|
|
## Development Approach
|
|
|
|
### Vertical Slices
|
|
|
|
- Implement complete end-to-end functionality slices
|
|
- Start with core user journeys
|
|
- Get data flowing through all layers early
|
|
- Add features horizontally only after core flows work
|
|
|
|
### Iterative Implementation
|
|
|
|
- 80/20 principle: Focus on high-value, low-effort features first
|
|
- One working feature > multiple partial features
|
|
- Validate with real usage before enhancing
|
|
- Be willing to refactor early work as patterns emerge
|
|
|
|
### Testing Strategy
|
|
|
|
- Emphasis on integration and end-to-end tests
|
|
- Manual testability as a design goal
|
|
- Focus on critical path testing initially
|
|
- Add unit tests for complex logic and edge cases
|
|
- Testing pyramid: 60% unit, 30% integration, 10% end-to-end
|
|
|
|
### Error Handling
|
|
|
|
- Handle common errors robustly
|
|
- Log detailed information for debugging
|
|
- Provide clear error messages to users
|
|
- Fail fast and visibly during development
|
|
|
|
## Decision-Making Framework
|
|
|
|
When faced with implementation decisions, ask these questions:
|
|
|
|
1. **Necessity**: "Do we actually need this right now?"
|
|
2. **Simplicity**: "What's the simplest way to solve this problem?"
|
|
3. **Directness**: "Can we solve this more directly?"
|
|
4. **Value**: "Does the complexity add proportional value?"
|
|
5. **Maintenance**: "How easy will this be to understand and change later?"
|
|
|
|
## Areas to Embrace Complexity
|
|
|
|
Some areas justify additional complexity:
|
|
|
|
1. **Security**: Never compromise on security fundamentals
|
|
2. **Data integrity**: Ensure data consistency and reliability
|
|
3. **Core user experience**: Make the primary user flows smooth and reliable
|
|
4. **Error visibility**: Make problems obvious and diagnosable
|
|
|
|
## Areas to Aggressively Simplify
|
|
|
|
Push for extreme simplicity in these areas:
|
|
|
|
1. **Internal abstractions**: Minimize layers between components
|
|
2. **Generic "future-proof" code**: Resist solving non-existent problems
|
|
3. **Edge case handling**: Handle the common cases well first
|
|
4. **Framework usage**: Use only what you need from frameworks
|
|
5. **State management**: Keep state simple and explicit
|
|
|
|
## Practical Examples
|
|
|
|
### Good Example: Direct SSE Implementation
|
|
|
|
```python
|
|
# Simple, focused SSE manager that does exactly what's needed
|
|
class SseManager:
|
|
def __init__(self):
|
|
self.connections = {} # Simple dictionary tracking
|
|
|
|
async def add_connection(self, resource_id, user_id):
|
|
"""Add a new SSE connection"""
|
|
connection_id = str(uuid.uuid4())
|
|
queue = asyncio.Queue()
|
|
self.connections[connection_id] = {
|
|
"resource_id": resource_id,
|
|
"user_id": user_id,
|
|
"queue": queue
|
|
}
|
|
return queue, connection_id
|
|
|
|
async def send_event(self, resource_id, event_type, data):
|
|
"""Send an event to all connections for a resource"""
|
|
# Direct delivery to relevant connections only
|
|
for conn_id, conn in self.connections.items():
|
|
if conn["resource_id"] == resource_id:
|
|
await conn["queue"].put({
|
|
"event": event_type,
|
|
"data": data
|
|
})
|
|
```
|
|
|
|
### Bad Example: Over-engineered SSE Implementation
|
|
|
|
```python
|
|
# Overly complex with unnecessary abstractions and state tracking
|
|
class ConnectionRegistry:
|
|
def __init__(self, metrics_collector, cleanup_interval=60):
|
|
self.connections_by_id = {}
|
|
self.connections_by_resource = defaultdict(list)
|
|
self.connections_by_user = defaultdict(list)
|
|
self.metrics_collector = metrics_collector
|
|
self.cleanup_task = asyncio.create_task(self._cleanup_loop(cleanup_interval))
|
|
|
|
# [50+ more lines of complex indexing and state management]
|
|
```
|
|
|
|
### Good Example: Simple MCP Client
|
|
|
|
```python
|
|
# Focused MCP client with clean error handling
|
|
class McpClient:
|
|
def __init__(self, endpoint: str, service_name: str):
|
|
self.endpoint = endpoint
|
|
self.service_name = service_name
|
|
self.client = None
|
|
|
|
async def connect(self):
|
|
"""Connect to MCP server"""
|
|
if self.client is not None:
|
|
return # Already connected
|
|
|
|
try:
|
|
# Create SSE client context
|
|
async with sse_client(self.endpoint) as (read_stream, write_stream):
|
|
# Create client session
|
|
self.client = ClientSession(read_stream, write_stream)
|
|
# Initialize the client
|
|
await self.client.initialize()
|
|
except Exception as e:
|
|
self.client = None
|
|
raise RuntimeError(f"Failed to connect to {self.service_name}: {str(e)}")
|
|
|
|
async def call_tool(self, name: str, arguments: dict):
|
|
"""Call a tool on the MCP server"""
|
|
if not self.client:
|
|
await self.connect()
|
|
|
|
return await self.client.call_tool(name=name, arguments=arguments)
|
|
```
|
|
|
|
### Bad Example: Over-engineered MCP Client
|
|
|
|
```python
|
|
# Complex MCP client with excessive state management and error handling
|
|
class EnhancedMcpClient:
|
|
def __init__(self, endpoint, service_name, retry_strategy, health_check_interval):
|
|
self.endpoint = endpoint
|
|
self.service_name = service_name
|
|
self.state = ConnectionState.DISCONNECTED
|
|
self.retry_strategy = retry_strategy
|
|
self.connection_attempts = 0
|
|
self.last_error = None
|
|
self.health_check_interval = health_check_interval
|
|
self.health_check_task = None
|
|
# [50+ more lines of complex state tracking and retry logic]
|
|
```
|
|
|
|
## Remember
|
|
|
|
- It's easier to add complexity later than to remove it
|
|
- Code you don't write has no bugs
|
|
- Favor clarity over cleverness
|
|
- The best code is often the simplest
|
|
|
|
This philosophy document serves as the foundational guide for all implementation decisions in the project.
|