Skip to main content

Architecture Document — CTA Public Transport Optimisation System

· 15 min read

Version: 1.0 Date: 2026-03-12 Status: Baselined Standard: 4+1 Architectural View Model (Kruchten, 1995) Notation: ArchiMate 3.1 concepts rendered as Mermaid diagrams


Table of Contents

  1. Document Purpose and Scope
  2. Architectural Drivers
  3. Use Case View (+1)
  4. Logical View
  5. Process View
  6. Development View
  7. Physical View
  8. Architectural Decisions Summary
  9. Risks and Technical Debt

1. Document Purpose and Scope

1.1 Purpose

This document describes the software architecture of the Chicago Transit Authority (CTA) Public Transport Optimisation System. It is structured according to the 4+1 Architectural View Model (Kruchten, IEEE Software 1995), which organises the architecture into five complementary views, each addressing the concerns of a different stakeholder group:

ViewPrimary AudienceCentral Concern
Use Case (+1)All stakeholdersScenarios that drive architectural decisions
LogicalArchitects, developersFunctional decomposition and key abstractions
ProcessArchitects, integratorsConcurrency, data flows, runtime behaviour
DevelopmentDevelopers, build engineersModule structure, package organisation
PhysicalOperations, DevOpsDeployment topology, infrastructure mapping

Diagrams use Mermaid syntax and follow ArchiMate 3.1 layering conventions:

  • Technology Layer — infrastructure elements (brokers, databases, containers)
  • Application Layer — software components and their interfaces
  • Business Layer — business processes and actors that the system serves

1.2 System Overview

The system is a real-time streaming pipeline that ingests simulated operational data from the CTA elevated rail network ("L"), processes it through multiple transformation stages, and presents a live transit status dashboard. It demonstrates a full Event-Driven Architecture (EDA) on the Confluent Kafka platform.

1.3 Scope

  • Three train lines: Blue, Red, Green (each with 10 trains, bidirectional)
  • Station arrival events, turnstile ridership counts, and weather telemetry
  • Static station reference data from PostgreSQL
  • A browser-accessible real-time status dashboard

2. Architectural Drivers

2.1 Quality Attribute Requirements

IDQuality AttributeScenarioArchitectural Response
QA-01Throughput3 lines × stations × 10 trains produce arrival events every 5 s10-partition Kafka topic; AvroProducer batching
QA-02DecouplingNew consumers must not require producer changesAll communication via Kafka topics (no direct calls)
QA-03Schema EvolutionFields may be added to events over timeAvro + Schema Registry with compatibility enforcement
QA-04ReplayabilityDashboard must recover state on restartConsumers start from offset_earliest; Faust rebuilds table from log
QA-05ResponsivenessDashboard must serve HTTP requests without stalling Kafka pollingTornado async IO loop; consumers as coroutines
QA-06ExtensibilityStation reference data changes without code deploymentKafka Connect JDBC connector; consumers subscribe to topic

2.2 Constraints

  • Python-only application code (no JVM services authored in-house)
  • Single-host Docker Compose deployment (development / demonstration environment)
  • Confluent Platform 5.2.2 (fixed version)

3. Use Case View (+1)

The Use Case View captures the key scenarios that motivated and validate the architectural decisions. In the 4+1 model this view acts as the glue — each scenario exercises a slice through every other view.

3.1 Actor Diagram

3.2 Key Scenarios

UC-01 — View Live Transit Status

Trigger: Transit operator opens http://localhost:8888 Flow: Tornado serves status.html populated from in-memory Lines and Weather state that is continuously updated by four Kafka consumers running as async coroutines. Architectural relevance: Drives the Tornado async server choice (ADR-006) and the requirement for in-process Kafka consumer coroutines.

UC-02 — Publish Train Arrival Event

Trigger: Simulation time step advances; a train moves to the next station. Flow: Station.run()AvroProducer.produce() → Schema Registry validates Avro → Kafka topic org.chicago.cta.station.arrivals.t001KafkaConsumer in server → Lines.process_message() → UI state updated. Architectural relevance: Establishes the end-to-end Kafka + Avro pipeline (ADR-001, ADR-002).

UC-04 — Publish Weather Reading

Trigger: Simulation hour boundary. Flow: Weather.run() → HTTP POST to Kafka REST Proxy → Kafka topic org.chicago.cta.weather.v1KafkaConsumer in server → Weather.process_message(). Architectural relevance: Demonstrates the REST Proxy integration path (ADR-005).

UC-06 — Aggregate Rider Counts

Trigger: Continuous turnstile events on com.cta.stations.turnstile.entry. Flow: KSQL turnstile table materialises from topic → KSQL TURNSTILE_SUMMARY GROUP BY aggregation → new Kafka topic → KafkaConsumer (is_avro=False) in server → UI ridership count. Architectural relevance: Drives the KSQL aggregation decision (ADR-004).

UC-07 — Transform Station Schema

Trigger: Kafka Connect pushes a raw station row to com.cta.stations.data.rawt001.stations. Flow: Faust transform_stations agent reads record → resolves red/blue/green booleans to line string → writes TransformedStation to org.chicago.cta.stations.table.v1t001 and updates Faust in-memory table. Architectural relevance: Drives the Faust stream processor choice (ADR-004).


4. Logical View

The Logical View describes the system's functional decomposition into key abstractions, their responsibilities, and their relationships. This view follows ArchiMate's Application Layer notation.

4.1 Component Overview

4.2 Key Abstractions

Producer Hierarchy

Consumer / Model Hierarchy

4.3 Kafka Topic Catalogue

TopicProducerConsumer(s)FormatPartitions
org.chicago.cta.station.arrivals.t001Station (AvroProducer)Tornado serverAvro10
com.cta.stations.turnstile.entryTurnstile (AvroProducer)KSQLAvro10
org.chicago.cta.weather.v1Weather (REST Proxy)Tornado serverAvro10
com.cta.stations.data.rawt001.stationsKafka Connect JDBCFaustJSON (Connect)1
org.chicago.cta.stations.table.v1t001FaustTornado serverJSON1
TURNSTILE_SUMMARYKSQLTornado serverJSON

5. Process View

The Process View describes the system's dynamic behaviour — how processes start, how data flows between them at runtime, and how concurrency is managed.

5.1 System Startup Sequence

The diagram below shows the mandatory startup order. Components further right depend on components to their left being fully initialised.

5.2 End-to-End Data Flow — Train Arrival

5.3 End-to-End Data Flow — Turnstile Aggregation

5.4 Concurrency Model

The entire consumer application runs in a single OS thread using cooperative multitasking. Kafka polling is non-blocking (0.1 s timeout). The HTTP handler is synchronous but executes between coroutine yield points, keeping UI latency low.


6. Development View

The Development View describes the organisation of the software in the development environment — module structure, package dependencies, and build artefacts.

6.1 Module Structure

6.2 Package Dependencies

6.3 Entry Points and Startup Commands

ProcessEntry PointCommand
Data producer + simulationproducers/simulation.pypython simulation.py
Station stream transformerconsumers/faust_stream.pyfaust -A faust_stream worker -l info
Turnstile KSQL setupconsumers/ksql.pypython ksql.py
Dashboard web serverconsumers/server.pypython server.py

Note: Processes 2, 3, and 4 have an implicit startup ordering dependency. The Kafka Connect JDBC connector (configured by the simulation) must produce station data before the Faust app can transform it; the KSQL tables must exist before the dashboard starts. There is no orchestration script enforcing this order.


7. Physical View

The Physical View maps software components onto physical (or virtualised) infrastructure. This view follows ArchiMate's Technology Layer.

7.1 Container Deployment Diagram

7.2 Network Port Map

PortServiceProtocolConsumer(s)
2181ZookeeperTCPKafka broker (internal)
9092Kafka brokerPLAINTEXTPython producers, Python consumers, Faust
8081Schema RegistryHTTPAvroProducer, AvroConsumer, Kafka Connect
8082Kafka REST ProxyHTTPWeather producer
8083Kafka Connect REST APIHTTPconnector.py setup
8084Connect UIHTTPOperator browser
8085Topics UIHTTPOperator browser
8086Schema Registry UIHTTPOperator browser
8088KSQL ServerHTTPksql.py setup
5432PostgreSQLTCPKafka Connect JDBC
8888Tornado DashboardHTTPTransit Operator browser

7.3 Data Persistence Boundary

All in-process state is rebuilt from Kafka on restart. Durable state exists only in PostgreSQL (station reference data) and the Kafka topic logs.


8. Architectural Decisions Summary

Cross-reference to the detailed ADR documents in docs/adr/.

IDDecisionRationaleADR
AD-01Apache Kafka as the central event busDecoupling, replayability, fan-outADR-001
AD-02Avro + Schema Registry for all first-party topicsSchema evolution, contract enforcementADR-002
AD-03Kafka Connect JDBC Source for PostgreSQLZero custom ingestion code; handles offset/retryADR-003
AD-04Faust for station transformationPython-native; record-level transformADR-004
AD-05KSQL for turnstile aggregationDeclarative SQL GROUP BY; no Python state managementADR-004
AD-06Kafka REST Proxy for weatherDemonstrates HTTP-based produce pathADR-005
AD-07Tornado async web serverSingle-thread concurrency for Kafka + HTTPADR-006

9. Risks and Technical Debt

9.1 Risks

IDRiskSeverityAffected ViewMitigation
R-01Single Kafka broker — SPOFHighPhysicalAdd 2 additional brokers; set replication_factor=3
R-02Replication factor 1 on all topicsHighPhysicalIncrease to 3 in production
R-03Hard-coded localhost addresses in both constants.py filesMediumDevelopmentExternalise via environment variables or a config file
R-04Hard-coded DB credentials in connector.pyHighPhysicalUse Kafka Connect secrets management or environment injection
R-05Manual startup ordering with no orchestrationMediumProcessAdd a readiness-check script or use depends_on with health checks
R-06AvroProducer is a deprecated Confluent APIMediumDevelopmentMigrate to SerializingProducer + AvroSerializer

9.2 Technical Debt

IDDescriptionLocationEffort
TD-01TURNSTILE_SUMMARY uses JSON while all other topics use Avro — inconsistency in serialisation conventionconsumers/ksql.py, consumers/server.py:87Low
TD-02Faust Table uses store="memory://" — state lost on restart, rebuild time increases with topic sizeconsumers/faust_stream.py:38Medium
TD-03Both producers/constants.py and consumers/constants.py duplicate identical constant valuesBoth filesLow
TD-04No unit or integration tests present in the repositoryEntire codebaseHigh
TD-05Weather schema JSON loaded on every Weather.__init__ call via file I/O (class variables mitigate partially)producers/models/weather.py:49-55Low
TD-06connector.py exits the process on connector creation failure, preventing graceful recoveryproducers/connector.py:51-53Low

Document generated by reverse-engineering the source code on 2026-03-12. All diagrams use Mermaid and render natively on GitHub.