Как спроектировать модель данных для системы подписок пользователей

«Как спроектировать модель данных для системы подписок пользователей» — вопрос из категории Архитектура, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Для реализации механизма подписок используется классический паттерн "многие-ко-многим" (Many-to-Many) через промежуточную (связующую) таблицу.

Эта таблица хранит пары идентификаторов: кто подписался (subscriber) и на кого подписались (target).

Пример реализации на Django ORM

Модель Subscription выступает в роли связующей таблицы между пользователями (User).

from django.db import models
from django.contrib.auth.models import User

class Subscription(models.Model):
    """Модель для хранения подписок пользователей друг на друга."""

    # Пользователь, который подписывается
    subscriber = models.ForeignKey(
        User, 
        on_delete=models.CASCADE,
        related_name='following'  # user.following -> на кого он подписан
    )

    # Пользователь, на которого подписываются
    target = models.ForeignKey(
        User, 
        on_delete=models.CASCADE,
        related_name='followers'  # user.followers -> кто на него подписан
    )

    created_at = models.DateTimeField(
        auto_now_add=True, 
        verbose_name="Дата подписки"
    )

    class Meta:
        # Гарантирует, что пользователь не может подписаться на другого дважды
        unique_together = ('subscriber', 'target')
        ordering = ['-created_at']

    def __str__(self):
        return f"{self.subscriber} follows {self.target}"

Ключевые аспекты этой архитектуры:

  1. Промежуточная таблица: Subscription связывает двух пользователей.
  2. Внешние ключи: subscriber и target ссылаются на одну и ту же модель User.
  3. related_name: Позволяет удобно получать связанные объекты. Например, user.followers.all() вернет всех подписчиков пользователя.
  4. Ограничение уникальности: unique_together предотвращает дублирование записей о подписке.
  5. Масштабируемость: Этот подход легко расширяется, например, добавлением поля для уведомлений.