mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 15:31:09 -05:00
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.
This commit is contained in:
186
docs/architecture/adr-003-state-machine-pattern.md
Normal file
186
docs/architecture/adr-003-state-machine-pattern.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# ADR-003: State Machine Pattern
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
Parks and rides in ThrillWiki go through various operational states:
|
||||
- Parks: Operating, Closed Temporarily, Closed Permanently, Under Construction
|
||||
- Rides: Operating, Closed, Under Construction, Removed, Relocated
|
||||
|
||||
Managing these state transitions requires:
|
||||
- Valid state transition enforcement
|
||||
- Audit trail of state changes
|
||||
- Business logic tied to state changes (notifications, cache invalidation)
|
||||
|
||||
## Decision
|
||||
|
||||
We implemented a **Finite State Machine (FSM) pattern** for managing entity states, using django-fsm with custom enhancements.
|
||||
|
||||
### State Model
|
||||
|
||||
```python
|
||||
from django_fsm import FSMField, transition
|
||||
|
||||
class Park(models.Model):
|
||||
status = FSMField(default='OPERATING')
|
||||
|
||||
@transition(
|
||||
field=status,
|
||||
source=['OPERATING', 'CLOSED_TEMP'],
|
||||
target='CLOSED_PERM'
|
||||
)
|
||||
def close_permanently(self, reason=None):
|
||||
"""Close the park permanently."""
|
||||
self.closure_reason = reason
|
||||
self.closure_date = timezone.now()
|
||||
|
||||
@transition(
|
||||
field=status,
|
||||
source='CLOSED_TEMP',
|
||||
target='OPERATING'
|
||||
)
|
||||
def reopen(self):
|
||||
"""Reopen a temporarily closed park."""
|
||||
self.closure_reason = None
|
||||
```
|
||||
|
||||
### State Diagram
|
||||
|
||||
```
|
||||
Park States:
|
||||
┌──────────────┐
|
||||
│ PLANNED │
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ UNDER_CONSTRUCTION │
|
||||
└──────────────────┬───────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ OPERATING │◄────┐
|
||||
└──────────────────┬───────────────────┘ │
|
||||
│ │
|
||||
┌───────────┼───────────┐ │
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ │
|
||||
┌────────────┐ ┌────────┐ ┌────────────┐ │
|
||||
│CLOSED_TEMP │ │SEASONAL│ │CLOSED_PERM │ │
|
||||
└─────┬──────┘ └────────┘ └────────────┘ │
|
||||
│ │
|
||||
└───────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Transition Validation
|
||||
|
||||
```python
|
||||
class ParkStateTransition(models.Model):
|
||||
"""Audit log for park state transitions."""
|
||||
park = models.ForeignKey(Park, on_delete=models.CASCADE)
|
||||
from_state = models.CharField(max_length=20)
|
||||
to_state = models.CharField(max_length=20)
|
||||
transition_date = models.DateTimeField(auto_now_add=True)
|
||||
transitioned_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
||||
reason = models.TextField(blank=True)
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
### Benefits
|
||||
|
||||
1. **Valid Transitions Only**: Invalid state changes are rejected at the model level
|
||||
2. **Audit Trail**: All transitions are logged with timestamps and users
|
||||
3. **Business Logic Encapsulation**: Transition methods contain related logic
|
||||
4. **Testability**: State machines are easy to unit test
|
||||
5. **Documentation**: State diagrams document valid workflows
|
||||
|
||||
### Trade-offs
|
||||
|
||||
1. **Learning Curve**: Developers need to understand FSM concepts
|
||||
2. **Migration Complexity**: Adding new states requires careful migration
|
||||
3. **Flexibility**: Rigid state transitions can be limiting for edge cases
|
||||
|
||||
### State Change Hooks
|
||||
|
||||
```python
|
||||
from django.db.models.signals import pre_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
@receiver(pre_save, sender=Park)
|
||||
def park_state_change(sender, instance, **kwargs):
|
||||
if instance.pk:
|
||||
old_instance = Park.objects.get(pk=instance.pk)
|
||||
if old_instance.status != instance.status:
|
||||
# Log transition
|
||||
ParkStateTransition.objects.create(
|
||||
park=instance,
|
||||
from_state=old_instance.status,
|
||||
to_state=instance.status,
|
||||
)
|
||||
# Invalidate caches
|
||||
invalidate_park_caches(instance)
|
||||
# Send notifications
|
||||
notify_state_change(instance, old_instance.status)
|
||||
```
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### Simple Status Field
|
||||
|
||||
**Rejected because:**
|
||||
- No validation of state transitions
|
||||
- Business logic scattered across codebase
|
||||
- No built-in audit trail
|
||||
|
||||
### Event Sourcing
|
||||
|
||||
**Rejected because:**
|
||||
- Overkill for current requirements
|
||||
- Significant complexity increase
|
||||
- Steeper learning curve
|
||||
|
||||
### Workflow Engine
|
||||
|
||||
**Rejected because:**
|
||||
- External dependency overhead
|
||||
- More complex than needed
|
||||
- FSM sufficient for current use cases
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Ride Status States
|
||||
|
||||
```python
|
||||
class RideStatus(models.TextChoices):
|
||||
OPERATING = 'OPERATING', 'Operating'
|
||||
CLOSED_TEMP = 'CLOSED_TEMP', 'Temporarily Closed'
|
||||
CLOSED_PERM = 'CLOSED_PERM', 'Permanently Closed'
|
||||
UNDER_CONSTRUCTION = 'UNDER_CONSTRUCTION', 'Under Construction'
|
||||
REMOVED = 'REMOVED', 'Removed'
|
||||
RELOCATED = 'RELOCATED', 'Relocated'
|
||||
```
|
||||
|
||||
### Testing State Transitions
|
||||
|
||||
```python
|
||||
class ParkStateTransitionTest(TestCase):
|
||||
def test_cannot_reopen_permanently_closed_park(self):
|
||||
park = ParkFactory(status='CLOSED_PERM')
|
||||
with self.assertRaises(TransitionNotAllowed):
|
||||
park.reopen()
|
||||
|
||||
def test_can_close_operating_park_temporarily(self):
|
||||
park = ParkFactory(status='OPERATING')
|
||||
park.close_temporarily(reason='Maintenance')
|
||||
self.assertEqual(park.status, 'CLOSED_TEMP')
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [django-fsm Documentation](https://github.com/viewflow/django-fsm)
|
||||
- [State Machine Diagrams](../state_machines/diagrams.md)
|
||||
- [Finite State Machine Wikipedia](https://en.wikipedia.org/wiki/Finite-state_machine)
|
||||
Reference in New Issue
Block a user