← Назад к вопросам
Как реализуешь рефереальную систему с точки зрения архитектуры?
2.4 Senior🔥 111 комментариев
#Архитектура и паттерны#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как реализуешь рефереальную систему с точки зрения архитектуры?
Реферальная система — это сложный бизнес-сценарий, требующий продуманной архитектуры. Разберём правильный подход с использованием Clean Architecture и DDD.
Общая архитектура
┌─────────────────────────────────────────────────────┐
│ Presentation (API endpoints, Telegram Bot) │
├─────────────────────────────────────────────────────┤
│ Application (Use Cases, Orchestration) │
├─────────────────────────────────────────────────────┤
│ Domain (Entity, Value Objects, Aggregates) │
├─────────────────────────────────────────────────────┤
│ Infrastructure (Database, Cache, Events) │
└─────────────────────────────────────────────────────┘
Domain Layer (Бизнес-логика)
1. Entities и Value Objects
# domain/entities/referral.py
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
class ReferralStatus(str, Enum):
PENDING = "pending" # Реферал не активирован
ACTIVE = "active" # Реферал активирован
REVOKED = "revoked" # Отозван (мошенничество)
class ReferralRewardType(str, Enum):
BONUS = "bonus" # Бонус после регистрации
PERCENTAGE = "percentage" # % от покупок реферала
FIXED = "fixed" # Фиксированная сумма
@dataclass(frozen=True)
class ReferralCode:
"""Value Object для уникального кода реферала"""
code: str
def __post_init__(self):
if len(self.code) < 4:
raise ValueError("Code too short")
if not self.code.isalnum():
raise ValueError("Code must be alphanumeric")
@dataclass
class Referral:
"""Entity: реферальные отношения между пользователями"""
id: str
referrer_id: str # Тот, кто привёл
referee_id: str # Того, кто привёлся
code: ReferralCode
status: ReferralStatus
created_at: datetime
activated_at: datetime | None = None
def activate(self) -> None:
if self.status == ReferralStatus.PENDING:
self.status = ReferralStatus.ACTIVE
self.activated_at = datetime.now(UTC)
def revoke(self, reason: str) -> None:
"""Отозвать реферал (например, при обнаружении мошенничества)"""
self.status = ReferralStatus.REVOKED
@dataclass(frozen=True)
class ReferralReward:
"""Value Object: награда за реферал"""
reward_type: ReferralRewardType
amount: float
def __post_init__(self):
if self.amount <= 0:
raise ValueError("Amount must be positive")
@dataclass
class ReferralRewardTransaction:
"""Entity: выплата награды"""
id: str
referral_id: str
reward: ReferralReward
transaction_id: str # ID платежа в системе
status: str # pending, completed, failed
created_at: datetime
2. Aggregate Root
# domain/aggregates/referrer_aggregate.py
from typing import List
@dataclass
class ReferrerAggregate:
"""Aggregate: реферер с его всеми реферальными отношениями"""
referrer_id: str
referral_code: ReferralCode
referrals: List[Referral] = field(default_factory=list)
total_earnings: float = 0.0
def add_referral(self, referral: Referral) -> None:
if referral.referrer_id != self.referrer_id:
raise ValueError("Invalid referrer")
self.referrals.append(referral)
def get_active_referrals_count(self) -> int:
return sum(
1 for r in self.referrals
if r.status == ReferralStatus.ACTIVE
)
def apply_reward(self, reward_transaction: ReferralRewardTransaction) -> None:
if reward_transaction.status == "completed":
self.total_earnings += reward_transaction.reward.amount
3. Domain Services
# domain/services/referral_service.py
from typing import Protocol
class ReferralRepository(Protocol):
"""Interface для работы с реферальными данными"""
def save(self, referral: Referral) -> None: ...
def get_by_code(self, code: ReferralCode) -> Referral | None: ...
def get_by_referrer_id(self, referrer_id: str) -> List[Referral]: ...
class ReferralCodeGenerator(Protocol):
"""Interface для генерации уникальных кодов"""
def generate(self) -> ReferralCode: ...
def validate_uniqueness(self, code: ReferralCode) -> bool: ...
class ReferralDomainService:
"""Domain Service: бизнес-логика, которая затрагивает несколько entities"""
def __init__(
self,
referral_repo: ReferralRepository,
code_generator: ReferralCodeGenerator,
):
self.referral_repo = referral_repo
self.code_generator = code_generator
def create_referral_pair(self, referrer_id: str, referee_id: str) -> Referral:
"""Создать реферальное отношение с уникальным кодом"""
# Генерируем уникальный код
code = self._generate_unique_code()
referral = Referral(
id=str(uuid4()),
referrer_id=referrer_id,
referee_id=referee_id,
code=code,
status=ReferralStatus.PENDING,
created_at=datetime.now(UTC),
)
self.referral_repo.save(referral)
return referral
def _generate_unique_code(self) -> ReferralCode:
while True:
code = self.code_generator.generate()
if self.code_generator.validate_uniqueness(code):
return code
Application Layer (Use Cases)
# application/use_cases/register_with_referral.py
from dataclasses import dataclass
from typing import Optional
@dataclass
class RegisterWithReferralRequest:
email: str
password: str
referral_code: Optional[str] = None
@dataclass
class RegisterWithReferralResponse:
user_id: str
referral_code: str
referrer_id: Optional[str]
class RegisterWithReferralUseCase:
"""Use Case: регистрация с использованием реферального кода"""
def __init__(
self,
user_repo: "UserRepository",
referral_repo: ReferralRepository,
user_service: "UserService",
domain_service: ReferralDomainService,
event_bus: "EventBus",
):
self.user_repo = user_repo
self.referral_repo = referral_repo
self.user_service = user_service
self.domain_service = domain_service
self.event_bus = event_bus
async def execute(self, req: RegisterWithReferralRequest) -> RegisterWithReferralResponse:
# Шаг 1: Валидация
self._validate_request(req)
# Шаг 2: Проверка реферального кода
referrer_id = None
if req.referral_code:
referrer_id = await self._get_referrer_by_code(req.referral_code)
if not referrer_id:
raise ReferralCodeNotFoundError()
# Шаг 3: Создание пользователя
user = await self.user_service.create_user(
email=req.email,
password=req.password,
)
# Шаг 4: Активирование реферального отношения
if referrer_id:
referral = await self._activate_referral(referrer_id, user.id)
# Публикуем domain event
self.event_bus.publish(
ReferralActivatedEvent(
referral_id=referral.id,
referrer_id=referrer_id,
referee_id=user.id,
)
)
# Шаг 5: Генерируем реферальный код для нового пользователя
new_referral_code = self.domain_service.create_referral_pair(
referrer_id=user.id,
referee_id="", # Placeholder
).code
return RegisterWithReferralResponse(
user_id=user.id,
referral_code=new_referral_code.code,
referrer_id=referrer_id,
)
async def _get_referrer_by_code(self, code: str) -> Optional[str]:
referral = self.referral_repo.get_by_code(ReferralCode(code))
return referral.referrer_id if referral else None
async def _activate_referral(self, referrer_id: str, referee_id: str) -> Referral:
referral = Referral(
id=str(uuid4()),
referrer_id=referrer_id,
referee_id=referee_id,
code=ReferralCode(str(uuid4())[:8]),
status=ReferralStatus.PENDING,
created_at=datetime.now(UTC),
)
referral.activate()
self.referral_repo.save(referral)
return referral
# application/use_cases/process_referral_reward.py
class ProcessReferralRewardUseCase:
"""Use Case: начисление награды за реферала"""
def __init__(
self,
referral_repo: ReferralRepository,
reward_calculator: "RewardCalculator",
payment_service: "PaymentService",
event_bus: "EventBus",
):
self.referral_repo = referral_repo
self.reward_calculator = reward_calculator
self.payment_service = payment_service
self.event_bus = event_bus
async def execute(self, referral_id: str, purchase_amount: float):
# Получаем реферальное отношение
referral = self.referral_repo.get_by_id(referral_id)
if referral.status != ReferralStatus.ACTIVE:
return # Не начисляем награду за неактивные реферралы
# Рассчитываем награду
reward = self.reward_calculator.calculate(purchase_amount)
# Обрабатываем платёж
transaction = await self.payment_service.transfer(
user_id=referral.referrer_id,
amount=reward.amount,
reason=f"Referral reward for {referral.referee_id}",
)
# Сохраняем информацию о вознаграждении
reward_tx = ReferralRewardTransaction(
id=str(uuid4()),
referral_id=referral_id,
reward=reward,
transaction_id=transaction.id,
status="completed",
created_at=datetime.now(UTC),
)
self.event_bus.publish(
ReferralRewardProcessedEvent(
referral_id=referral_id,
referrer_id=referral.referrer_id,
reward_amount=reward.amount,
)
)
Infrastructure Layer
# infrastructure/persistence/referral_repository.py
from sqlalchemy.orm import Session
class SQLAlchemyReferralRepository:
def __init__(self, db: Session):
self.db = db
def save(self, referral: Referral) -> None:
db_referral = ReferralModel(
id=referral.id,
referrer_id=referral.referrer_id,
referee_id=referral.referee_id,
code=referral.code.code,
status=referral.status.value,
created_at=referral.created_at,
activated_at=referral.activated_at,
)
self.db.add(db_referral)
self.db.commit()
def get_by_code(self, code: ReferralCode) -> Optional[Referral]:
db_referral = self.db.query(ReferralModel).filter(
ReferralModel.code == code.code
).first()
if not db_referral:
return None
return self._to_domain(db_referral)
# infrastructure/events/referral_events.py
from dataclasses import dataclass
from datetime import datetime
@dataclass
class ReferralActivatedEvent:
"""Domain Event: реферал активирован"""
referral_id: str
referrer_id: str
referee_id: str
timestamp: datetime = field(default_factory=datetime.now)
@dataclass
class ReferralRewardProcessedEvent:
"""Domain Event: награда за реферал выплачена"""
referral_id: str
referrer_id: str
reward_amount: float
timestamp: datetime = field(default_factory=datetime.now)
Presentation Layer (API)
# presentation/api/referrals.py
from fastapi import APIRouter, Depends
router = APIRouter(prefix="/api/v1/referrals")
@router.post("/register")
async def register_with_referral(
request: RegisterWithReferralRequest,
use_case: RegisterWithReferralUseCase = Depends(),
):
response = await use_case.execute(request)
return {
"user_id": response.user_id,
"referral_code": response.referral_code,
}
@router.get("/me/stats")
async def get_referral_stats(
user_id: str = Depends(get_current_user),
repo: ReferralRepository = Depends(),
):
referrals = repo.get_by_referrer_id(user_id)
return {
"total_referrals": len(referrals),
"active_referrals": sum(
1 for r in referrals if r.status == ReferralStatus.ACTIVE
),
"total_earnings": sum(
r.total_earnings for r in referrals
),
}
Ключевые особенности архитектуры
- Separation of Concerns — бизнес-логика отделена от деталей реализации
- Testability — легко тестировать каждый слой независимо
- Scalability — структура позволяет легко добавлять новые типы наград
- Fraud Prevention — явная обработка ошибок и отзыва рефералов
- Event-Driven — асинхронная обработка наград через events
Вызовы при реализации
⚠️ Race Conditions — использовать SELECT FOR UPDATE при активации
⚠️ Fraud Detection — отслеживать подозрительные паттерны (multiple accounts)
⚠️ Compensation — что делать если реферал отменит покупку?
⚠️ Scalability — как обрабатывать millions of referrals?
Заключение
Правильная архитектура реферальной системы требует:
- Четкого разделения ответственности
- Независимого тестирования
- Обработки edge cases
- Мониторинга и fraud detection
Это комплексная система, но следуя DDD и Clean Architecture, можно создать масштабируемое и надёжное решение.