Вот основные **Functional Requirements** для системы парковки:
## Functional Requirements:
**Vehicle Entry:**
- Система должна регистрировать въезд транспортного средства (время, номер, тип)
- Поддержка различных типов транспортных средств (мотоциклы, легковые, грузовые, инвалидные места)
- Автоматическое определение свободного места и назначение парковочного слота
- Генерация уникального ticket/token для каждого автомобиля
- Поддержка license plate recognition (LPR) для бесконтактного въезда
**Vehicle Exit:**
- Регистрация выезда транспортного средства
- Расчет стоимости парковки на основе времени и типа ТС
- Обработка платежей (наличные, карты, мобильные платежи)
- Валидация ticket перед открытием шлагбаума
- Освобождение парковочного слота
**Parking Slot Management:**
- Отслеживание статуса каждого слота в реальном времени (свободен/занят)
- Поддержка разных размеров слотов (compact, regular, large, handicap)
- Резервирование мест через мобильное приложение
- Приоритизация слотов (ближние к выходу, VIP, disabled)
**Payment System:**
- Поддержка почасовой/поминутной тарификации
- Интеграция с платежными провайдерами
- Генерация чеков и инвойсов
- Поддержка абонементов и месячных подписок
**Monitoring & Reporting:**
- Dashboard с occupancy rate в реальном времени
- Логирование всех событий (въезд/выезд/оплата)
- Отчеты по выручке, загрузке, популярным часам
- Алерты при заполнении >90%
**User Features:**
- Мобильное приложение для поиска/резервирования мест
- Уведомления о времени истечения парковки
- История парковок пользователя
- Поддержка QR-кодов для оплаты
**Admin Features:**
- Управление тарифами и правилами
- Конфигурация парковочных зон
- Управление пользователями и доступом
- Экспорт данных для аналитики
**Security:**
- Аутентификация для доступа к admin panel
- Шифрование платежных данных
- Audit log всех критических операций
High Availability — система должна быть доступна 24/7. Допустимый downtime — менее 1 часа в год (99.99% uptime). Используем Redis Cluster с master-replica failover или Redis Sentinel для автоматического переключения. Кеш является single source of truth для статуса мест, что также гарантирует strong consistency через single-threaded обработку команд.
Strong Consistency — система должна гарантировать отсутствие double-booking парковочных мест. Все операции записи статуса слота (занят/свободен) проходят через single-threaded Redis, что исключает race conditions.
Scalability — система должна поддерживать тысячи парковок в разных городах/странах. Горизонтальное масштабирование через partition по lot_id. API layer и application servers масштабируются независимо.
Performance — время отклика для операций въезда/выезда ≤ 200ms (p95). Чтение статуса мест ≤ 50ms. Поддержка 1000+ concurrent operations на одну парковку.
Reliability — система должна корректно обрабатывать сбои оборудования (шлагбаумы, камеры LPR, платежные терминалы). Graceful degradation при потере связи с центральным сервером.
Security — шифрование платежных данных (PCI DSS compliance). Аутентификация и авторизация для admin panel. Audit log всех критических операций.
Maintainability — модульная архитектура с четким разделением ответственности. CI/CD pipeline для автоматического деплоя. Monitoring и alerting для всех критических метрик.
Теперь скажите coach, что добавили NFR — он перепроверит оценку.
Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
Отлично, давайте спроектируем API для системы парковки. Разделим на логические группы:
text
POST /api/v1/parking-lots/{lot_id}/entry
Request: { license_plate, vehicle_type, ticket_id? }
Response: { ticket_id, slot_id, entry_time, barcode/qr_code }
Latency: ≤ 200ms (p95)
POST /api/v1/parking-lots/{lot_id}/exit
Request: { ticket_id, license_plate? }
Response: { amount, duration, payment_required, exit_granted }
Latency: ≤ 200ms (p95)
text
GET /api/v1/parking-lots/{lot_id}/slots/availability
Query: { vehicle_type?, floor? }
Response: { available_slots: [{slot_id, type, location}], total_available }
Latency: ≤ 50ms
GET /api/v1/parking-lots/{lot_id}/slots/{slot_id}/status
Response: { status: occupied|available|reserved, vehicle_info?, reserved_until? }
text
POST /api/v1/reservations
Request: { lot_id, slot_id?, vehicle_type, start_time, duration, user_id }
Response: { reservation_id, slot_id, qr_code, expires_at }
Idempotency key: required
PUT /api/v1/reservations/{reservation_id}/cancel
Response: { cancelled: true, refund_amount? }
GET /api/v1/users/{user_id}/reservations
Response: { reservations: [...] }
text
POST /api/v1/payments
Request: { ticket_id/reservation_id, payment_method, amount }
Response: { payment_id, status: success|failed, receipt_url }
GET /api/v1/payments/{payment_id}
Response: { amount, status, timestamp, receipt_url }
text
GET /api/v1/admin/parking-lots/{lot_id}/dashboard
Response: { occupancy_rate, revenue_today, available_slots, entry_queue_length }
GET /api/v1/admin/parking-lots/{lot_id}/reports
Query: { start_date, end_date, report_type: revenue|occupancy|traffic }
Response: { report_data: [...] }
PUT /api/v1/admin/parking-lots/{lot_id}/pricing
Request: { hourly_rate, daily_max, vehicle_type_rates: {...} }
text
GET /api/v1/health
Response: { status: healthy|degraded, redis_status, db_status, timestamp }
GET /api/v1/parking-lots/{lot_id}/status
Response: { operational: true|false, entry_available, exit_available, payment_available }
Key Design Decisions:
idempotency_keylot_id для горизонтального шардирования/status эндпоинт позволяет hardware (шлагбаумам) проверять availability перед запросомflowchart TB
subgraph Clients["Clients and Parking Hardware"]
Mobile[Mobile App / Web]
Admin[Admin Dashboard]
Gate[Entry / Exit Gate Controller]
LPR[LPR Camera / QR Scanner]
Terminal[Payment Terminal]
end
subgraph Edge["Edge Layer"]
LB[API Gateway / Load Balancer]
Local[Local Gate Controller<br/>offline fallback]
end
subgraph Services["Stateless Application Services"]
Auth[Auth Service]
Parking[Parking Service<br/>allocation, entry, exit]
Reservation[Reservation Service]
Payment[Payment Service]
Pricing[Pricing Service]
Notification[Notification Service]
AdminSvc[Admin / Reporting Service]
end
subgraph Realtime["Real-time State: partitioned by lot_id"]
Redis[(Redis Cluster<br/>slot state, holds,<br/>idempotency keys)]
Sentinel[Redis Replica / Sentinel<br/>automatic failover]
end
subgraph Durable["Durable Storage and Async Processing"]
DB[(PostgreSQL<br/>tickets, reservations,<br/>payments, tariffs)]
Queue[(Event Bus / Queue)]
Analytics[(Analytics / Data Warehouse)]
PSP[External Payment Provider]
end
Mobile --> LB
Admin --> LB
Gate --> LB
LPR --> Gate
Terminal --> Gate
Gate -. connection lost .-> Local
LB --> Auth
LB --> Parking
LB --> Reservation
LB --> Payment
LB --> Pricing
LB --> AdminSvc
Parking <--> Redis
Reservation <--> Redis
Payment --> PSP
Payment --> DB
Pricing --> DB
Auth --> DB
AdminSvc --> DB
Payment --> PSP
PSP -->|signed payment webhook| Payment
Payment --> DB
Payment -->|PaymentSucceeded / PaymentFailed| Queue
Queue --> Reservation
Reservation --> DB
Reservation --> Redis
Redis --- Sentinel
Parking --> Queue
Reservation --> Queue
Payment --> Queue
Queue --> Notification
Queue --> Analytics
Queue --> DB
Define the data model. Identify the main entities, their attributes, and relationships. Consider the choice of database type (SQL vs NoSQL) and justify your decision based on access patterns...
Вставь ниже в Detailed Component Design. Это закрывает три наиболее важных и рискованных части: атомарное выделение места, бронирование с оплатой и обработку въезда/выезда.[codemia]
Цель: гарантированно выбрать и занять подходящее место без double-booking при конкурентных запросах.
Для каждого lot_id хранить состояние в одном Redis shard:
text
slot:{lot_id}:{slot_id} -> {
status: AVAILABLE | HELD | RESERVED | OCCUPIED,
type: COMPACT | REGULAR | LARGE | HANDICAP,
zone_id,
priority_score,
reservation_id?,
ticket_id?,
expires_at?
}
available:{lot_id}:{vehicle_type} -> Redis Sorted Set
score = priority_score
member = slot_id
Sorted Set позволяет получить ближайший/приоритетный свободный слот через ZRANGE ... 0 0. Например, для обычного автомобиля сначала выбираем REGULAR, затем LARGE; для disabled-user — только HANDICAP.
Все действия выполняются одним Lua script на Redis primary для shard-а данного lot_id:
available:{lot_id}:{vehicle_type}.AVAILABLE.HELD или OCCUPIED.reservation_id/ticket_id.HELD устанавливает TTL, например 10 минут.slot_id.Поскольку Lua script исполняется атомарно и Redis обрабатывает команды последовательно, два клиента не смогут получить один слот. Redis lock здесь не нужен: он добавил бы время ожидания и риск stale lock.
lot_id: все слоты одной парковки находятся в одном shard, поэтому атомарное выделение не требует distributed transaction.ZADD/ZREM/ZRANGE, где NNN — число слотов нужного типа; при тысячах слотов latency остаётся низкой.lot_id. Это приемлемо, поскольку один слот не принадлежит нескольким парковкам.HELD в AVAILABLE и снова добавляет его в Sorted Set.idempotency_key; сохранённый результат предотвращает повторное выделение.Цель: превратить временный hold в подтверждённую бронь только после фактической оплаты, без распределённой транзакции между Redis, PostgreSQL и PSP.
text
PENDING_PAYMENT
-> CONFIRMED (PaymentSucceeded)
-> PAYMENT_FAILED (PaymentFailed)
-> EXPIRED (hold TTL expired)
-> CANCELLED (user cancellation)
CONFIRMED
-> CHECKED_IN
-> NO_SHOW (grace period expired)
-> CANCELLED
CHECKED_IN
-> COMPLETED (vehicle exit)
POST /reservations/hold вызывает Slot Allocation Service. Тот атомарно переводит слот AVAILABLE -> HELD и создаёт TTL, например 10 минут.PENDING_PAYMENT, а Payment Service создаёт payment_intent у PSP.POST /payments/webhooks/psp; Payment Service проверяет подпись webhook, payment_intent_id, сумму, currency и уникальность provider_event_id.PaymentSucceeded(reservation_id).PENDING_PAYMENT -> CONFIRMED, а слот HELD -> RESERVED; после этого создаётся QR/token для въезда.PaymentFailed, отмене или expiry Reservation Service освобождает слот и при необходимости инициирует refund.PaymentSucceeded; следовательно, оплаченная бронь не может быть подтверждена дважды.provider_event_id и идемпотентный consumer делают повтор безопасным.CONFIRMED, зато нет хрупкой двухфазной транзакции с внешним PSP.[codemia]Цель: поддерживать быстрый въезд/выезд и продолжать работу при временной потере связи с центральным backend.
license_plate либо QR token в локальный Gate Controller.POST /parking-lots/{lot_id}/entry с idempotency_key, уникальным для попытки проезда.RESERVED -> OCCUPIED, создаёт ticket и возвращает gate_open=true.ticket_id.POST /parking-lots/{lot_id}/exit.tariff(ticket, entry_time, exit_time), проверяет результат оплаты и только затем переводит слот OCCUPIED -> AVAILABLE.VehicleExited для истории, аналитики и receipt.exit_granted=true; повторные сообщения не освобождают слот второй раз благодаря ticket_id и idempotency_key.event_id; backend дедуплицирует их.