Initial commit
This commit is contained in:
181
skills/clean-architecture-review/SKILL.md
Normal file
181
skills/clean-architecture-review/SKILL.md
Normal file
@@ -0,0 +1,181 @@
|
||||
---
|
||||
name: clean-architecture-review
|
||||
description: Validate Clean Architecture implementation in iOS. Checks layer separation (Presentation/Domain/Data), MVVM patterns, dependency injection with Swinject, and UseCase/Repository patterns. Use when reviewing architecture, checking layer boundaries, or validating DI.
|
||||
allowed-tools: Read, Grep, Glob
|
||||
---
|
||||
|
||||
# Clean Architecture Validator
|
||||
|
||||
Verify Clean Architecture and MVVM implementation in iOS code following Payoo Merchant patterns.
|
||||
|
||||
## When to Activate
|
||||
|
||||
- "architecture review", "layer separation", "clean architecture"
|
||||
- "MVVM", "dependency injection", "DI"
|
||||
- "use case", "repository pattern"
|
||||
- Reviewing module structure or refactoring
|
||||
|
||||
## Architecture Layers
|
||||
|
||||
**Presentation** → ViewControllers, ViewModels, Views
|
||||
**Domain** → UseCases (business logic), Models, Repository protocols
|
||||
**Data** → Repository implementations, API Services, Local Storage
|
||||
|
||||
**Correct Flow**:
|
||||
```
|
||||
UI → ViewController → ViewModel → UseCase → Repository → API/DB
|
||||
```
|
||||
|
||||
## Review Process
|
||||
|
||||
### Step 1: Map Architecture
|
||||
|
||||
Classify files into layers:
|
||||
- Presentation: `*ViewController.swift`, `*ViewModel.swift`
|
||||
- Domain: `*UseCase.swift`, `*Repository.swift` (protocols)
|
||||
- Data: `*RepositoryImpl.swift`, `*ApiService.swift`
|
||||
|
||||
### Step 2: Check Layer Violations
|
||||
|
||||
**Critical Issues**:
|
||||
- 🔴 ViewModel calling API directly (bypassing UseCase)
|
||||
- 🔴 Business logic in ViewModel (should be in UseCase)
|
||||
- 🔴 UseCase calling API directly (bypassing Repository)
|
||||
- 🔴 Direct instantiation (no DI)
|
||||
|
||||
### Step 3: Verify Patterns
|
||||
|
||||
**BaseViewModel**:
|
||||
```swift
|
||||
✅ class PaymentViewModel: BaseViewModel<PaymentState>
|
||||
❌ class PaymentViewModel // Should extend BaseViewModel
|
||||
```
|
||||
|
||||
**UseCase Pattern**:
|
||||
```swift
|
||||
✅ protocol PaymentUseCase { }
|
||||
✅ class PaymentUseCaseImpl: PaymentUseCase { }
|
||||
❌ class PaymentUseCase { } // Should be protocol + impl
|
||||
```
|
||||
|
||||
**Repository Pattern**:
|
||||
```swift
|
||||
✅ protocol PaymentRepository { } // In Domain
|
||||
✅ class PaymentRepositoryImpl: PaymentRepository { } // In Data
|
||||
```
|
||||
|
||||
**Dependency Injection**:
|
||||
```swift
|
||||
✅ init(paymentUC: PaymentUseCase) { // Constructor injection
|
||||
self.paymentUC = paymentUC
|
||||
}
|
||||
❌ let paymentUC = PaymentUseCaseImpl() // Direct instantiation
|
||||
```
|
||||
|
||||
### Step 4: Generate Report
|
||||
|
||||
Provide:
|
||||
- Architecture compliance score
|
||||
- Layer violations by severity
|
||||
- Current vs. should-be architecture
|
||||
- Refactoring steps
|
||||
- Effort estimate
|
||||
|
||||
## Common Violations
|
||||
|
||||
### ❌ ViewModel Bypassing UseCase
|
||||
```swift
|
||||
class PaymentViewModel {
|
||||
private let apiService: PaymentApiService // WRONG LAYER!
|
||||
}
|
||||
```
|
||||
**Should be**:
|
||||
```swift
|
||||
class PaymentViewModel {
|
||||
private let paymentUC: PaymentUseCase // CORRECT!
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Business Logic in ViewModel
|
||||
```swift
|
||||
class PaymentViewModel {
|
||||
func processPayment(amount: Double) {
|
||||
// ❌ Validation in ViewModel
|
||||
guard amount > 1000 else { return }
|
||||
// ❌ Business rules in ViewModel
|
||||
let fee = amount * 0.01
|
||||
}
|
||||
}
|
||||
```
|
||||
**Should be in UseCase**:
|
||||
```swift
|
||||
class PaymentUseCaseImpl {
|
||||
func execute(amount: Double) -> Single<PaymentResult> {
|
||||
// ✅ Validation in UseCase
|
||||
return validateAmount(amount)
|
||||
.flatMap { processPayment($0) }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
# Clean Architecture Review
|
||||
|
||||
## Compliance Score: X/100
|
||||
|
||||
## Critical Violations: X
|
||||
|
||||
### 1. ViewModel Bypassing UseCase
|
||||
**File**: `PaymentViewModel.swift:15`
|
||||
**Current**: ViewModel → API
|
||||
**Should be**: ViewModel → UseCase → Repository → API
|
||||
|
||||
**Fix**: [Refactoring steps]
|
||||
|
||||
---
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
### Current (Problematic)
|
||||
ViewModel → ApiService ❌
|
||||
|
||||
### Should Be
|
||||
ViewModel → UseCase → Repository → ApiService ✅
|
||||
|
||||
## Recommendations
|
||||
1. Create missing UseCases
|
||||
2. Move business logic to Domain layer
|
||||
3. Setup DI container
|
||||
4. Add Repository layer
|
||||
|
||||
## Effort Estimate
|
||||
- Module refactoring: X hours
|
||||
- DI setup: X hours
|
||||
- Testing: X hours
|
||||
```
|
||||
|
||||
## Quick Checks
|
||||
|
||||
**Layer Boundaries**:
|
||||
- [ ] ViewModels only depend on UseCases
|
||||
- [ ] UseCases contain all business logic
|
||||
- [ ] Repositories handle data access only
|
||||
- [ ] No UI code in Domain/Data layers
|
||||
|
||||
**Dependency Injection**:
|
||||
- [ ] All dependencies via constructor
|
||||
- [ ] No direct instantiation
|
||||
- [ ] Swinject container registration
|
||||
- [ ] Protocol-based dependencies
|
||||
|
||||
**Patterns**:
|
||||
- [ ] ViewModels extend BaseViewModel
|
||||
- [ ] UseCases follow protocol + impl
|
||||
- [ ] Repositories follow protocol + impl
|
||||
- [ ] State management via setState()
|
||||
|
||||
## Reference
|
||||
|
||||
**Detailed Examples**: See `examples.md` for complete architecture patterns and refactoring guides.
|
||||
505
skills/clean-architecture-review/examples.md
Normal file
505
skills/clean-architecture-review/examples.md
Normal file
@@ -0,0 +1,505 @@
|
||||
# Clean Architecture Examples
|
||||
|
||||
Complete examples of proper layer separation, MVVM patterns, and dependency injection.
|
||||
|
||||
## Complete Architecture Example
|
||||
|
||||
### Proper 3-Layer Implementation
|
||||
|
||||
#### Presentation Layer - ViewModel
|
||||
```swift
|
||||
class PaymentViewModel: BaseViewModel<PaymentState> {
|
||||
// ✅ Depends only on UseCase (Domain layer)
|
||||
private let paymentUC: PaymentUseCase
|
||||
private let disposeBag = DisposeBag()
|
||||
|
||||
// UI State
|
||||
let paymentAmount = BehaviorRelay<String>(value: "")
|
||||
let isProcessing = BehaviorRelay<Bool>(value: false)
|
||||
|
||||
// ✅ Constructor injection
|
||||
init(paymentUC: PaymentUseCase) {
|
||||
self.paymentUC = paymentUC
|
||||
super.init()
|
||||
}
|
||||
|
||||
func processPayment() {
|
||||
isProcessing.accept(true)
|
||||
|
||||
// ✅ Delegates to UseCase, no business logic here
|
||||
paymentUC.execute(amount: paymentAmount.value)
|
||||
.subscribeOn(ConcurrentScheduler.background)
|
||||
.observeOn(MainScheduler.instance)
|
||||
.subscribe(
|
||||
onNext: { [weak self] result in
|
||||
self?.setState(.success(result))
|
||||
},
|
||||
onError: { [weak self] error in
|
||||
self?.setState(.error(error))
|
||||
},
|
||||
onCompleted: { [weak self] in
|
||||
self?.isProcessing.accept(false)
|
||||
}
|
||||
)
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Domain Layer - UseCase
|
||||
```swift
|
||||
// ✅ Protocol in Domain layer
|
||||
protocol PaymentUseCase {
|
||||
func execute(amount: String) -> Single<PaymentResult>
|
||||
}
|
||||
|
||||
// ✅ Implementation in Domain layer
|
||||
class PaymentUseCaseImpl: PaymentUseCase {
|
||||
// ✅ Depends on Repository protocol (Domain layer)
|
||||
private let paymentRepository: PaymentRepository
|
||||
private let validationService: ValidationService
|
||||
|
||||
init(paymentRepository: PaymentRepository,
|
||||
validationService: ValidationService) {
|
||||
self.paymentRepository = paymentRepository
|
||||
self.validationService = validationService
|
||||
}
|
||||
|
||||
func execute(amount: String) -> Single<PaymentResult> {
|
||||
// ✅ Business logic in UseCase
|
||||
return validationService.validateAmount(amount)
|
||||
.flatMap { validatedAmount in
|
||||
// ✅ Calls Repository, not API directly
|
||||
return self.paymentRepository.processPayment(amount: validatedAmount)
|
||||
}
|
||||
.map { response in
|
||||
// ✅ Business rules applied here
|
||||
return self.applyBusinessRules(response)
|
||||
}
|
||||
}
|
||||
|
||||
private func applyBusinessRules(_ response: PaymentResponse) -> PaymentResult {
|
||||
// Business logic here
|
||||
return PaymentResult(from: response)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Data Layer - Repository
|
||||
```swift
|
||||
// ✅ Protocol in Domain layer
|
||||
protocol PaymentRepository {
|
||||
func processPayment(amount: Double) -> Single<PaymentResponse>
|
||||
}
|
||||
|
||||
// ✅ Implementation in Data layer
|
||||
class PaymentRepositoryImpl: PaymentRepository {
|
||||
private let apiService: PaymentApiService
|
||||
private let localStorage: PaymentLocalStorage
|
||||
|
||||
init(apiService: PaymentApiService, localStorage: PaymentLocalStorage) {
|
||||
self.apiService = apiService
|
||||
self.localStorage = localStorage
|
||||
}
|
||||
|
||||
func processPayment(amount: Double) -> Single<PaymentResponse> {
|
||||
// ✅ Data access only, no business logic
|
||||
return apiService.processPayment(amount: amount)
|
||||
.do(onSuccess: { [weak self] response in
|
||||
// Save to local storage
|
||||
self?.localStorage.savePaymentRecord(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### DI Setup - Swinject
|
||||
```swift
|
||||
extension Container {
|
||||
func registerPaymentModule() {
|
||||
// Register Use Cases
|
||||
register(PaymentUseCase.self) { resolver in
|
||||
PaymentUseCaseImpl(
|
||||
paymentRepository: resolver.resolve(PaymentRepository.self)!,
|
||||
validationService: resolver.resolve(ValidationService.self)!
|
||||
)
|
||||
}
|
||||
|
||||
// Register Repositories
|
||||
register(PaymentRepository.self) { resolver in
|
||||
PaymentRepositoryImpl(
|
||||
apiService: resolver.resolve(PaymentApiService.self)!,
|
||||
localStorage: resolver.resolve(PaymentLocalStorage.self)!
|
||||
)
|
||||
}
|
||||
|
||||
// Register ViewModels
|
||||
register(PaymentViewModel.self) { resolver in
|
||||
PaymentViewModel(
|
||||
paymentUC: resolver.resolve(PaymentUseCase.self)!
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Anti-Patterns
|
||||
|
||||
### ❌ Anti-Pattern 1: ViewModel Calling API Directly
|
||||
|
||||
```swift
|
||||
class PaymentViewModel: BaseViewModel<PaymentState> {
|
||||
private let apiService: PaymentApiService // ❌ Wrong layer!
|
||||
|
||||
func processPayment(amount: Double) {
|
||||
// ❌ Direct API call bypasses business logic
|
||||
apiService.processPayment(amount: amount)
|
||||
.subscribe(onNext: { result in
|
||||
// Handle result
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Problems**:
|
||||
- Business logic scattered or missing
|
||||
- Hard to test
|
||||
- Violates layer separation
|
||||
- Cannot reuse logic elsewhere
|
||||
|
||||
**Fix**: Add UseCase layer
|
||||
```swift
|
||||
// 1. Create UseCase
|
||||
protocol PaymentUseCase {
|
||||
func execute(amount: Double) -> Single<PaymentResult>
|
||||
}
|
||||
|
||||
// 2. Update ViewModel
|
||||
class PaymentViewModel: BaseViewModel<PaymentState> {
|
||||
private let paymentUC: PaymentUseCase // ✅ Correct!
|
||||
|
||||
func processPayment(amount: Double) {
|
||||
paymentUC.execute(amount: amount) // ✅ Through UseCase
|
||||
.subscribe(/* ... */)
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ❌ Anti-Pattern 2: Business Logic in ViewModel
|
||||
|
||||
```swift
|
||||
class PaymentViewModel: BaseViewModel<PaymentState> {
|
||||
func processPayment(amount: String) {
|
||||
// ❌ Validation logic in ViewModel
|
||||
guard let amt = Double(amount), amt > 1000 else {
|
||||
setState(.showError("Invalid amount"))
|
||||
return
|
||||
}
|
||||
|
||||
// ❌ Business rules in ViewModel
|
||||
let fee = amt * 0.01
|
||||
let total = amt + fee
|
||||
|
||||
if total > 50_000_000 {
|
||||
setState(.showError("Amount too high"))
|
||||
return
|
||||
}
|
||||
|
||||
// Process...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Problems**:
|
||||
- Cannot reuse validation/business logic
|
||||
- Hard to test logic independently
|
||||
- ViewModel becomes complex
|
||||
- Violates Single Responsibility
|
||||
|
||||
**Fix**: Move to UseCase
|
||||
```swift
|
||||
// ✅ Business logic in UseCase
|
||||
class PaymentUseCaseImpl: PaymentUseCase {
|
||||
func execute(amount: String) -> Single<PaymentResult> {
|
||||
return validateAmount(amount)
|
||||
.flatMap { validatedAmount in
|
||||
return self.calculateFeesAndProcess(validatedAmount)
|
||||
}
|
||||
}
|
||||
|
||||
private func validateAmount(_ amount: String) -> Single<Double> {
|
||||
guard let amt = Double(amount), amt > 1000 else {
|
||||
return .error(PaymentError.invalidAmount)
|
||||
}
|
||||
|
||||
let fee = amt * 0.01
|
||||
let total = amt + fee
|
||||
|
||||
guard total <= 50_000_000 else {
|
||||
return .error(PaymentError.amountTooHigh)
|
||||
}
|
||||
|
||||
return .just(amt)
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ ViewModel simplified
|
||||
class PaymentViewModel: BaseViewModel<PaymentState> {
|
||||
func processPayment(amount: String) {
|
||||
paymentUC.execute(amount: amount)
|
||||
.subscribe(
|
||||
onNext: { [weak self] result in
|
||||
self?.setState(.success(result))
|
||||
},
|
||||
onError: { [weak self] error in
|
||||
self?.setState(.error(error))
|
||||
}
|
||||
)
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ❌ Anti-Pattern 3: UseCase Calling API Directly
|
||||
|
||||
```swift
|
||||
class PaymentUseCaseImpl: PaymentUseCase {
|
||||
private let apiService: PaymentApiService // ❌ Bypasses Repository!
|
||||
|
||||
func execute(amount: Double) -> Single<PaymentResult> {
|
||||
// ❌ Direct API call
|
||||
return apiService.processPayment(amount: amount)
|
||||
.map { response in
|
||||
return PaymentResult(from: response)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Problems**:
|
||||
- Cannot swap data sources (API/local/mock)
|
||||
- Hard to test independently
|
||||
- Violates Dependency Inversion
|
||||
|
||||
**Fix**: Add Repository layer
|
||||
```swift
|
||||
// ✅ Repository protocol in Domain
|
||||
protocol PaymentRepository {
|
||||
func processPayment(amount: Double) -> Single<PaymentResponse>
|
||||
}
|
||||
|
||||
// ✅ UseCase depends on protocol
|
||||
class PaymentUseCaseImpl: PaymentUseCase {
|
||||
private let paymentRepository: PaymentRepository // ✅ Correct!
|
||||
|
||||
func execute(amount: Double) -> Single<PaymentResult> {
|
||||
return paymentRepository.processPayment(amount: amount)
|
||||
.map { response in
|
||||
return PaymentResult(from: response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Implementation in Data layer
|
||||
class PaymentRepositoryImpl: PaymentRepository {
|
||||
private let apiService: PaymentApiService
|
||||
|
||||
func processPayment(amount: Double) -> Single<PaymentResponse> {
|
||||
return apiService.processPayment(amount: amount)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Refactoring Examples
|
||||
|
||||
### Example: Refactoring ViewModel with Business Logic
|
||||
|
||||
#### Before (Violates Clean Architecture)
|
||||
```swift
|
||||
class TransactionViewModel: BaseViewModel<TransactionState> {
|
||||
private let apiService: TransactionApiService
|
||||
|
||||
func loadTransactions(from startDate: Date, to endDate: Date) {
|
||||
// ❌ Date validation in ViewModel
|
||||
guard startDate <= endDate else {
|
||||
setState(.showError("Invalid date range"))
|
||||
return
|
||||
}
|
||||
|
||||
// ❌ Business rule in ViewModel
|
||||
let daysDiff = Calendar.current.dateComponents([.day], from: startDate, to: endDate).day!
|
||||
guard daysDiff <= 90 else {
|
||||
setState(.showError("Date range too large"))
|
||||
return
|
||||
}
|
||||
|
||||
// ❌ Direct API call
|
||||
apiService.getTransactions(from: startDate, to: endDate)
|
||||
.subscribe(onNext: { [weak self] transactions in
|
||||
// ❌ Business logic: filtering
|
||||
let filtered = transactions.filter { $0.amount > 0 }
|
||||
self?.transactions.accept(filtered)
|
||||
})
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### After (Clean Architecture)
|
||||
```swift
|
||||
// DOMAIN LAYER - UseCase
|
||||
protocol TransactionUseCase {
|
||||
func getTransactions(from: Date, to: Date) -> Single<[Transaction]>
|
||||
}
|
||||
|
||||
class TransactionUseCaseImpl: TransactionUseCase {
|
||||
private let repository: TransactionRepository
|
||||
|
||||
func getTransactions(from startDate: Date, to endDate: Date) -> Single<[Transaction]> {
|
||||
// ✅ Validation in UseCase
|
||||
return validateDateRange(startDate, endDate)
|
||||
.flatMap { _ in
|
||||
return self.repository.getTransactions(from: startDate, to: endDate)
|
||||
}
|
||||
.map { transactions in
|
||||
// ✅ Business logic in UseCase
|
||||
return self.filterValidTransactions(transactions)
|
||||
}
|
||||
}
|
||||
|
||||
private func validateDateRange(_ start: Date, _ end: Date) -> Single<Void> {
|
||||
guard start <= end else {
|
||||
return .error(TransactionError.invalidDateRange)
|
||||
}
|
||||
|
||||
let daysDiff = Calendar.current.dateComponents([.day], from: start, to: end).day!
|
||||
guard daysDiff <= 90 else {
|
||||
return .error(TransactionError.dateRangeTooLarge)
|
||||
}
|
||||
|
||||
return .just(())
|
||||
}
|
||||
|
||||
private func filterValidTransactions(_ transactions: [Transaction]) -> [Transaction] {
|
||||
return transactions.filter { $0.amount > 0 }
|
||||
}
|
||||
}
|
||||
|
||||
// PRESENTATION LAYER - ViewModel
|
||||
class TransactionViewModel: BaseViewModel<TransactionState> {
|
||||
private let transactionUC: TransactionUseCase // ✅ Depends on UseCase
|
||||
|
||||
func loadTransactions(from startDate: Date, to endDate: Date) {
|
||||
// ✅ Simply delegates to UseCase
|
||||
transactionUC.getTransactions(from: startDate, to: endDate)
|
||||
.subscribe(
|
||||
onNext: { [weak self] transactions in
|
||||
self?.transactions.accept(transactions)
|
||||
self?.setState(.loaded)
|
||||
},
|
||||
onError: { [weak self] error in
|
||||
self?.setState(.error(error))
|
||||
}
|
||||
)
|
||||
.disposed(by: disposeBag)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- ✅ Clear separation of concerns
|
||||
- ✅ Business logic testable in isolation
|
||||
- ✅ ViewModel is simple coordinator
|
||||
- ✅ Can reuse UseCase elsewhere
|
||||
|
||||
---
|
||||
|
||||
## Testing Benefits
|
||||
|
||||
### With Clean Architecture
|
||||
|
||||
```swift
|
||||
class PaymentUseCaseTests: XCTestCase {
|
||||
func testExecute_WithValidAmount_ProcessesPayment() {
|
||||
// ✅ Can test UseCase in isolation
|
||||
let mockRepository = MockPaymentRepository()
|
||||
mockRepository.processPaymentResult = .just(PaymentResponse.success)
|
||||
|
||||
let useCase = PaymentUseCaseImpl(paymentRepository: mockRepository)
|
||||
|
||||
// Test business logic directly
|
||||
let result = try! useCase.execute(amount: "10000").toBlocking().first()!
|
||||
XCTAssertTrue(result.isSuccess)
|
||||
}
|
||||
|
||||
func testExecute_WithInvalidAmount_ReturnsError() {
|
||||
let mockRepository = MockPaymentRepository()
|
||||
let useCase = PaymentUseCaseImpl(paymentRepository: mockRepository)
|
||||
|
||||
// Test validation logic
|
||||
XCTAssertThrowsError(
|
||||
try useCase.execute(amount: "500").toBlocking().first()
|
||||
) { error in
|
||||
XCTAssertEqual(error as? PaymentError, .invalidAmount)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Without Clean Architecture
|
||||
|
||||
```swift
|
||||
// ❌ Hard to test business logic without UI/Network
|
||||
class PaymentViewModelTests: XCTestCase {
|
||||
func testProcessPayment() {
|
||||
// Need to mock UI, network, and test everything together
|
||||
// Business logic mixed with presentation logic
|
||||
// Hard to isolate what's being tested
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist for Reviews
|
||||
|
||||
```markdown
|
||||
## Clean Architecture Checklist
|
||||
|
||||
### Layer Separation
|
||||
- [ ] ViewModel only depends on UseCase (not API/Repository)
|
||||
- [ ] UseCase contains all business logic
|
||||
- [ ] UseCase only depends on Repository protocol
|
||||
- [ ] Repository implementation is in Data layer
|
||||
- [ ] No business logic in ViewModel
|
||||
- [ ] No business logic in Repository
|
||||
- [ ] No UI code in Domain/Data layers
|
||||
|
||||
### Patterns
|
||||
- [ ] ViewModel extends BaseViewModel<State>
|
||||
- [ ] UseCase follows protocol + implementation pattern
|
||||
- [ ] Repository follows protocol + implementation pattern
|
||||
- [ ] State management uses setState()
|
||||
|
||||
### Dependency Injection
|
||||
- [ ] All dependencies injected via constructor
|
||||
- [ ] No direct instantiation of dependencies
|
||||
- [ ] Swinject container properly configured
|
||||
- [ ] Dependencies are protocol-based (not concrete types)
|
||||
|
||||
### Data Flow
|
||||
- [ ] UI → ViewController → ViewModel → UseCase → Repository → API/DB
|
||||
- [ ] Never skips layers
|
||||
- [ ] Observables/Singles for async operations
|
||||
- [ ] Errors properly propagated through layers
|
||||
```
|
||||
Reference in New Issue
Block a user