Skip to content

API architecture

Controllers HTTP routes, validation, response mapping
Application/Services Business orchestration and use-case logic
Application/DTOs Request and response contracts
Domain Entities and repository interfaces
Infrastructure EF Core, blob storage, auth, Foundry, OpenGolf, messaging
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.

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.

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.

  • EF Core repositories are the system of record for structured data.
  • Repositories such as EfPhotoRepository, EfProfileRepository, and EfGolfCourseRepository are registered in Program.cs.
  • Blob storage paths are stored in relational rows, while the actual bytes live in Azure Blob Storage.

Swagger is enabled unconditionally. Endpoint descriptions should explain the product intent of each route, not only the transport mechanics.