← Назад к вопросам
Расскажи о крупном проекте над которым работал
1.3 Junior🔥 201 комментариев
#Soft Skills
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Крупный проект: Платформа управления подписками
Контекст
Я был lead backend разработчиком в проекте платформы управления подписками для SaaS компании. Это был high-load проект с 500k+ активных пользователей, обрабатывающий 10k запросов в секунду.
Задача
Построить систему, которая:
- Обрабатывает платежи от Stripe, Paddle, AppStore
- Управляет подписками пользователей (создание, продление, отмена)
- Отправляет уведомления (email, push, webhook)
- Работает 24/7 с uptime > 99.9%
- Обеспечивает консистентность данных при сбоях
Архитектура
Стек технологий:
- Django + Django REST Framework для основного API
- PostgreSQL для основной БД
- Redis для кеша и очередей
- Celery + RabbitMQ для асинхронных задач
- Docker + Kubernetes для развёртывания
- Prometheus + Grafana для мониторинга
- Sentry для отслеживания ошибок
Слои архитектуры (Clean Architecture):
Presentation (API endpoints)
↓
Application (Use Cases, Services)
↓
Domain (Business Logic, Entities)
↓
Infrastructure (DB, External APIs)
Основные компоненты
1. Payment Service (обработка платежей)
# domain/payment/entities.py
class Payment(AggregateRoot):
id: UUID
subscription_id: UUID
amount: Decimal
currency: str
status: PaymentStatus # pending, completed, failed
payment_provider: str # stripe, paddle, appstore
external_id: str # ID в системе провайдера
created_at: datetime
def mark_completed(self, transaction_id: str):
self.status = PaymentStatus.COMPLETED
self.external_id = transaction_id
self.publish(PaymentCompletedEvent(self.id))
def mark_failed(self, reason: str):
self.status = PaymentStatus.FAILED
self.publish(PaymentFailedEvent(self.id, reason))
# application/payment/use_cases/process_payment.py
class ProcessPaymentUseCase:
def __init__(self, payment_repo: PaymentRepository,
stripe_service: StripeService,
event_bus: EventBus):
self.payment_repo = payment_repo
self.stripe_service = stripe_service
self.event_bus = event_bus
async def execute(self, payment_id: UUID) -> Result:
payment = await self.payment_repo.get(payment_id)
try:
# Обрабатываем платёж
result = await self.stripe_service.charge(
amount=payment.amount,
currency=payment.currency,
customer_id=payment.customer_id
)
# Обновляем платёж
payment.mark_completed(result.transaction_id)
await self.payment_repo.save(payment)
# Публикуем события
for event in payment.events:
await self.event_bus.publish(event)
return Result.ok(payment)
except StripeError as e:
payment.mark_failed(str(e))
await self.payment_repo.save(payment)
return Result.fail(str(e))
2. Subscription Service (управление подписками)
# domain/subscription/entities.py
class Subscription(AggregateRoot):
id: UUID
user_id: UUID
plan_id: UUID
status: SubscriptionStatus # active, paused, cancelled
billing_period: BillingPeriod # monthly, yearly
current_period_start: date
current_period_end: date
renewal_count: int = 0
cancelled_at: datetime | None = None
def activate(self):
self.status = SubscriptionStatus.ACTIVE
self.publish(SubscriptionActivatedEvent(self.id))
def pause(self):
self.status = SubscriptionStatus.PAUSED
self.publish(SubscriptionPausedEvent(self.id))
def cancel(self, reason: str = ''):
self.status = SubscriptionStatus.CANCELLED
self.cancelled_at = datetime.now(UTC)
self.publish(SubscriptionCancelledEvent(self.id, reason))
def needs_renewal(self) -> bool:
return self.current_period_end <= date.today()
# application/subscription/use_cases/renew_subscription.py
class RenewSubscriptionUseCase:
def __init__(self, subscription_repo: SubscriptionRepository,
payment_service: PaymentService,
event_bus: EventBus,
logger: Logger):
self.subscription_repo = subscription_repo
self.payment_service = payment_service
self.event_bus = event_bus
self.logger = logger
async def execute(self, subscription_id: UUID) -> Result:
async with transaction.atomic():
# Заблокируем подписку для других операций
sub = await self.subscription_repo.get_for_update(subscription_id)
if not sub.needs_renewal():
return Result.ok(sub)
# Создаём платёж
plan = await self.get_plan(sub.plan_id)
payment = Payment(
subscription_id=sub.id,
amount=plan.price,
currency=plan.currency,
payment_provider=sub.payment_provider
)
# Обрабатываем платёж
result = await self.payment_service.process(payment)
if result.is_ok():
# Обновляем подписку
sub.renewal_count += 1
sub.current_period_start = date.today()
sub.current_period_end = date.today() + timedelta(
days=365 if sub.billing_period == 'yearly' else 30
)
sub.publish(SubscriptionRenewedEvent(sub.id))
else:
self.logger.error(f"Payment failed for subscription {sub.id}")
# Отправляем уведомление пользователю
sub.publish(SubscriptionRenewalFailedEvent(sub.id))
await self.subscription_repo.save(sub)
return result
3. Webhook обработчик
# presentation/api/webhooks.py
@router.post("/webhooks/stripe")
async def stripe_webhook(request: Request,
stripe_service: StripeService = Depends()):
# Верифицируем подпись
payload = await request.body()
signature = request.headers.get('Stripe-Signature')
event = stripe_service.verify_webhook(payload, signature)
# Отправляем на обработку через Celery
process_stripe_event.delay(event)
return {"ok": True}
# tasks/webhooks.py
@app.task(bind=True, max_retries=3)
def process_stripe_event(self, event: dict):
try:
event_type = event['type']
data = event['data']['object']
handlers = {
'payment_intent.succeeded': handle_payment_succeeded,
'payment_intent.payment_failed': handle_payment_failed,
'charge.refunded': handle_refund,
}
handler = handlers.get(event_type)
if handler:
handler(data)
except Exception as exc:
raise self.retry(exc=exc, countdown=2 ** self.request.retries)
Вызовы и решения
1. Идемпотентность при сбоях
Проблема: Если webhook пришёл дважды, платёж обработается дважды.
Решение:
class IdempotencyService:
async def process(self, key: str, operation, *args, **kwargs):
# Проверяем, обработали ли уже
result = await self.cache.get(f"idempotency:{key}")
if result:
return result
# Если нет, выполняем операцию
result = await operation(*args, **kwargs)
# Кешируем результат на час
await self.cache.set(f"idempotency:{key}", result, ttl=3600)
return result
# Использование
await idempotency_service.process(
key=f"payment_{payment_id}",
operation=process_payment_use_case.execute,
payment_id=payment_id
)
2. Консистентность данных
Проблема: При сбое сервера платёж обработан, но подписка не обновлена.
Решение: Transactional Outbox Pattern
class SubscriptionService:
async def renew(self, sub_id: UUID):
async with transaction.atomic():
# Обновляем подписку в одной транзакции
sub = await Subscription.objects.select_for_update().get(id=sub_id)
sub.current_period_end = date.today() + timedelta(days=30)
await sub.save()
# Записываем события в ту же транзакцию
await OutboxEvent.objects.create(
event_type='subscription.renewed',
data={'subscription_id': str(sub.id)}
)
# После commit все события отправятся асинхронно
3. Масштабирование
Проблема: 10k запросов/сек требует кеширования и оптимизации.
Решение:
class SubscriptionCache:
async def get(self, user_id: UUID) -> Subscription:
# Кешируем в Redis на 5 минут
cache_key = f"subscription:{user_id}"
cached = await self.redis.get(cache_key)
if cached:
return Subscription.parse_raw(cached)
# Если не в кеше, читаем из БД
sub = await Subscription.objects.get(user_id=user_id)
# Кешируем результат
await self.redis.setex(
cache_key,
300, # 5 минут
sub.json()
)
return sub
async def invalidate(self, user_id: UUID):
await self.redis.delete(f"subscription:{user_id}")
# Индекс в БД
class Subscription(models.Model):
class Meta:
indexes = [
models.Index(fields=['user_id', 'status']),
models.Index(
fields=['current_period_end'],
condition=Q(status='active'),
name='idx_active_subs_renew'
),
]
Результаты
- Обработка 10k запросов/сек с latency < 100ms
- Uptime 99.95% (4 часа downtime в год)
- Корректная обработка 500k пользователей
- Автоматическое восстановление при сбоях
- Zero дублирования платежей (идемпотентность)
- 90%+ test coverage
Ключевые уроки
- DDD и Clean Architecture работают отлично при высокой нагрузке
- Асинхронная обработка (Celery) критична для масштабирования
- Кеширование требуется для высокой пропускной способности
- Идемпотентность необходима при работе с внешними системами
- Мониторинг и logging помогают находить проблемы быстро
- Транзакции и SELECT FOR UPDATE предотвращают race conditions
Этот проект научил меня строить действительно надёжные системы, которые работают в production.