← Назад к вопросам
Как выглядел полный процесс — от идеи в 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"
Уточнение требований
Мойпервый шаг — расшифровать требование:
-
Созваниваюсь с Product Manager:
- Какие события триггерят уведомления?
- Какой текст должен быть в письме/SMS?
- Есть ли ограничения на частоту отправки?
- Нужно ли отслеживать статус доставки?
-
Общаюсь с QA:
- Как тестировать email/SMS в staging?
- Нужны ли тестовые номера?
-
Проверяю архитектуру:
- Есть ли уже очередь сообщений (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 всегда приводит к багам в продакшене.