Duplicated Configuration Data Solution

The Problem: Duplicated Configuration Data

I ran into a fundamental architectural problem while building BallBuilder: configuration data was duplicated between the frontend (Swift/iOS) and the backend (AWS).

Consider a simple example: an upgrade costs 100 gems. For that transaction to be secure, the backend must validate the cost—it cannot trust whatever value the client sends. This means the cost has to exist in both places: hardcoded in Swift for the UI, and stored on the backend for validation.

This creates an error-prone system. Every time you change a value, you must update it in two locations. Miss one, and the game breaks.

Observing the Problem in Practice

I noticed this pattern while using Claude to write code. Claude would make a change to a configuration value in the Swift codebase, then we'd update the mocked API call, which also required the same change. Watching this happen repeatedly made it clear: this approach is unsustainable. Two sources of truth for the same data guarantees eventual inconsistency.

The Solution: Single Source of Truth on the Backend

The fix is straightforward in concept: return all configuration data from the backend. The client becomes a consumer of configuration, not a co-owner of it.

This requires a significant refactor. Every hardcoded value in Swift—upgrade costs, energy costs, level rewards, game settings—needs to be replaced with data fetched from the backend.

Implementation: The Profile API Call

Here's the flow:

  1. User launches the app and sees a splash screen
  2. The app authenticates with Apple
  3. We exchange the Apple authentication for a Cognito JWT token
  4. That token is used for the initial API call, which fetches the user profile and level statistics

The profile table is lean—just user data. The bulk of the data (level progress) comes from a separate call. Neither response is paginated. This makes the profile call the ideal location to include game configuration data.

The critical requirement: the user cannot leave the splash screen until this API call completes. This configuration data contains everything—component costs, energy costs, rewards, summon probabilities. If somehow the app proceeded without it, the game would be broken.

Storage: Git-Managed JSON vs. S3

I considered two approaches for storing the configuration:

Option 1: JSON file in the code repository

  • Configuration changes go through the normal deployment pipeline: development → staging → production
  • Integration tests can validate the JSON structure before deployment
  • Git provides commit history for auditing and rollback
  • Changes require a deployment—you cannot modify production configuration on the fly

Option 2: S3 with native versioning

  • Built-in version control
  • Can update configuration in production without a deployment
  • That last point is the problem

The ability to hot-swap configuration in production is a double-edged sword. I could write a script that updates the config file directly in production. While that sounds convenient for emergencies, it bypasses all the controls that prevent mistakes from reaching users.

I chose the Git-managed approach. Changes flow through development and staging, integration tests validate the configuration structure, and I cannot accidentally break production with a hasty script. Git's commit history provides the same rollback capability as S3 versioning, just through a different mechanism.

Validation Strategy

The Swift client will perform strict validation on the configuration data returned from the API. If a required key is missing or malformed, the app will throw an error. This forces configuration problems to surface in development or staging, not production.

Next Steps

I've documented the migration plan in a 1,500-line planning file. The work involves:

  1. Adding the configuration payload to the Profile API response
  2. Creating a configuration manager in Swift that initializes values on login
  3. Systematically replacing every hardcoded value with references to the configuration manager
  4. Adding validation to catch missing or invalid configuration before it causes runtime failures

The result will be a system where configuration lives in exactly one place, changes are deployed through a controlled pipeline, and the frontend simply consumes whatever the backend provides.

Comments

Popular posts from this blog

Solving Aspect Ratio Problems For Good!!

Designing a Scalable Community Content System

Customizing the playing experience with special ball effects