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