API architecture
Layered backend structure
Section titled “Layered backend structure”Controllers HTTP routes, validation, response mappingApplication/Services Business orchestration and use-case logicApplication/DTOs Request and response contractsDomain Entities and repository interfacesInfrastructure EF Core, blob storage, auth, Foundry, OpenGolf, messagingImportant services
Section titled “Important services”| Service | Responsibility |
|---|---|
PhotoService |
Stores uploaded images, metadata, and analysis state transitions. |
ShotAnalysisPipeline |
Orchestrates lie analysis, personalization loading, stance recommendation, and fallback behavior. |
ShotRecommendationService |
Produces built-in recommendation output when the AI path is unavailable. |
SwingDataService |
Imports CSV swing data and calculates usable averages. |
GolfCourseService / CourseSyncService |
Searches, imports, and refreshes course data from OpenGolf. |
SpeechSynthesisService |
Produces spoken caddie output for clients. |
Controller example
Section titled “Controller example”PhotosController keeps transport concerns in the controller and delegates real work to services:
var photo = await _photoService.CreatePhotoAsync( user: userId, fileName: file.FileName, content: imageBuffer, contentType: file.ContentType, fileSizeBytes: file.Length, description: request.Description ?? string.Empty, imageUrlFactory: id => $"{Request.Scheme}://{Request.Host}/api/photos/{id}/image", location: location, distanceToHoleYards: distanceToHoleYards, ct: ct);
var analysis = await _analysisPipeline.AnalyzeAsync(photo, imageBuffer, file.ContentType, wind, ct);photo = await _photoService.UpdateAnalysisAsync(photo.Id, analysis) ?? photo;This pattern keeps the HTTP surface thin while preserving a clear orchestration story in the service layer.
Pipeline example
Section titled “Pipeline example”ShotAnalysisPipeline is a focused orchestrator:
var swingAverages = await _swings.GetAveragesAsync(photo.UserId, ct);var lie = await AnalyzeLieWithFallbackAsync(photo, imageContent, imageContentType, ct);var personalization = await LoadPersonalizationAsync(photo.UserId, ct);var stance = await RecommendStanceWithFallbackAsync(photo, lie, swingAverages, personalization, wind, ct);That sequence is important because it shows the backend composes multiple smaller responsibilities instead of hiding everything in a controller or a single giant service method.
Persistence model
Section titled “Persistence model”- EF Core repositories are the system of record for structured data.
- Repositories such as
EfPhotoRepository,EfProfileRepository, andEfGolfCourseRepositoryare registered inProgram.cs. - Blob storage paths are stored in relational rows, while the actual bytes live in Azure Blob Storage.
Swagger guidance
Section titled “Swagger guidance”Swagger is enabled unconditionally. Endpoint descriptions should explain the product intent of each route, not only the transport mechanics.