' ============================================================ ' WALKGUIDE — DESIGN PATTERNS (GoF) ' Flutter × Spring Boot × In-Device AI ' Pattern 7 of 7: CHAIN OF RESPONSIBILITY (Behavioral) ' ============================================================ @startuml WalkGuide_07_ChainOfResponsibility 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 7 — CHAIN OF RESPONSIBILITY (Behavioral) ' ============================================================ package "⑦ Chain of Responsibility [Behavioral]" #FCE4EC { ' --- Flutter: Dio Interceptor Chain --- abstract class "Interceptor\n<>" as DioInterceptor { + onRequest(options, handler) + onResponse(response, handler) + onError(error, handler) } class "AuthInterceptor\n<>" as AuthInterceptor { - _secureStorage : FlutterSecureStorage + onRequest(options, handler) ' Attach Bearer token ke setiap request + onError(error, handler) ' Jika 401: refresh token → retry request } class "ErrorInterceptor\n<>" as ErrorInterceptor { + onError(error, handler) ' Map HTTP error codes → Failure domain objects ' 401 → UnauthorizedFailure ' 404 → NotFoundFailure ' 500 → ServerFailure } class "LogInterceptor\n<>" as LogInterceptorFlutter { + onRequest(options, handler) + onResponse(response, handler) + onError(error, handler) ' Print request/response untuk debugging } class "ApiClient\n<>" as ApiClient { - _dio : Dio + get(url) : Response + post(url, data) : Response + put(url, data) : Response ' Interceptors dieksekusi berurutan } ' --- Backend: Spring Security Filter Chain --- abstract class "OncePerRequestFilter\n<>" as SpringFilter { + {abstract} doFilterInternal(req, res, chain) + doFilter(req, res, chain) } class "JwtAuthFilter\n<>" as JwtAuthFilter { - _jwtUtil : JwtUtil - _userDetailsService : UserDetailsService + doFilterInternal(req, res, chain) ' Extract JWT → validate → set SecurityContext ' Jika invalid: 401 Unauthorized langsung } class "SecurityConfig\n<>" as SecurityConfigChain { + securityFilterChain(http) : SecurityFilterChain ' /auth/** → permitAll ' /api/v1/** → authenticated ' ROLE_GUARDIAN vs ROLE_USER routing } class "Controller\n<>" as ControllerChain { + handleRequest(request) : ResponseEntity ' Request tiba hanya jika lolos semua filter } DioInterceptor <|-- AuthInterceptor : extends DioInterceptor <|-- ErrorInterceptor : extends DioInterceptor <|-- LogInterceptorFlutter : extends ApiClient --> AuthInterceptor : chain[0] AuthInterceptor --> ErrorInterceptor : passes to\nchain[1] ErrorInterceptor --> LogInterceptorFlutter : passes to\nchain[2] SpringFilter <|-- JwtAuthFilter : extends JwtAuthFilter --> SecurityConfigChain : passes to SecurityConfigChain --> ControllerChain : passes to\n(if authorized) } note bottom of ApiClient Flutter — Dio interceptor chain: HTTP Request → AuthInterceptor → ErrorInterceptor → LogInterceptor → Response Setiap handler bisa: proses, modifikasi, atau blok request. AuthInterceptor otomatis refresh JWT jika expired (401). end note note bottom of JwtAuthFilter Backend — Spring Security filter chain: HTTP Request → JwtAuthFilter → SecurityConfig → Controller JwtAuthFilter extract & validasi Bearer token setiap request. Jika invalid → langsung 401 tanpa lanjut ke controller. end note @enduml