' ============================================================ ' WALKGUIDE — DESIGN PATTERNS (GoF) ' Flutter × Spring Boot × In-Device AI ' Pattern 4 of 7: REPOSITORY / PROXY (Structural) ' ============================================================ @startuml WalkGuide_04_Repository_Proxy skinparam monochrome false skinparam shadowing false skinparam defaultFontName Arial skinparam defaultFontSize 12 skinparam roundCorner 10 skinparam ArrowColor #555555 skinparam ArrowThickness 1.2 skinparam class { BackgroundColor #FAFAFA BorderColor #AAAAAA HeaderBackgroundColor #E8E8E8 FontColor #222222 StereotypeFontColor #666666 AttributeFontColor #333333 } skinparam package { BackgroundColor #F5F5F5 BorderColor #888888 FontColor #333333 FontStyle bold } skinparam note { BackgroundColor #FFFDE7 BorderColor #F9A825 FontColor #444444 FontSize 11 } ' ============================================================ ' PATTERN 4 — REPOSITORY / PROXY (Structural) ' ============================================================ package "④ Repository Pattern [Structural — Proxy]" #FFF3E0 { interface "WalkGuideRepository\n<>" as WalkGuideRepo { + startSession() : Either + stopSession() : Either + logObstacle(ObstacleLogRequest) : Either + getObstacleLogs() : Either> } interface "ActivityLogRepository\n<>" as ActivityRepo { + getLogs(page) : Either> + savePending(ActivityLog) : Either + syncPending() : Either } class "WalkGuideRepositoryImpl\n<>" as WalkGuideRepoImpl { - _apiClient : ApiClient - _offlineQueue : OfflineQueueService - _connectivity : ConnectivityPlus + startSession() : Either + logObstacle(req) : Either - _isOnline() : bool } class "ActivityLogRepositoryImpl\n<>" as ActivityRepoImpl { - _remoteDataSource : ActivityRemoteDataSource - _localDataSource : ActivityLocalDataSource + getLogs(page) : Either + syncPending() : Either } class "ApiClient\n<>" as RemoteDSWalk { + startSession() : void + logObstacle(req) : void ' POST /api/v1/user/obstacle } class "OfflineQueueService + LocalDatabase\n<>" as LocalDSWalk { + cacheObstacle(ObstacleLog) : void + getPendingLogs() : List ' SQLite-backed offline first } WalkGuideRepo <|.. WalkGuideRepoImpl : implements ActivityRepo <|.. ActivityRepoImpl : implements WalkGuideRepoImpl --> RemoteDSWalk : online path WalkGuideRepoImpl --> LocalDSWalk : offline path } note bottom of WalkGuideRepoImpl Proxy memutuskan sumber data: if (isOnline) → fetch remote API else → baca/tulis SQLite (Drift) Domain layer tidak tahu perbedaannya. dartz Either untuk typed error. end note @enduml