Hangangjari System Overview
The full Hangangjari data flow, from external collection through the API to the iOS app and widgets
Hangangjari is an iOS app for quickly checking information needed before and during a visit to a Hangang park. Its park lists, parking status, widgets, and notifications help users answer four questions: “Is it okay to go now?” “Can I bring the car?” “Would another park be better?” and “What can I find nearby?”
It is hard to calculate those answers from scratch on every app screen. Parking status changes often, events and notices refresh on different schedules, and forecasts need to be calculated in advance. Widgets must be readable even when the app is not open.
Workers prepare external data, the API creates responses the app and widgets can read immediately, and clients handle presentation and interaction.
The Whole System in One Sentence
Hangangjari turns slow, empty, or late-changing public and official data into screen-ready values accompanied by their state.
Collection, normalization, response generation, and display use separate execution paths. External sources can fail and update on different schedules, while the app and widgets need to respond quickly at the moment the user sees them.
The user-facing API keeps the read work needed to build screens. Workers and CronJobs handle work that takes time or can fail, such as collection, forecasting, notifications, and translation.
App and Widget as the Read Surface
The app and widgets are the first surfaces users see. Users check the situation there and move into a map app when needed. A widget runs separately from the app and reads display values that the app has saved.
flowchart LR
User["User<br/>Checks before and during a visit"] --> Widget["Widget<br/>Short glance"]
User --> App["App<br/>Compare · Detail · Settings"]
Widget --> App
App --> Maps["Map app<br/>Start moving"]
subgraph Device["iOS device"]
App
Widget
Local["In-app cache<br/>Recent screen values"]
Saved["Stored display values<br/>Shared by app and widget"]
end
App <--> Local
App --> Saved
Widget --> Saved
App --> API["Screen-ready API response"]
Widget -.-> API
The server prepares responses the app can display immediately, so drawing a screen does not require another check of every external source. The client handles display and interaction.
The widget moves differently from the app process. It reads stored display values first and asks the API for fresh values only when needed.
External Data Prepared in Advance
If external sources are called one after another after a user request arrives, the app inherits their speed and failures directly. In Hangangjari, workers collect and normalize data first. Postgres keeps reference data and history, while Redis holds current state and summaries that need fast reads.
flowchart LR
Sources["Public/official sources<br/>Parking · Events · Notices · Facilities"] --> Workers["Workers / CronJob<br/>Collect · Normalize · Validate"]
Workers --> Records[("Postgres<br/>Reference data · History")]
Workers --> Fast[("Redis<br/>Fast lookup values")]
Records --> Prepared["Forecasts · Notification candidates<br/>Precomputed"]
Prepared --> Records
Prepared --> Fast
Records --> API["Screen-ready API response"]
Fast --> API
API --> Client["App · Widget"]
This separation prevents “no value” from collapsing into one failure. An external source failure, a stopped worker, an empty fast lookup, and missing reference data in Postgres require different operational checks even when they produce a similar screen state.
The Last Confirmed Value During an Incident
During an incident or delay, fallback distinguishes the last successful value from unavailable information. The last successful value carries its observation time and delayed state.
flowchart TD
Open["Open app or widget"] --> Saved{"Stored display value<br/>exists?"}
Saved -->|Yes| ShowSaved["Show last checked value<br/>with updated time"]
Saved -->|No| Request["Request a new response"]
Request --> Result{"Can a response<br/>be produced?"}
Result -->|Yes| Fresh["Show fresh value<br/>with refresh state"]
Result -->|Last successful value| Last["Show last successful value<br/>with delay state"]
Result -->|No| Empty["Show unavailable state<br/>without overclaiming"]
In a parking app, an honest response matters as much as a fast response. Showing a 40-minute-old value as current may make the screen fast, but it can move the user in the wrong direction.
home-summary, forecasts, and widget snapshots carry refresh state and source state along with each value.
Roles That Expanded Beyond Parking
The product began with a small problem: checking remaining spaces in Hangang park parking lots on the web every time was inconvenient.
Using the app revealed that a parking check is followed by questions about park congestion, events, facilities, notices, weather, and public transit. Favorites, widgets, and notifications connect that information to short checks before and during a visit.
The roles therefore split in the order the product needed them.
- The app creates the screen where the user makes a decision now.
- The widget shows values that can be checked briefly before opening the app.
- The API returns responses shaped for each screen.
- Workers handle slow sources, failures, and different update schedules outside the user request time.
- Postgres preserves reference data, history, and audit records.
- Redis handles caches and supporting indexes that can be rebuilt if lost.
Redis stores query results that can be rebuilt from Postgres. Workers read external sources ahead of time so user requests do not wait on collection delays.
One Response for the First Screen
The current app and general park widget use home-summary for the first screen. It bundles parking, outing information, forecasts, and source-level refresh states into a shape that both clients can read directly.
home-summary reduces call count while giving the app and widget the same first-screen structure.
If the client calls parking, outing, and forecast APIs separately and combines them locally, each screen can interpret values differently. When the server sends a combined response, the app can focus more on display and interaction.
The response format coordinates cases where parking, events, and forecasts carry different refresh states.
Workers record external data and source state, and the API assembles screen-ready responses. The app and widgets can then display each value with its freshness without interpreting external response formats.
Comments
No comments yet. Be the first to leave one.
Pending review