Design patterns
1. Layered architecture
Section titled “1. Layered architecture”The API follows a layered architecture that separates HTTP concerns, business logic, domain contracts, and infrastructure details.
Why it is used
- Keeps controllers thin.
- Makes core logic easier to test and reason about.
- Prevents persistence and vendor details from leaking everywhere.
2. Repository pattern
Section titled “2. Repository pattern”Repository interfaces live in Domain/Interfaces and EF-backed implementations live in Infrastructure/Repositories/Ef.
Why it is used
- Application services depend on abstractions rather than EF types.
- Storage concerns stay replaceable and localized.
- User-scoping and blob-path logic stay centralized.
3. Orchestrator pattern for AI analysis
Section titled “3. Orchestrator pattern for AI analysis”ShotAnalysisPipeline acts as an orchestrator for multiple smaller steps: loading swing averages, analyzing the lie, loading personalization, and requesting stance guidance.
Why it is used
- The AI workflow is naturally multi-stage.
- Fallback behavior can be inserted cleanly.
- Future agent expansion can happen without rewriting controller code.
4. Transport-client mirroring across platforms
Section titled “4. Transport-client mirroring across platforms”The web, iOS, and Android apps each implement a thin shared-client concept:
- Axios client in the web app
URLSessionwrapper in iOS- OkHttp wrapper in Android
Why it is used
- Each platform gets native ergonomics.
- The mental model stays consistent across teams.
- Contract bugs are easier to compare and debug.
5. Fallback-first resiliency
Section titled “5. Fallback-first resiliency”When AI agent calls fail, the platform still returns a recommendation using built-in logic. This avoids blocking the golfer’s round on an integration outage.
6. Configuration-driven hosting
Section titled “6. Configuration-driven hosting”Azure endpoints, auth settings, and storage settings are configured through environment-aware configuration instead of being hard-coded into business logic.
Example: controller to service delegation
Section titled “Example: controller to service delegation”[HttpGet]public async Task<IActionResult> GetAll([FromQuery] string? user){ var userId = ResolveUserId(user); var photos = userId is null ? await _photoService.GetAllPhotosAsync() : await _photoService.GetPhotosForUserAsync(userId); return Ok(photos.Select(MapToResponse));}This is the preferred pattern for new API endpoints: keep the controller responsible for HTTP concerns and keep use-case logic in services.