Four projects I worked on at Roution, a diet management app, spanning the app, web, admin, and infrastructure.
Auth architecture: external authentication only at sign-inSocial and Kurly sign-in go through external authentication only once. After that, every request is authenticated with a single JWT issued by the Roution server. Pre-migration app versions keep working through the existing path, where the gateway checks the Kurly session. Hover over an element to see details.
Roution JWT pathPre-migration app pathSign-in and server-side external calls
Token management in the app: refresh happens in one placeApp screens, the WebView, and the Android pedometer background process share the same sign-in token. If each refreshed on its own, tokens could fall out of sync and log the user out, so RoutionAPI handles every refresh in one place, one at a time. The pedometer process leaves refreshing to the app and refreshes directly only if there is no response within 15 seconds.
Infrastructure: uploading files is separate from serving themGitHub Actions only adds signed bundles to S3. The admin decides which device gets which bundle through pointers in KeyValueStore. When the app checks for an update, a CloudFront edge function reads these pointers to pick the file to serve. Production and staging each get an identical copy of this setup. Hover over an element to see its configuration.
Publish: GitHub Actions adds filesControl: the admin changes pointersRead: the app downloads
Deployment flow: how a release reaches usersFor each release, the pipeline checks whether a store build with the same native code already exists. If not, it makes a store build. If so, it confirms the native code’s fingerprint matches that build, then publishes an OTA candidate. Publishing alone doesn’t reach users; the admin widens the rollout, starting with designated devices.Gradual rollout: widening a new bundle device by deviceIn the admin, a rollout starts with designated devices, raises the percentage, and ends with a full rollout. Only devices whose fixed per-device value falls within the percentage get the new bundle, so raising the percentage never drops devices that already have it. If a problem shows up, it can be rolled back at any stage. Drag the slider or press an auto-advance preset.
New bundle (candidate)Current bundle (stable)Designated devices (internal testers)
Devices with the new bundle
0/ 100 devices
Auto-advance (admin presets)
A new release reshuffles which devices are included
Migration: proven on the beta app first, now moving the production appTo keep the app people use running without interruption, I validated each step in turn on the internal beta app and in staging.
0Unify fingerprints across build environmentsDone
1Pilot on the beta appDone
2-1Validate gradual rollout in stagingDone
2-2Move the beta app to the new infrastructureDone
3Switch the production appIn progress
4Retire the paid service (EAS)Planned
The full pipeline: a human checks in at only 2 of 30 stepsFilled pills are AI sessions, outlined pills are script checks, and diamonds are human checkpoints. Hover over a step to see what it does.
19 AI sessions9 script gates2 human checksRe-review in a fresh session until nothing is left to fix, up to 15 times
App architecture: requests flow down the layers, navigation goes through eventsThe View only renders. The ViewModel owns state and actions, and UseCases and Repositories handle data requests. The ViewModel never navigates directly; it publishes events, and the View subscribes to them to navigate.
Three projects I worked on in the iOS app of Yogiyo, a food delivery service.
Screen composition: one screen, split into componentsThe server sends data for the components that make up a screen, and the app renders each component from it. Each blue box is one component.Performance: redraw only the cells that changedThe whole screen used to redraw on even a small data change; I changed it so only the changed cells redraw. I solved the animation issues this introduced with a custom DataSource, and component data now loads in parallel.Architecture: one-way flow, features as deep linksTangled Delegates for talking to views made the code hard to follow, and server-rendered screens couldn’t hold logic. I separated the layers so everything flows one way, and predefined features as deep links for the server to call.Dynamic scope: settled by agreement, not technologyHow much the server should be able to change couldn’t be decided by technology alone.
Module structure: UI, Domain, and Data modules for each featureThis is the structure from the blog post. Each feature, such as Home, Search, and Restaurant, has its own UI, Domain, and Data modules.Results: faster builds and launches, and no more conflicts
88%reduction in CI/CD build time
50%reduction in cold launch time
Nonecode conflicts caused by project files
Transmission: send in batches, and send leftover logs on the next launchHolding events briefly to batch them means logs are lost if the app quits in the meantime. So the pending events are saved to storage as well as memory.