235 lines
12 KiB
Markdown
235 lines
12 KiB
Markdown
# Hướng Dẫn Kiến Trúc
|
|
|
|
> Tài liệu kiến trúc chi tiết cho ứng dụng iOS AppClientBaseSwift.
|
|
|
|
## Tổng Quan
|
|
|
|
AppClientBaseSwift là ứng dụng iOS native được xây dựng theo mẫu kiến trúc **MVVM (Model-View-ViewModel)** với **SwiftUI** cho UI declarative. Ứng dụng tuân theo các best practices phát triển hiện đại của Apple bao gồm:
|
|
|
|
- **Swift Concurrency** (async/await)
|
|
- **Combine** cho reactive data binding
|
|
- **Protocol-oriented programming** để tăng khả năng test
|
|
- **Keychain Services** cho lưu trữ bảo mật
|
|
|
|
## Sơ Đồ Kiến Trúc
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ LỚP PRESENTATION │
|
|
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ SwiftUI Views │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ SplashView │ │ HomeView │ │ ExploreView │ │ ProfileView │ │ │
|
|
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ LoginView │ │RegisterView │ │ForgotPasswd │ │ │
|
|
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
│ @StateObject / @EnvironmentObject │
|
|
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ ViewModels (@MainActor) │ │
|
|
│ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │
|
|
│ │ │ AuthViewModel │ │ HomeViewModel │ │ProfileViewModel│ │ │
|
|
│ │ └────────────────┘ └────────────────┘ └────────────────┘ │ │
|
|
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
Dependency Injection (dựa trên Protocol)
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ LỚP SERVICE │
|
|
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │
|
|
│ │ APIService │ │ AuthManager │ │
|
|
│ │ • request<T>() │ │ • @Published authState │ │
|
|
│ │ • get(), post() │ │ • login(), register() │ │
|
|
│ │ • URLSession │ │ • Lưu trữ Keychain │ │
|
|
│ └─────────────────────────────┘ └─────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ LỚP DATA │
|
|
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │
|
|
│ │ Models │ │ Constants │ │
|
|
│ │ • User (Codable) │ │ • APIConfig │ │
|
|
│ │ • HomeItem │ │ • StorageKeys │ │
|
|
│ │ • AuthState │ │ • DesignSystem │ │
|
|
│ └─────────────────────────────┘ └─────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Chi Tiết Component
|
|
|
|
### 1. Lớp Presentation
|
|
|
|
#### Views
|
|
| Component | Trách nhiệm |
|
|
|-----------|-------------|
|
|
| `SplashView` | Màn hình splash động, điều hướng trễ |
|
|
| `ContentView` | Container TabView gốc, routing theo auth state |
|
|
| `AuthContainerView` | Điều hướng luồng Auth (Login/Đăng ký/Quên MK) |
|
|
| `HomeView` | Tab Home với lời chào, promo, dịch vụ |
|
|
| `ExploreView` | Tính năng khám phá và tìm kiếm |
|
|
| `ProfileView` | Hồ sơ người dùng và cài đặt |
|
|
|
|
#### ViewModels
|
|
```swift
|
|
@MainActor
|
|
final class HomeViewModel: ObservableObject {
|
|
@Published var isLoading: Bool = false
|
|
@Published var items: [HomeItem] = []
|
|
@Published var errorMessage: String?
|
|
|
|
private let apiService: APIServiceProtocol
|
|
|
|
func loadData() async { ... }
|
|
}
|
|
```
|
|
|
|
### 2. Lớp Service
|
|
|
|
#### APIService
|
|
HTTP client tuân theo nguyên tắc Single Responsibility:
|
|
|
|
```swift
|
|
protocol APIServiceProtocol {
|
|
func request<T: Decodable>(
|
|
endpoint: String,
|
|
method: HTTPMethod,
|
|
body: Encodable?,
|
|
headers: [String: String]?
|
|
) async throws -> T
|
|
}
|
|
```
|
|
|
|
**Tính năng:**
|
|
- Xử lý request/response generic
|
|
- Tự động mã hóa/giải mã JSON (snake_case ↔ camelCase)
|
|
- Tự động thêm Bearer token
|
|
- Xử lý mã trạng thái HTTP
|
|
|
|
#### AuthManager
|
|
Singleton quản lý trạng thái xác thực:
|
|
|
|
```swift
|
|
final class AuthManager: ObservableObject {
|
|
@MainActor static let shared = AuthManager()
|
|
@Published var authState: AuthState = .unknown
|
|
}
|
|
```
|
|
|
|
**Enum AuthState:**
|
|
```swift
|
|
enum AuthState {
|
|
case unknown // Trạng thái khởi tạo
|
|
case unauthenticated // Chưa đăng nhập
|
|
case authenticated(User) // Đã đăng nhập
|
|
}
|
|
```
|
|
|
|
## Luồng Dữ Liệu
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant V as View
|
|
participant VM as ViewModel
|
|
participant S as Service
|
|
participant API as Backend API
|
|
|
|
V->>VM: Hành động User (tap button)
|
|
VM->>VM: Đặt isLoading = true
|
|
VM->>S: await service.request()
|
|
S->>API: HTTP Request
|
|
API-->>S: JSON Response
|
|
S-->>VM: Model đã giải mã
|
|
VM->>VM: Cập nhật @Published
|
|
VM-->>V: SwiftUI render lại
|
|
```
|
|
|
|
## Luồng Xác Thực
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> Unknown: Khởi động App
|
|
Unknown --> Authenticated: Token hợp lệ
|
|
Unknown --> Unauthenticated: Không có Token
|
|
|
|
Unauthenticated --> Login
|
|
Login --> Authenticated: Thành công
|
|
Login --> Register: Điều hướng
|
|
Register --> Authenticated: Thành công
|
|
|
|
Authenticated --> HomeScreen
|
|
HomeScreen --> Unauthenticated: Đăng xuất
|
|
```
|
|
|
|
## Quyết Định Thiết Kế
|
|
|
|
### 1. Tại sao MVVM?
|
|
|
|
| Lợi ích | Mô tả |
|
|
|---------|-------|
|
|
| Khả năng test | ViewModel có thể test độc lập không cần UI |
|
|
| Phân tách trách nhiệm | View chỉ hiển thị, logic nằm ở ViewModel |
|
|
| Tương thích SwiftUI | `@ObservableObject` + `@Published` native |
|
|
| Cập nhật reactive | UI tự động làm mới dựa trên Combine |
|
|
|
|
### 2. Tại sao DI dựa trên Protocol?
|
|
|
|
```swift
|
|
// Protocol cho phép mock khi test
|
|
protocol APIServiceProtocol { ... }
|
|
|
|
// Production
|
|
final class APIService: APIServiceProtocol { ... }
|
|
|
|
// Test mock
|
|
final class MockAPIService: APIServiceProtocol { ... }
|
|
```
|
|
|
|
### 3. Tại sao Keychain thay vì UserDefaults?
|
|
|
|
| Keychain | UserDefaults |
|
|
|----------|--------------|
|
|
| ✅ Mã hóa khi lưu trữ | ❌ Text thuần |
|
|
| ✅ Secure enclave | ❌ Có thể truy cập |
|
|
| ✅ Riêng cho app | ❌ Shared prefs |
|
|
|
|
### 4. Tại sao @MainActor trên ViewModels?
|
|
|
|
- Đảm bảo tất cả cập nhật `@Published` xảy ra trên main thread
|
|
- Ngăn chặn vấn đề concurrency với SwiftUI
|
|
- Contract an toàn thread rõ ràng
|
|
|
|
## Kiến Trúc Bảo Mật
|
|
|
|
```
|
|
┌────────────────────────────────────────────────────────────┐
|
|
│ CÁC LỚP BẢO MẬT │
|
|
├────────────────────────────────────────────────────────────┤
|
|
│ Lớp 1: Bảo mật Transport (HTTPS/TLS) │
|
|
│ • Tất cả API calls sử dụng HTTPS │
|
|
├────────────────────────────────────────────────────────────┤
|
|
│ Lớp 2: Bảo mật Token (Keychain) │
|
|
│ • Access token lưu trong Keychain │
|
|
│ • Refresh token lưu trong Keychain │
|
|
├────────────────────────────────────────────────────────────┤
|
|
│ Lớp 3: Bảo mật Session │
|
|
│ • Kiểm tra hết hạn token │
|
|
│ • Tự động refresh token │
|
|
├────────────────────────────────────────────────────────────┤
|
|
│ Lớp 4: Validation Input │
|
|
│ • Kiểm tra định dạng email │
|
|
│ • Kiểm tra độ mạnh mật khẩu │
|
|
└────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Hướng Phát Triển
|
|
|
|
| Tính năng | Ưu tiên | Mô tả |
|
|
|-----------|---------|-------|
|
|
| Certificate Pinning | Cao | Xác thực chứng chỉ TLS |
|
|
| Xác thực sinh trắc | Cao | Đăng nhập Face ID / Touch ID |
|
|
| Chế độ Offline | Trung bình | Cache local với SwiftData |
|
|
| Push Notifications | Trung bình | Tích hợp APNs |
|
|
|
|
## Tài Liệu Liên Quan
|
|
|
|
- [README.md](./README.md) - Hướng dẫn bắt đầu nhanh
|