Requirements


Functional Requirements:


Core

  1. Vehicle entry
    • At entry gate: detect vehicle, identify type (car/motorbike/truck/EV), assign a spot (or deny if full).
    • Issue a ticket (QR / barcode / license-plate-linked) with entry timestamp.
  2. Vehicle exit
    • At exit gate: scan ticket / plate, compute fee, accept payment, open barrier.
    • Close ticket and free the spot.
  3. Spot allocation
    • Support spot types: COMPACT, LARGE, MOTORBIKE, EV_CHARGING, DISABLED.
    • Allocation rules (configurable): closest-to-entry, floor preference, EV only, etc.
  4. Real-time availability
    • Query available spots by lot/floor/spot-type.
    • Show counts and optionally “guidance” (zone-based).
  5. Payments
    • Payment methods: cash (optional), card, e-wallet.
    • Refund/cancel policy (optional).
    • Support grace period and lost-ticket fee policy.
  6. Admin operations
    • Manage lots/floors/spots (add/remove/disable).
    • Manage pricing rules and promotions.
    • Force open/close gates, mark spot as blocked/maintenance.
  7. Enforcement & audit
    • Keep history: tickets, payments, spot changes.
    • Dispute handling (manual override).

Nice-to-have (common in real deployments)

  • License Plate Recognition (LPR) to speed entry/exit.
  • Reservations (for premium members).
  • Multi-lot portfolio management.



Non-Functional Requirements:


  1. Availability: gates must work even if cloud is down.
    • Target: 99.9%+ overall; gate control should degrade gracefully.
  2. Latency: entry/exit should be fast.
    • Target: ticket issuance + allocation < 300ms p95 (excluding hardware scan).
  3. Consistency: no double-allocation of a spot.
    • Strong consistency for “spot occupancy state”.
  4. Scalability: handle peak bursts (e.g., stadium).
    • Many gates → high contention on allocation.
  5. Reliability & Recovery
    • Handle lost connectivity: local cache + sync.
    • Idempotent APIs to avoid double charges.
  6. Security
    • RBAC for admin, audit logs.
    • PCI scope minimized: payment tokenization via PSP.
  7. Observability
    • Metrics: gate latency, allocation failures, payment failures, occupancy accuracy.
  8. Maintainability
    • Pricing rules configurable (not hard-coded).
  9. Data privacy
    • Plate numbers are PII in many regions; encrypt at rest; retention policy.


API Design

Key domain objects

  • ParkingLot(lotId, name, address, timezone)
  • Floor(floorId, lotId, level)
  • Spot(spotId, floorId, type, status: AVAILABLE|OCCUPIED|OUT_OF_SERVICE|RESERVED, zone)
  • Ticket(ticketId, lotId, vehicle: {plate?, type}, entryGateId, entryAt, assignedSpotId, status: ACTIVE|CLOSED|LOST)
  • Payment(paymentId, ticketId, amount, currency, method, status, providerRef)
  • PricingRule(ruleId, lotId, version, definition)


Public-facing APIs (for gate devices + apps)

Entry flow

POST /v1/tickets/entry

{ "lotId": "L1", "gateId": "GATE_E_01", "vehicle": { "type": "CAR", "plate": "SGP1234A" }, "preferences": { "needEvCharging": false, "needDisabled": false } }

Response:

{ "ticketId": "T_9f...", "issuedAt": "2026-02-24T10:01:22+08:00", "spot": { "spotId": "S_2_041", "floorId": "F2", "zone": "B" }, "barcode": "....", "status": "ACTIVE" }

Exit pricing quote (optional but practical)

GET /v1/tickets/{ticketId}/quote

Response:

{ "ticketId": "T_9f...", "entryAt": "...", "exitAt": "...", "amount": 6.50, "currency": "SGD", "breakdown": [{"rule":"weekday_hourly","amount":6.50}] }

Pay

POST /v1/payments

{ "ticketId": "T_9f...", "method": "CARD", "paymentToken": "tok_xxx", "idempotencyKey": "uuid-unique" }

Exit confirmation / close ticket

POST /v1/tickets/{ticketId}/exit

{ "gateId": "GATE_X_02", "paidPaymentId": "P_12...", "vehiclePlate": "SGP1234A" }

Availability

GET /v1/lots/{lotId}/availability?spotType=COMPACT

Response:

{ "lotId":"L1", "counts": { "COMPACT": 120, "EV_CHARGING": 8 } }

Admin APIs

  • PUT /v1/spots/{spotId} (status, type)
  • POST /v1/pricingRules/publish (new version)
  • GET /v1/reports/occupancy?from=...&to=...

API correctness rules (important)

  • All state-changing endpoints support Idempotency-Key.
  • Gate endpoints must tolerate retries without double-allocations or double-charges.
  • Use server time (lot timezone) for billing.




High-Level Design

Architecture style

This is a classic edge + central system:

  • Edge (on-prem at parking lot) for low latency + offline:
    • Gate Controller Service (local)
    • Local cache / local DB (small)
  • Central (cloud) for portfolio mgmt, pricing, reporting:
    • Ticketing, Allocation, Pricing, Payment Orchestration, Admin, Analytics

3.2 Services (logical)

  1. Gate Service (Edge)
    • Talks to barrier, scanner, LPR camera.
    • Calls central APIs; falls back to local logic if offline.
  2. Ticket Service
    • Creates/updates tickets, lifecycle.
  3. Spot Inventory Service
    • Source of truth for spot status, occupancy counts.
  4. Allocation Service
    • Chooses a spot (policy-based). Must be consistent + low latency.
  5. Pricing Service
    • Computes fee based on rules + ticket timeline.
  6. Payment Service
    • Integrates PSP, manages idempotency, payment states.
  7. Admin/Config Service
    • Lots, floors, spots config; pricing publish; user roles.
  8. Event Bus / Stream
    • TicketCreated, SpotOccupied, PaymentCompleted, TicketClosed
    • Feeds analytics, monitoring, reconciliation.

Data stores

  • Spot Inventory DB (needs strong consistency)
    • Postgres with row-level locks OR Redis + Lua + persistence OR specialized allocator store.
  • Ticket DB
    • Postgres (transactional), partition by lotId/time.
  • Event store/stream
    • Kafka / Pulsar (optional), or cloud pub/sub.

Key flows (HLD)

Entry

  1. Gate scans plate/type → calls Ticket Service.
  2. Ticket Service calls Allocation Service.
  3. Allocation Service reserves spot via Inventory Service (atomic).
  4. Ticket created with assigned spot.
  5. Gate prints/returns QR; barrier opens.

Exit

  1. Gate scans ticket/plate → Ticket Service fetches ticket.
  2. Pricing Service computes amount.
  3. Payment Service processes payment (idempotent).
  4. Ticket Service closes ticket + Inventory frees spot.
  5. Barrier opens.




Detailed Component Design

Spot Inventory + Allocation (the hard part)

Core invariants

  • A spot can be OCCUPIED by at most one ACTIVE ticket.
  • Ticket closure must free exactly one spot.
  • Allocation must avoid “overselling” during concurrency spikes.

Approach A (recommended): DB-backed allocation with atomic reservation

Schema (simplified)

  • spots(spot_id, lot_id, floor_id, type, status, zone, updated_at)
  • spot_reservations(spot_id, ticket_id, reserved_at, expires_at, state)
  • tickets(ticket_id, lot_id, spot_id, status, entry_at, exit_at, vehicle_plate_hash, ...)

Algorithm

  • Pick candidate spots by policy (zone/floor/type).
  • Reserve with atomic update:
    • UPDATE spots SET status='OCCUPIED' WHERE spot_id=? AND status='AVAILABLE'
    • If affected rows = 1 → success.
  • If fail → try next candidate.

Trade-offs

  • ✅ Strong correctness, easy audit
  • ✅ Works with Postgres + indexes
  • ❌ Contention if you scan too wide; mitigate via zone counters + shortlist

Improve performance: Zone-based counters + shortlist

Maintain zone_availability(lot_id, zone, type, available_count) updated transactionally.

Allocation:

  1. Choose best zone with available_count > 0.
  2. Query only that zone for a spot.
  3. Reserve with atomic update.

This reduces lock contention.

Expiring reservations

If you want “soft reservation” (issue ticket but car hasn’t reached spot), use RESERVED state with TTL; if not occupied within N minutes, release.


Ticket Service design

State machine

  • ACTIVE → (CLOSED | LOST)
  • Close requires: payment satisfied OR policy allows pay-on-exit cash.

Idempotency

  • POST /tickets/entry with idempotencyKey from gate.
  • Store request hash to return same ticket on retry.

Plate handling

  • Store plate hashed/encrypted. Keep raw plate only if absolutely necessary and compliant.

Pricing Service (rules engine-lite)

Why: pricing changes constantly; don’t hardcode.

Rule model

  • Versioned JSON rules:
    • time bands, day types, free minutes, max daily cap, overnight, EV surcharge, holidays.
  • Evaluate:
    • Build timeline from entryAt → exitAt
    • Apply rules in order (priority)
    • Return breakdown for transparency

Trade-offs

  • ✅ Flexible
  • ❌ Complexity. Keep it small: don’t build a full DSL unless needed.

Payment Service

Key concerns

  • Idempotency to avoid double charge:
    • Use idempotencyKey per ticket payment attempt.
  • Payment states:
    • INITIATED -> AUTHORIZED -> CAPTURED/FAILED -> REFUNDED
  • Integrate PSP via tokenization (minimize PCI).

Exit gate behavior

  • Gate should not depend on payment callback.
  • Use synchronous capture if possible; if async, allow “pending exit” policy.

Edge Gate Service (offline-first)

Responsibilities

  • Device integration: barrier, printer, scanner, LPR.
  • Local fallback:
    • Cache last known availability + pricing rules snapshot.
    • If cloud down: issue offline tickets with local sequence prefix (lotId + timestamp + counter).
    • Sync when connection restored.

Conflict handling

  • Offline allocation cannot guarantee global correctness.
  • Mitigation:
    • Use local inventory partition per gate/zone (pre-allocated quota) so offline doesn’t oversell.
    • Or operate in degraded mode: “allow entry but no guaranteed spot” (depends on business).

Observability & audit

Metrics

  • Allocation success rate, retries per allocation
  • Ticket create latency p95/p99
  • Payment failure rate
  • Occupancy accuracy (sensor vs DB)
  • Offline mode duration

Logs

  • Correlate by ticketId, gateId, requestId

Events

  • Emit domain events for analytics and reconciliation.