Architecture System Design - onouyek/MailApe GitHub Wiki
System Design
Overall architecture and design patterns used in MailApe.
Architectural Pattern
MailApe follows the Model-View-Template (MVT) pattern, Django's variant of MVC:
graph TB
User[User Request] --> URL[URL Dispatcher]
URL --> View[View Layer]
View --> Model[Model Layer]
Model --> DB[(Database)]
View --> Template[Template Layer]
Template --> Response[HTTP Response]
subgraph "Django MVT"
View
Model
Template
end
Application Architecture
Multi-App Structure
MailApe is organized into focused Django applications:
graph TD
Config[config app] --> ML[mailinglist app]
Config --> User[user app]
Config --> Admin[Django Admin]
ML --> Models[Mailing List Models]
ML --> Views[List Management Views]
ML --> API[REST API]
ML --> Tasks[Celery Tasks]
User --> Auth[Authentication]
User --> Registration[User Registration]
Applications:
- config: Project configuration and URL routing
- mailinglist: Core mailing list functionality
- user: User authentication and management
Source: common_settings.py#L33-46 and urls.py
Design Patterns
1. Repository Pattern (via Django ORM)
Models encapsulate data access logic:
class SubscriberManager(models.Manager):
def confirmed_subscribers_for_mailing_list(self, mailing_list):
qs = self.get_queryset()
qs = qs.filter(confirmed=True)
qs = qs.filter(mailing_list=mailing_list)
return qs
Source: models.py#L25-30
2. Observer Pattern (via Django Signals)
Model save operations trigger automatic actions:
def save(self, ...):
is_new = self._state.adding or force_insert
super().save(...)
if is_new:
self.send_confirmation_email()
Source: models.py#L47-58
3. Command Pattern (via Celery Tasks)
Asynchronous operations are encapsulated as tasks:
@shared_task
def send_confirmation_email_to_subscriber(subscriber_id):
subscriber = Subscriber.objects.get(id=subscriber_id)
emails.send_confirmation_email(subscriber)
Source: tasks.py#L6-11
4. Strategy Pattern (via Class-Based Views)
Different view strategies for different operations:
class MailingListListView(LoginRequiredMixin, ListView):
def get_queryset(self):
return MailingList.objects.filter(owner=self.request.user)
class CreateMailingListView(LoginRequiredMixin, CreateView):
form_class = MailingListForm
template_name = "mailinglist/mailinglist_form.html"
Source: views.py#L21-34
Security Design
Authentication Architecture
graph LR
Request[HTTP Request] --> Auth[Authentication Middleware]
Auth --> Login{User Logged In?}
Login -->|No| LoginPage[Login Page]
Login -->|Yes| Perm[Permission Check]
Perm --> Owner{Is Owner?}
Owner -->|Yes| Allow[Allow Access]
Owner -->|No| Deny[403 Forbidden]
Authorization Strategy
- Ownership-based: Users can only access their own mailing lists
- Mixin pattern: Reusable authorization logic
class UserCanUseMailingList:
def dispatch(self, request, *args, **kwargs):
user = request.user
mailing_list = self.get_object()
if not mailing_list.user_can_use_mailing_list(user):
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
Source: mixins.py referenced in views.py#L12
Data Processing Architecture
Email Processing Pipeline
graph TD
A[Message Created] --> B[Save to Database]
B --> C[Trigger build_subscriber_messages]
C --> D[Create SubscriberMessage Records]
D --> E[Queue send_subscriber_message Tasks]
E --> F[Process Tasks in Background]
F --> G[Send Individual Emails]
G --> H[Update Delivery Status]
Task Queue Design
graph LR
Web[Web Application] --> Redis[Redis Queue]
Redis --> Worker1[Celery Worker 1]
Redis --> Worker2[Celery Worker N]
Worker1 --> Email[Email Service]
Worker2 --> Email
Worker1 --> DB[(Database)]
Worker2 --> DB
API Design
RESTful API Structure
- Resource-based URLs:
/api/mailinglists/
,/api/subscribers/
- HTTP methods: GET, POST, PUT, DELETE
- Authentication required: All endpoints require login
- Rate limiting: Throttling to prevent abuse
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
"DEFAULT_THROTTLE_CLASSES": (
"rest_framework.throttling.UserRateThrottle",
"rest_framework.throttling.AnonRateThrottle",
),
"DEFAULT_THROTTLE_RATES": {"user": "60/min", "anon": "30/min"},
}
Source: common_settings.py#L125-132
Database Design Principles
UUID Usage
- Security: Prevents enumeration attacks
- Scalability: Globally unique identifiers
- Distribution: Works across multiple databases
Unique Constraints
- Data integrity: One subscription per email per list
- Business rules: Enforced at database level
class Meta:
unique_together = [
"email",
"mailing_list",
]
Source: models.py#L41-45
Error Handling Strategy
Graceful Degradation
- Task failures: Automatic retry mechanisms
- Email failures: Individual failure tracking
- Database errors: Transaction rollbacks
Logging Architecture
graph TD
App[Application] --> Logger[Django Logger]
Logger --> Console[Console Handler]
Logger --> File[File Handler]
Logger --> Level{Log Level}
Level --> Debug[DEBUG: Development]
Level --> Info[INFO: Production]
Source: common_settings.py#L134-152
Scalability Considerations
Horizontal Scaling
- Stateless design: No server-side session storage
- Database separation: Can use read replicas
- Task queue scaling: Multiple Celery workers
Performance Optimization
- Query optimization: Manager methods for efficient queries
- Bulk operations: Batch email creation
- Caching: Redis for session and task data
Configuration Management
Environment-based Settings
graph TD
Base[common_settings.py] --> Dev[development_settings.py]
Base --> Prod[production_settings.py]
Dev --> Local[Local Development]
Prod --> AWS[AWS Production]
External Configuration
- Environment variables: Sensitive data (secret keys, passwords)
- Settings files: Application configuration
- Infrastructure as Code: CloudFormation templates
Next Steps
- Module Structure - Code organization details
- Data Flow - Detailed data flow diagrams
- Development Guides - Implementation guidance