← Назад к вопросам

Как выглядел полный процесс — от идеи в JIRA до завершённой задачи в продакшене?

2.2 Middle🔥 241 комментариев
#DevOps и инфраструктура

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Полный цикл разработки: от JIRA до продакшена

Это была моя стандартная рабочая流程 в компаниях с Agile методологией. Опишу весь цикл на примере конкретной задачи.

Этап 1: Планирование и уточнение (День 1 утро)

Получение задачи в JIRA

JIRA Ticket: BACKEND-1234
Тип: Story / Task
Время на оценку: 2-3 дня спринта
Приоритет: High

Описание:
"Реализовать систему уведомлений пользователям при
покупке в их аккаунте по email и SMS"

Уточнение требований

Мойпервый шаг — расшифровать требование:

  1. Созваниваюсь с Product Manager:

    • Какие события триггерят уведомления?
    • Какой текст должен быть в письме/SMS?
    • Есть ли ограничения на частоту отправки?
    • Нужно ли отслеживать статус доставки?
  2. Общаюсь с QA:

    • Как тестировать email/SMS в staging?
    • Нужны ли тестовые номера?
  3. Проверяю архитектуру:

    • Есть ли уже очередь сообщений (RabbitMQ, Celery)?
    • Какой email/SMS сервис используется (SendGrid, Twilio)?

Этап 2: Дизайн архитектуры (День 1 день)

Я рисую диаграмму

User Action (purchase)
        ↓
Event Bus / Kafka
        ↓
Notification Service
        ├─→ EmailWorker (Celery task) → SendGrid API
        ├─→ SMSWorker (Celery task) → Twilio API
        └─→ NotificationLog (DB)
        
Фронтенд может спрашивать статус в /api/notifications

Создаю ADR (Architecture Decision Record)

# docs/architecture/notifications_design.md

# Решение: Асинхронная обработка через Celery

## Почему Celery?
- Email/SMS отправляют медленно (100-500мс)
- Не блокируем основной request
- Можем retry при ошибках
- Легко масштабировать

## Альтернативы рассмотрены?
- WebhookQueue: слишком сложно для этого случая
- Синхронная отправка: блокирует request

## Выбор: Celery + Redis

Этап 3: TDD разработка (День 1-2)

Шаг 1: Пишу тесты (RED)

# tests/unit/notifications/test_send_email.py

from datetime import datetime, timezone
from unittest.mock import patch, MagicMock
import pytest
from app.notifications.tasks import send_purchase_email
from app.notifications.models import NotificationLog


def test_send_purchase_email_successful(session):
    """Email отправляется успешно и логируется"""
    user_email = 'john@example.com'
    order_id = 'order_123'
    amount = '99.99'
    
    with patch('app.notifications.tasks.sendgrid_client.send') as mock_send:
        mock_send.return_value = {'status': 200}
        
        result = send_purchase_email(user_email, order_id, amount)
    
    # Проверяем, что email отправлен
    assert result['status'] == 'sent'
    mock_send.assert_called_once()
    
    # Проверяем, что залогировано в БД
    log = session.query(NotificationLog).filter_by(
        user_email=user_email, order_id=order_id
    ).one()
    assert log.notification_type == 'purchase_email'
    assert log.status == 'sent'
    assert log.created_at.tzinfo is not None  # Timezone aware


def test_send_purchase_email_retry_on_failure(session):
    """При ошибке сохраняем статус и можем retry"""
    
    with patch('app.notifications.tasks.sendgrid_client.send') as mock_send:
        mock_send.side_effect = Exception('SendGrid API error')
        
        with pytest.raises(Exception):
            send_purchase_email('john@example.com', 'order_123', '99.99')
    
    # Задача залогирована с статусом failed
    log = session.query(NotificationLog).order_by(
        NotificationLog.created_at.desc()
    ).first()
    assert log.status == 'failed'
    assert log.error_message == 'SendGrid API error'
    assert log.retry_count == 0


def test_sms_rate_limiting():
    """SMS не отправляются если уже отправляли в последний час"""
    user_phone = '+79999999999'
    
    # Первый SMS отправляется
    send_purchase_sms(user_phone, 'order_123')
    log1 = NotificationLog.query.order_by(desc(created_at)).first()
    assert log1.status == 'sent'
    
    # Второй SMS в тот же час — пропускается
    result = send_purchase_sms(user_phone, 'order_456')
    assert result['status'] == 'rate_limited'
    assert result['reason'] == 'Already sent SMS in last hour'

Шаг 2: Пишу минимальный код (GREEN)

# app/notifications/tasks.py

from celery import shared_task
from datetime import datetime, timezone, timedelta
from sqlalchemy.orm import Session
from app.notifications.models import NotificationLog
from app.notifications.services import sendgrid_client, twilio_client
from app.core.db import get_session


@shared_task(bind=True, max_retries=3)
def send_purchase_email(self, user_email: str, order_id: str, amount: str):
    """Отправить email при покупке"""
    session = get_session()
    
    try:
        # Отправляем email
        response = sendgrid_client.send(
            to_email=user_email,
            template_id='purchase_email',
            dynamic_data={'order_id': order_id, 'amount': amount}
        )
        
        # Логируем успех
        log = NotificationLog(
            user_email=user_email,
            order_id=order_id,
            notification_type='purchase_email',
            status='sent',
            created_at=datetime.now(timezone.utc),
        )
        session.add(log)
        session.commit()
        
        return {'status': 'sent', 'message_id': response.get('id')}
    
    except Exception as exc:
        # Логируем ошибку
        log = NotificationLog(
            user_email=user_email,
            order_id=order_id,
            notification_type='purchase_email',
            status='failed',
            error_message=str(exc),
            retry_count=self.request.retries,
            created_at=datetime.now(timezone.utc),
        )
        session.add(log)
        session.commit()
        
        # Retry через 5 минут
        raise self.retry(exc=exc, countdown=300)


@shared_task(bind=True, max_retries=3)
def send_purchase_sms(self, user_phone: str, order_id: str):
    """Отправить SMS при покупке с rate limiting"""
    session = get_session()
    
    # Проверяем rate limit: не более 1 SMS в час
    last_sms = session.query(NotificationLog).filter(
        NotificationLog.user_phone == user_phone,
        NotificationLog.notification_type == 'purchase_sms',
        NotificationLog.created_at > datetime.now(timezone.utc) - timedelta(hours=1),
        NotificationLog.status == 'sent',
    ).first()
    
    if last_sms:
        return {'status': 'rate_limited', 'reason': 'Already sent SMS in last hour'}
    
    try:
        response = twilio_client.messages.create(
            body=f'Your order {order_id} is confirmed!',
            from_='+1234567890',
            to=user_phone,
        )
        
        log = NotificationLog(
            user_phone=user_phone,
            order_id=order_id,
            notification_type='purchase_sms',
            status='sent',
            created_at=datetime.now(timezone.utc),
        )
        session.add(log)
        session.commit()
        
        return {'status': 'sent', 'message_sid': response.sid}
    
    except Exception as exc:
        log = NotificationLog(
            user_phone=user_phone,
            order_id=order_id,
            notification_type='purchase_sms',
            status='failed',
            error_message=str(exc),
            retry_count=self.request.retries,
            created_at=datetime.now(timezone.utc),
        )
        session.add(log)
        session.commit()
        
        raise self.retry(exc=exc, countdown=300)

Шаг 3: Запускаю тесты

make test  # Все тесты проходят (GREEN) ✓
make lint  # Линтер доволен ✓
pytest tests/unit/notifications/ --cov --cov-fail-under=90

Этап 4: Интеграция с существующей системой (День 2 вечер)

Подключаю к event'ам покупки

# app/orders/handlers.py

from app.notifications.tasks import send_purchase_email, send_purchase_sms


def handle_order_created(order_id: str, user_id: str):
    """Триггер при создании заказа"""
    order = Order.query.get(order_id)
    user = User.query.get(user_id)
    
    # Отправляем уведомления асинхронно
    send_purchase_email.delay(
        user_email=user.email,
        order_id=order_id,
        amount=str(order.total_amount),
    )
    
    if user.phone and user.opted_in_sms:
        send_purchase_sms.delay(
            user_phone=user.phone,
            order_id=order_id,
        )

Создаю миграцию БД

-- migrations/0042_create_notification_logs.sql

CREATE TABLE notification_logs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_email VARCHAR(255),
    user_phone VARCHAR(20),
    order_id VARCHAR(100),
    notification_type VARCHAR(50) NOT NULL,
    status VARCHAR(20) NOT NULL,
    error_message TEXT,
    retry_count INT DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL
);

CREATE INDEX idx_notifications_user_email ON notification_logs(user_email);
CREATE INDEX idx_notifications_created_at ON notification_logs(created_at);

Запускаю миграцию:

goose -dir migrations postgres "$DATABASE_URL" up

Этап 5: Тестирование (День 3 утро)

Unit тесты проходят

pytest tests/unit/notifications/ -v
# ✓ test_send_purchase_email_successful
# ✓ test_send_purchase_email_retry_on_failure
# ✓ test_sms_rate_limiting
# Coverage: 92%

Интеграционные тесты в staging

# tests/integration/test_notifications.py

from app.orders.handlers import handle_order_created


def test_order_created_sends_email_and_sms(app, session):
    """Полный цикл: создание заказа → уведомления"""
    
    # Создаём тестового пользователя
    user = User(email='test@example.com', phone='+7999999999')
    session.add(user)
    session.commit()
    
    # Создаём заказ
    order = Order(user_id=user.id, total_amount=99.99)
    session.add(order)
    session.commit()
    
    # Триггерим событие
    handle_order_created(order_id=order.id, user_id=user.id)
    
    # Даём Celery время на обработку
    app.celery.conf.task_always_eager = True
    
    # Проверяем, что логи созданы
    logs = session.query(NotificationLog).filter_by(order_id=order.id).all()
    assert len(logs) == 2  # Email и SMS
    assert all(log.status == 'sent' for log in logs)

Тестирую в staging вручную

# SSH в staging сервер
ssh ubuntu@staging.example.com

# Проверяю Celery очередь
celery -A app.celery inspect active

# Смотрю логи Celery
tail -f /var/log/app/celery.log

# Отправляю тестовый заказ через API
curl -X POST http://localhost:8000/api/v1/orders \
  -H "Authorization: Bearer $TEST_TOKEN" \
  -d '{"items": [{"product_id": "prod_123"}]}'

# Проверяю, что письмо пришло на тестовый email

Этап 6: Code Review (День 3 вечер)

Создаю Pull Request

ПР: [BACKEND-1234] Add purchase notifications (email + SMS)

Что сделано:
- Создал NotificationLog модель для отслеживания
- Реализовал Celery tasks для email и SMS
- Добавил rate limiting на SMS
- Покрытие тестами: 92%
- Документирован в docs/architecture/notifications_design.md

Тестирование:
✓ Unit tests: 8/8 passed
✓ Integration tests: 2/2 passed
✓ Manual testing in staging: OK

Чеклист:
✓ Нет console.log / TODO
✓ Все типы явно указаны
✓ Нет нарушений архитектуры
✓ Migrations откачены и накачены

Коллеги дают feedback

  • Reviewer 1: "Добавь мониторинг на failed tasks"

    • Исправляю: добавляю Sentry интеграцию
  • Reviewer 2: "Rate limit проверяется в коде? Не лучше ли в Redis?"

    • Исправляю: использую Redis для rate limit

Апрув + мерж в main

git checkout main
git pull origin main
git merge --squash feature/notifications
git commit -m "[BACKEND-1234] Add purchase notifications"

Этап 7: Развёртывание в продакшен (День 4 утро)

Подготовка

# Проверяю, что миграция работает
goose -dir migrations postgres "$PROD_DATABASE_URL" up

# Проверяю, что Celery task зарегистрирована
celery -A app.celery inspect registered_tasks | grep send_purchase

# Проверяю environment variables в prod
echo $SENDGRID_API_KEY
echo $TWILIO_AUTH_TOKEN

Развёртывание

# Через Dokku (push deploy)
git push dokku main

# Или вручную через CI/CD (GitHub Actions, GitLab CI)

Мониторинг после развёртывания

# Смотрю логи Celery
dokku logs app celery-worker

# Проверяю метрики в DataDog/New Relic
# - Сколько email успешно отправлено
# - Сколько failed tasks
# - Time to send (P50, P95)

# Смотрю rate limiting в Redis
redis-cli
> GET "sms_rate_limit:+79999999999"

Финализация

# В JIRA ставлю статус "Done"
# Комментарий: "Развёрнуто в prod, мониторинг OK"

# Закрываю PR

# Пишу ретроспективу:
# - Что пошло хорошо: TDD помог поймать баги до prod
# - Что можно улучшить: добавить webhook для отслеживания доставки

Ключевые моменты полного цикла

# Цикл:
IDEA (JIRA)
  ↓ (1 день)
DESIGN + CLARIFICATION
  ↓ (1-2 дня)
TDD DEVELOPMENT (RED → GREEN → REFACTOR)
  ↓ (1 день)
INTEGRATION + TESTING (unit + integration + staging)
  ↓ (1 день)
CODE REVIEW + FIXES
  ↓ (1 день)
PRODUCTION DEPLOYMENT
  ↓ (1 день)
MONITORING + CLOSING

Время: ~5-7 дней спринта на полный цикл

Заключение

Полный цикл включает не только код, но и:

  • Уточнение требований
  • Дизайн архитектуры
  • TDD разработку
  • Полное тестирование (unit, integration, staging)
  • Code review
  • Безопасное развёртывание
  • Мониторинг в prod

Важно: каждый этап имеет значение, и пропуск тестирования или review всегда приводит к багам в продакшене.