Implementing Mock API Layer for Backend Integration
Problem Statement
During initial development of the iOS application, I implemented data management using hard-coded values directly within Swift files. While this approach enabled rapid prototyping and validation of core functionality, it created technical debt that would complicate the transition to production-grade cloud storage. The application needed to evolve from in-memory state management to persistent cloud storage using AWS DynamoDB, requiring a systematic approach to prevent disruption during the migration.
The challenge involved two interdependent concerns:
- Finalizing a comprehensive data model that would scale to production requirements
- Implementing an abstraction layer that would enable seamless transition from mock to production APIs without requiring extensive client refactoring
Data Model Architecture
Strategic Decision: Manual Schema Design
I made the deliberate decision to author the complete data model manually rather than delegating this work to code generation tools or LLMs. This choice reflected the criticality of the schema design—errors in the data model would compound over time and create expensive migration scenarios in production. While I leveraged Claude for schema validation and optimization suggestions, I maintained authorship to ensure deep understanding of access patterns, consistency requirements, and scaling characteristics.
In retrospect, this work should have preceded major development efforts. However, the exploratory development process with rapid prototyping had clarified requirements sufficiently that delayed formalization remained viable, though not optimal.
DynamoDB Table Design
The finalized architecture consists of four primary tables, each optimized for specific access patterns:
1. Profile Table
The authoritative source for user state, including gems, energy, unlocked content, and progression metrics. This table implements a dual-key pattern:
- Primary Pattern:
USER_ID#{user_id}/PROFILEcontaining complete user state - Secondary Pattern:
DISPLAY_NAME#{display_name}/DISPLAYenforcing display name uniqueness
Display name updates require transactional writes across both patterns to maintain consistency and prevent race conditions during concurrent updates.
2. UserLevelStats Table
Individual level completion records stored as separate items to prevent exceeding DynamoDB's 400KB item size limit. The design acknowledges that users may eventually complete hundreds or thousands of levels, making embedded storage within the profile table untenable. Each item represents a single level completion with associated statistics (stars, coins, completion time).
3. Community Levels Table
The most complex schema, supporting multiple query patterns through two Global Secondary Indexes:
- GSI 1: Query by status and popularity score
- GSI 2: Query by level name prefix
- Primary Table: Query levels by creator user ID
Includes a proprietary scoring algorithm for content ranking based on engagement metrics (covered in a separate post).
4. Mailbox Table
Temporary reward storage implementing DynamoDB's TTL functionality. Items automatically expire seven days after creation, providing a self-managing reward delivery system without requiring scheduled cleanup operations.
Mock API Implementation Strategy
Architectural Approach
With the data model finalized, I implemented a mock API layer that would mirror production API behavior while maintaining data in local memory. This approach provided several strategic advantages:
- Enabled development and testing of data persistence logic before backend implementation
- Established clear contracts between client and server
- Facilitated incremental migration to production APIs
- Reduced coupling between frontend and backend development timelines
Data Transformation Layer
A critical design decision involved separating backend data representation from client data models. Rather than having the backend conform to client expectations, I implemented transformation logic within the iOS application to convert between API response formats and internal data structures.
This separation of concerns provides flexibility for backend optimization without requiring client updates, though it places the transformation responsibility on the mobile application. The trade-off favors backend simplicity and enables independent evolution of both layers.
Implementation Process
Using the Markdown-formatted data model as specification, I worked with Claude to generate mock data structures and API methods. The process followed these stages:
- Mock Data Generation: Created realistic test data conforming to schema specifications
- API Method Stubs: Implemented mock GET, PUT, POST, and DELETE operations
- Transformation Functions: Built conversion utilities between API format and client models
- Integration Testing: Validated data flow through application workflows
Data Persistence Challenges
Initial Implementation Gap
The initial mock API implementation exposed a critical oversight. The previous hard-coded approach maintained state entirely in memory with direct mutations. When I introduced the API abstraction layer, read operations functioned correctly, but state changes were not propagating back to the mock storage layer.
This manifested as a silent data loss scenario: users could complete levels, collect rewards, and modify settings, but none of these changes persisted. The UI would reflect updates during the session, but any navigation away from the current view would revert to the original state.
Resolution: Complete Request-Response Cycle
I resolved this by implementing the full API lifecycle for state-modifying operations:
- Client Action: User performs an operation (completes level, spends currency, updates settings)
- API Call: Client invokes appropriate mock API method (PUT, POST, DELETE)
- Mock Storage Update: API method modifies in-memory data structures
- Success Response: API returns updated state to client
- UI Update: Client updates interface based on response data
This approach required adding mock implementations for all state-modifying operations across three primary tables:
- Profile Updates: Energy consumption, currency transactions, upgrade purchases
- UserLevelStats Updates: Level completion records, high score tracking
- Community Levels Updates: Level creation, publication, likes, statistics
Deferred Implementation: Mailbox System
The mailbox table remains unimplemented in the mock layer because the iOS application does not yet include mailbox functionality. Following the principle of avoiding premature implementation, I deferred this work until the client features requiring it are developed.
Production Migration Path
Authentication Integration
The production transition will leverage AWS Cognito for authentication, integrated with Apple's Sign in with Apple service. This provides:
- Secure authentication without managing credentials
- JWT token-based API authorization
- Automatic user identity federation
Incremental API Replacement
The mock API abstraction enables methodical migration. Each API endpoint can be transitioned independently:
- Implement production endpoint in AWS Lambda
- Update API client to route to production endpoint
- Validate behavior through manual testing
- Verify data persistence in DynamoDB console
- Monitor for errors or inconsistencies
- Proceed to next endpoint
This approach minimizes risk by isolating each integration point and enabling rapid rollback if issues arise.
Validation Strategy
The production migration will benefit from direct DynamoDB inspection. For each API integration, I can:
- Execute operations through the iOS application
- Query DynamoDB tables directly to verify data accuracy
- Confirm attribute values match expected state
- Validate that transforms between client and server formats preserve data integrity
Lessons Learned
Schema Design Ownership
Manual authorship of the data model, while time-intensive, provided deep understanding of access patterns and consistency requirements. This knowledge proved essential when debugging the data persistence issues, as I could quickly identify where state should flow through the system.
Complete API Lifecycle Simulation
Implementing only GET operations initially created a false sense of completeness. A proper mock API layer must include all state-modifying operations to accurately represent production behavior and expose integration issues during development.
Delayed Schema Formalization Trade-offs
While ideally the data model should precede implementation, the exploratory development approach with rapid prototyping clarified requirements that might have been unclear in pure design phases. The key is recognizing when to transition from exploration to formalization—in this case, when planning the cloud storage integration.
Transformation Layer Placement
Placing data transformation responsibility on the client increases mobile app complexity but provides backend flexibility. This trade-off should be evaluated based on team structure, performance requirements, and anticipated schema evolution patterns.
Next Steps
With the mock API layer complete and functioning correctly, the path forward involves:
- Completing frontend feature development using mock APIs
- Implementing AWS Lambda functions for each API endpoint
- Integrating Cognito authentication
- Systematically replacing mock endpoints with production endpoints
- Validating data persistence and transformation accuracy
- Monitoring production behavior and performance characteristics
The mock API layer transforms what could have been a high-risk "big bang" migration into a controlled, incremental integration process.
Comments
Post a Comment