System Design
Microservices Architecture
1. Project Overview¶
TrafficGrid consolidates vehicle records, traffic offences, parking fines, and payment processing for Harare into a single unified digital platform. Officers log offences via mobile. Citizens view their vehicle records and pay fines electronically. All actions are audited.
This document is version 2.1, superseding v2.0. The core microservices architecture is unchanged. This revision retires fines-service from the service inventory — it was planned but never built as a separate deployed process; fines and parking_tickets are a permanent part of payments-service instead (see Data Model.md §4.11 for the rationale) — and adds the trafficgrid.users Kafka topic introduced alongside that work. (v2.0 corrected the service inventory to make notifications-service a dedicated service rather than co-located in payments-service, removed system-parameters from the shared-library category, and added Kafka for all asynchronous communication.)
2. Service Inventory¶
The system is composed of 9 running processes. core-service is a shared Maven dependency (published to GitHub Packages) — it is not a running process.
| Service | Port | Responsibility |
|---|---|---|
| discovery-service | 8761 | Eureka service registry - all services register here on startup |
| config-server | 8888 | Centralised configuration served to all services from Git or classpath |
| api-gateway | 8080 | Single entry point — auth validation, routing, rate limiting, SSL termination |
| auth-service | 8081 | User registration, login, JWT issuance, refresh token rotation, RBAC |
| audit-service | 8082 | Receives audit events and persists immutable audit_logs records |
| system-parameters-service | 8083 | settings in the system like currencies, notification channels, organizations, fine categories |
| vehicle-service | 8084 | vehicles, vehicle documents, citizen vehicle records |
| payments-service | 8086 | fines, parking tickets, payments, payment items |
| notifications-service | 8088 | notifications, notification preferences |
fines-service retirement¶
fines-service (formerly port 8085) was planned as a dedicated service but was never built, and is no longer part of the architecture. payments-service permanently owns fines and parking tickets in addition to payments and payment items. Fines and the payments that settle them are tightly coupled: a successful payment must mark its fines PAID, and consolidating them keeps that state change inside one service and one schema instead of crossing a service boundary. The trafficgrid.fines topic and its fine.created / fine.status.updated events are unchanged; only the publisher is now payments-service. Fine categories remain in system-parameters-service. Port 8085 is unassigned.
core-service¶
core-service is a shared JAR dependency published to GitHub Packages. It is imported by all services as a Maven dependency. It is not deployed or registered with Eureka. It contains: response envelope shape, pagination wrappers, common exception types, and shared HTTP error handling. Domain-specific concerns (enums, entity classes, business logic) must never be placed in core-service — each service owns its own domain types unless it's explicitly needed to be a global dependency.
3. Architecture Style¶
Each domain service owns its own PostgreSQL schema (or separate database). No service queries another service's database directly. Cross-service data is accessed through HTTP API calls (via OpenFeign) or consumed via asynchronous events published to Kafka topics.
All client traffic (mobile, web, officer app) enters exclusively through the API Gateway. The gateway validates JWT tokens and forwards decoded claims as request headers (X-User-Id, X-User-Role, X-Organisation-Id) so downstream services do not need to call auth-service on each request.
3.1 Communication Patterns¶
| Pattern | When Used | Example |
|---|---|---|
| Synchronous Rest (OpenFeign) | Query-response where the caller needs data immediately | payments-service calls vehicle-service to verify a vehicle exists before issuing a fine |
| OpenFeign to system-parameters | Runtime reference data lookup | Any service calls system-parameters to resolve currencies, notification channel config, or fine category details |
| Async Kafka events | State changes that other services react to independently | payments-service publishes to the trafficgrid.payments topic; notifications-service and audit-service both consume from it |
| Gateway header forwarding | Propagating auth context to downstream services | Gateway decodes JWT and sets X-User-Role: ZRP_OFFICER on every forwarded request |
3.2 Kafka — Message Architecture¶
TrafficGrid uses Kafka topics, partitioned by aggregate ID, to decouple producers from consumers. A producer publishes to a topic and does not know which services consume the message; any service can subscribe by starting a new consumer group without requiring any change to the publisher. Full design detail — partitioning strategy, consumer groups, delivery semantics, retry/dead-letter handling — lives in the dedicated Kafka Design page. The summary below covers only the topic inventory.
Topic Design
| Topic | Partition Key | Description |
|---|---|---|
| trafficgrid.vehicles | vehicle_id | Vehicle domain events |
| trafficgrid.fines | fine_id | Fine and parking ticket events |
| trafficgrid.payments | payment_id | Payment transaction events |
| trafficgrid.users | entity_id / user_id | User, role and permission management events |
| trafficgrid.notifications | notification_id | Notification dispatch commands (internal) |
Events By Topic
| Topic | Event Type | Publisher | Consumers | Trigger |
|---|---|---|---|---|
| trafficgrid.fines | fine.created | payments-service | audit-service, notifications-service | New fine issued against a vehicle |
| trafficgrid.fines | fine.status.updated | payments-service | audit-service, notifications-service | Fine cleared or cancelled by an admin |
| trafficgrid.fines | fine.dispute.opened | payments-service | audit-service, notifications-service (planned) | Citizen disputes a fine |
| trafficgrid.fines | fine.dispute.withdrawn | payments-service | audit-service, notifications-service (planned) | Citizen withdraws their dispute |
| trafficgrid.fines | fine.dispute.decided | payments-service | audit-service, notifications-service (planned) | Reviewer upholds or rejects a dispute |
| trafficgrid.payments | payment.success | payments-service | payments-service (settles covered fines), audit-service, notifications-service | Payment confirmed by provider |
| trafficgrid.payments | payment.failed | payments-service | audit-service, notifications-service | Payment declined or failed |
| trafficgrid.vehicles | vehicle.document.expiry | vehicle-service | notifications-service | Vehicle document within 30 days of expiry |
| trafficgrid.users | user.created | auth-service | audit-service | User account created |
| trafficgrid.users | user.deactivated | auth-service | audit-service | User account deactivated |
| trafficgrid.users | role.created | auth-service | audit-service | Role created |
| trafficgrid.users | role.updated | auth-service | audit-service | Role updated |
| trafficgrid.users | role.deleted | auth-service | audit-service | Role deleted |
| trafficgrid.users | role.permissions.assigned | auth-service | audit-service | Permissions assigned to a role |
trafficgrid.users carries auth-service's own state-changing actions (user and role/permission management — see Roles and Permissions.md) and replaces its earlier single audit.events topic. Session lifecycle actions (login, logout, access-token refresh) deliberately publish no events.
Consumer Group Pattern
Each consumer service subscribes with its own consumer group (e.g. notifications-service, audit-service). Every consumer group gets its own copy of every event on the topics it subscribes to, independent of what other groups consume — so adding a new consumer never requires changes to the publisher. Within a group, partitions are divided across instances, which is how a service scales horizontally without duplicate processing.
notifications-service Communication Model notifications-service is event-driven for sending notifications and exposes REST endpoints for notification history. It consumes events from Kafka (fine.created, fine.status.updated, payment.success, payment.failed, vehicle.document.expiry) to trigger outbound messages. No other service calls it directly to send a notification. REST endpoints (GET /notifications, GET/PUT /notifications/preferences) are called directly by authenticated clients via the API Gateway.
4. Stakeholders & Integrations¶
4.1 Organizations in Scope¶
| Organisation | Role | Integration Type | Status |
|---|---|---|---|
| ZRP | Issues traffic fines. Officers log via mobile app. | Manual entry — officer mobile app | Active |
| City Council (Harare) | Issues council fines and violations. | Manual entry — council officer portal | Active |
| ZINARA | Source of vehicle licence and registration data. | REST API integration (mocking for the meantime) | Planned |
| VTS (Vehicle Theft Squad) | Verifies VIN numbers and vehicle ownership. | REST API integration (mocking for the meantime) | Planned |
| City Parking | Issues parking tickets. | REST API integration (mocking for the meantime) | Planned |
| System Provider (ShiftFocus) | Builds and administers the platform. | Full super-admin access | Active |
4.2 Out of Scope — Phase 1¶
- ZIMRA — vehicle import duties and tax clearance
- Foreign vehicle registration
- Insurance provider API integration
5. User Roles¶
| Role | Internal Enum | Description |
|---|---|---|
| General Public / Citizen | CITIZEN | Self-registered. Views own vehicles, fines, renewal reminders. Pays online. |
| ZRP Officer | ZRP_OFFICER | Logs traffic fines from mobile. Searches vehicle history. Cannot see unrelated records. |
| City Council Officer | COUNCIL_OFFICER | Logs council fines. Queries outstanding fines on a vehicle. |
| City Parking Officer | PARKING_OFFICER | Manages parking tickets. Scoped to parking data only. |
| Authority Admin | AUTHORITY_ADMIN | Supervisor within one organisation. Read-only reports scoped to their org. |
| Super Admin | SUPER_ADMIN | Full system access. User management, cross-authority reporting, audit log access. |
6. Functional Requirements¶
6.1 Vehicle Management¶
- Officers can create vehicle records when issuing fines against vehicles not yet in the system
- Citizens link vehicles to their account by number plate
- Each vehicle has a dashboard: registration details, active fines, linked documents, renewal dates
- Vehicle data sourced from VTS and ZINARA via API (dummy data during develo pment)
- Vehicle documents (licence, insurance, ZBC, fitness) tracked with expiry dates
- Citizens can self-declare document expiry dates until ZINARA integration is live
6.2 Fines¶
- ZRP and Council officers issue fines against a vehicle from their mobile device
- Fine amount is copied from the category at issue time and frozen — never retroactively changed
- Each fine has a status: PENDING, PAID, CLEARED, DISPUTED, CANCELLED
- Default payment window: 30 days from issue date
- Admins can manually clear or cancel fines with a mandatory reason for audit purposes
- Citizens can dispute a pending fine once; an admin of the issuing organisation upholds (fine cleared) or rejects (fine payable again, with at least 7 days to pay) the dispute
- Officers can record cash payments on the spot, creating an audited paper trail
6.3 Parking Tickets¶
Status: Planned — pending City Parking API integration. parking_tickets owned by payments-service.
- Parking tickets sync from City Parking's system or are entered manually by parking officers
- Tickets appear on the relevant vehicle's record
- Tickets are payable through the same payments infrastructure as fines
6.4 Payments¶
- Supported methods: EcoCash , bank transfer (swipe) , cash recorded by officer
- A single payment can settle multiple fines and/or parking tickets in one transaction
- Payment is idempotent — duplicate attempts using the same idempotency key are safely rejected
- payment.success Kafka event automatically marks associated fines as PAID
- Full payment receipts generated and accessible to the vehicle owner
- Partial payments are out of scope for Phase 1
6.5 Notifications¶
- Citizens receive notifications when a new fine is issued against their vehicle
- Citizens receive reminders for upcoming vehicle document expiry (30 days before)
- Citizens receive confirmation when a payment succeeds
- Notification channels are configured in system-parameters; citizens choose their preferred channel per their notification_preferences record
- Notification history is queryable via REST. Sending is triggered exclusively by Kafka events.
6.6 Audit & Transparency¶
- Every write action logged with: actor, role, organisation, timestamp, IP, before/after state
- Audit logs are immutable — never updated or deleted
- Super admins access full audit log; authority admins see logs scoped to their organisation
- Includes: fine creation, payment recording, manual clearances, user account changes
7. Non-functional requirements¶
| Concern | Requirement |
|---|---|
| Security | JWT-based auth. RBAC enforced at gateway and within each service. Officer mobile sessions protected. |
| Rate Limiting | API gateway enforces per-user and per-role rate limits on all endpoints. |
| Idempotency | Payment endpoints are idempotent. Duplicate requests for the same fine never result in double charges. |
| Race Conditions | Concurrent payment attempts on the same fine handled via optimistic locking or distributed lock on the fine ID. |
| Scalability | Services are independently deployable and horizontally scalable. Initial scope is Harare — national expansion must not require re-architecture. |
| Auditability | Every state-changing action emits an audit event consumed by audit-service. Logs stored as JSONB snapshots. |
| Availability | Fine lookup and payment processing target high availability. Degraded mode acceptable for notifications. |
| Offline Support | Officer mobile app supports offline fine logging with queue-and-sync when connectivity is restored. |
8. Client Interfaces¶
| Interface | Users | Notes |
|---|---|---|
| Citizen Mobile App | General Public | Android and iOS. Vehicle dashboard, fines, payments, renewal reminders. Mobile-first |
| Officer Mobile App | ZRP and Council officers | Android. Offline-capable fine logging. Secure login. Syncs on connectivity restore. |
| Web Portal | Authority admins | Query vehicle records, view scoped reports, manage manual clearances. |
| Admin Portal | SUPER_ADMIN | Full system oversight, user management, audit log access, cross-authority reporting. |
USSD and WhatsApp interfaces are planned for a future phase to increase accessibility for users without smartphones.
9. Infrastructure¶
All infrastructure (PostgreSQL, Redis, Kafka) runs in Docker containers. Services themselves can run as plain Java processes connecting to containerised infrastructure, or all 9 services can be containerised and orchestrated via Docker Compose.
| Component | Docker Image |
|---|---|
| PostgreSQL | postgres:16-alpine — one container, multiple databases or schemas per service |
| Redis | redis:7-alpine |
| Kafka | apache/kafka:3.7.0 — single-broker KRaft mode (no ZooKeeper) |
A docker-compose.yml in the project root should define these three infrastructure services. Java services connect to them via config-server properties (spring.datasource.url, spring.kafka.bootstrap-servers, etc.).
9.2 Production (Future)¶
- Containerisation: Docker — each service packaged as an image
- Orchestration: Kubernetes — manages deployment, scaling, and self-healing
- CI/CD: GitHub Actions — automated testing, image build, and K8s deployment on push
- Message Broker: Kafka cluster — multiple brokers, topics replicated (replication factor ≥ 3) for HA
- Database: PostgreSQL per service (separate instances in production)
- API Documentation: Swagger / OpenAPI auto-generated from Spring Boot annotations
10. Open Questions & Risk¶
| Item | Notes |
|---|---|
| Data Ownership | Vehicle and offence data legally belongs to ZRP and ZINARA. Platform acts as operational source of truth pending formal data governance agreements. |
| ZINARA / VTS API Access | Integration feasibility and data access agreements not yet confirmed. Dummy data used during development. |
| City Parking Sync | City Parking system exists but API availability is unconfirmed. |
| Officer Adoption | UX and offline capability are critical. If the mobile app is cumbersome, officers will not use it. |
| Cash Payment Integrity | Cash payments recorded by officers carry corruption risk. Audit trail mitigates this. Supervisory approval step may be needed. |
| Regulatory Approval | A government body must formally oversee the system before official deployment. Security requirements may change significantly. |
| Concurrent Users | Expected load not yet estimated. Architecture is designed to scale; load testing benchmarks to be defined. |
Future Backlog¶
- ZIMRA integration — vehicle import duties and tax clearance
- Foreign vehicle handling — separate category and workflow
- Partial and instalment payments
- USSD interface — for users without smartphones
- WhatsApp notifications — additional notification channel
- National expansion — beyond Harare
- Insurance provider API integration
- Full regulatory compliance framework and data governance agreements
- Offline sync conflict resolution strategy for officer mobile app
- Database backup and point-in-time recovery
- Redis high availability — current single instance is a SPOF for the token blocklist
- Rate limiting on public auth endpoints