mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 13:31:09 -05:00
- 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.
161 lines
4.5 KiB
Markdown
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)
|