Files
thrillwiki_django_no_react/docs/architecture/adr-002-hybrid-api-design.md
pacnpal edcd8f2076 Add secret management guide, client-side performance monitoring, and search accessibility enhancements
- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols.
- Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage.
- Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
2025-12-23 16:41:42 -05:00

161 lines
4.5 KiB
Markdown

# ADR-002: Hybrid API Design Pattern
## Status
Accepted
## Context
ThrillWiki serves two types of clients:
1. **Web browsers**: Need HTML responses for rendering pages
2. **API clients**: Need JSON responses for mobile apps and integrations
We needed to decide how to handle these different client types efficiently without duplicating business logic.
## Decision
We implemented a **Hybrid API Design Pattern** where views can serve both HTML and JSON responses based on content negotiation.
### Implementation Strategy
1. **API-first approach**: All business logic is implemented in serializers and services
2. **Content negotiation**: Views check request headers to determine response format
3. **Dual endpoints**: Some resources are available at both web and API URLs
4. **Shared serializers**: Same serializers used for both API responses and template context
### URL Structure
```
# Web URLs (HTML responses)
/parks/ # Park list page
/parks/cedar-point/ # Park detail page
# API URLs (JSON responses)
/api/v1/parks/ # Park list (JSON)
/api/v1/parks/cedar-point/ # Park detail (JSON)
```
### View Implementation
```python
class HybridViewMixin:
"""
Mixin that enables views to serve both HTML and JSON responses.
"""
serializer_class = None
def get_response_format(self, request):
"""Determine response format from Accept header or query param."""
if request.htmx:
return 'html'
if 'application/json' in request.headers.get('Accept', ''):
return 'json'
if request.GET.get('format') == 'json':
return 'json'
return 'html'
def render_response(self, request, context, **kwargs):
"""Render appropriate response based on format."""
format = self.get_response_format(request)
if format == 'json':
serializer = self.serializer_class(context['object'])
return JsonResponse(serializer.data)
return super().render_to_response(context, **kwargs)
```
### Serializer Patterns
```python
# API serializers use camelCase for JSON responses
class ParkSerializer(serializers.ModelSerializer):
operatorName = serializers.CharField(source='operator.name')
rideCount = serializers.IntegerField(source='ride_count')
class Meta:
model = Park
fields = ['id', 'name', 'slug', 'operatorName', 'rideCount']
```
## Consequences
### Benefits
1. **Code Reuse**: Single set of business logic serves both web and API
2. **Consistency**: API and web views always return consistent data
3. **Flexibility**: Easy to add new response formats
4. **Progressive Enhancement**: API available without duplicating work
5. **Mobile-Ready**: API ready for mobile app development
### Trade-offs
1. **Complexity**: Views need to handle multiple response formats
2. **Testing**: Need to test both HTML and JSON responses
3. **Documentation**: Must document both web and API interfaces
### Response Format Decision
| Request Type | Response Format |
|-------------|-----------------|
| HTMX request | HTML partial |
| Browser (Accept: text/html) | Full HTML page |
| API (Accept: application/json) | JSON |
| Query param (?format=json) | JSON |
## Alternatives Considered
### Separate API and Web Views
**Rejected because:**
- Duplicate business logic
- Risk of divergence between API and web
- More code to maintain
### API-Only with JavaScript Frontend
**Rejected because:**
- Conflicts with ADR-001 (Django + HTMX architecture)
- Poor SEO without SSR
- Increased complexity
### GraphQL
**Rejected because:**
- Overkill for current requirements
- Steeper learning curve
- Less mature Django ecosystem support
## Implementation Details
### Hybrid Loader Services
For complex data loading scenarios, we use hybrid loader services:
```python
# backend/apps/parks/services/hybrid_loader.py
class ParkHybridLoader:
"""
Loads park data optimized for both API and web contexts.
"""
def load_park_detail(self, slug, context='web'):
park = Park.objects.optimized_for_detail().get(slug=slug)
if context == 'api':
return ParkSerializer(park).data
return {'park': park}
```
### API Versioning
API endpoints are versioned to allow breaking changes:
```
/api/v1/parks/ # Current version
/api/v2/parks/ # Future version (if needed)
```
## References
- [Django REST Framework](https://www.django-rest-framework.org/)
- [Content Negotiation](https://www.django-rest-framework.org/api-guide/content-negotiation/)
- [ThrillWiki API Documentation](../THRILLWIKI_API_DOCUMENTATION.md)