❓ Qué problema resuelve
- Acoplamiento con frameworks: Lógica de negocio mezclada con UI
- Difícil testing: Componentes complejos difíciles de probar
- Mantenibilidad: Cambios en UI afectan lógica de negocio
- Reutilización: Lógica no reutilizable entre diferentes interfaces
🔧 Cómo funciona
Separa la aplicación en capas concéntricas donde las dependencias apuntan hacia adentro, manteniendo la lógica de negocio independiente de frameworks y UI.
📊 Diagrama de Arquitectura
graph TB
subgraph "Clean Architecture - Frontend"
subgraph "External Layer"
UI[Components/Views]
API[HTTP Services]
STORAGE[Local Storage]
end
subgraph "Interface Adapters"
PRESENTER[Presenters]
CONTROLLER[Controllers]
GATEWAY[API Gateways]
end
subgraph "Use Cases Layer"
UC1[Login Use Case]
UC2[Get Users Use Case]
UC3[Create Order Use Case]
end
subgraph "Entities Layer"
USER[User Entity]
ORDER[Order Entity]
PRODUCT[Product Entity]
end
end
UI --> PRESENTER
PRESENTER --> UC1
PRESENTER --> UC2
PRESENTER --> UC3
UC1 --> USER
UC2 --> USER
UC3 --> ORDER
UC3 --> PRODUCT
UC1 --> GATEWAY
UC2 --> GATEWAY
UC3 --> GATEWAY
GATEWAY --> API
GATEWAY --> STORAGE
🏗️ Estructura de Carpetas Angular
src/app/
├── core/ # Entities & Use Cases
│ ├── entities/
│ │ ├── user.entity.ts
│ │ ├── order.entity.ts
│ │ └── product.entity.ts
│ ├── use-cases/
│ │ ├── auth/
│ │ │ ├── login.use-case.ts
│ │ │ └── logout.use-case.ts
│ │ ├── user/
│ │ │ ├── get-users.use-case.ts
│ │ │ └── create-user.use-case.ts
│ │ └── order/
│ │ ├── create-order.use-case.ts
│ │ └── get-orders.use-case.ts
│ └── repositories/ # Abstract repositories
│ ├── user.repository.ts
│ ├── order.repository.ts
│ └── auth.repository.ts
├── data/ # Interface Adapters - Data
│ ├── repositories/ # Repository implementations
│ │ ├── user-api.repository.ts
│ │ ├── order-api.repository.ts
│ │ └── auth-local.repository.ts
│ ├── services/ # HTTP Services
│ │ ├── user-api.service.ts
│ │ ├── order-api.service.ts
│ │ └── auth-api.service.ts
│ └── models/ # DTOs/API Models
│ ├── user-api.model.ts
│ ├── order-api.model.ts
│ └── auth-api.model.ts
├── presentation/ # Interface Adapters - UI
│ ├── pages/
│ │ ├── login/
│ │ │ ├── login.component.ts
│ │ │ ├── login.presenter.ts
│ │ │ └── login.component.html
│ │ ├── users/
│ │ │ ├── user-list.component.ts
│ │ │ ├── user-list.presenter.ts
│ │ │ ├── user-create.component.ts
│ │ │ └── user-create.presenter.ts
│ │ └── orders/
│ │ ├── order-list.component.ts
│ │ ├── order-list.presenter.ts
│ │ └── order-create.component.ts
│ ├── shared/
│ │ ├── components/
│ │ ├── pipes/
│ │ └── directives/
│ └── view-models/ # View Models
│ ├── user.view-model.ts
│ ├── order.view-model.ts
│ └── auth.view-model.ts
└── shared/ # Cross-cutting concerns
├── utils/
├── constants/
└── types/
📋 Implementación por Capas
🔵 Entities Layer
classDiagram
class User {
+id: string
+email: string
+name: string
+isActive: boolean
+validate(): ValidationResult
+isValidEmail(): boolean
}
class Order {
+id: string
+userId: string
+items: OrderItem[]
+total: number
+status: OrderStatus
+calculateTotal(): number
+canBeCancelled(): boolean
}
class Product {
+id: string
+name: string
+price: number
+stock: number
+isAvailable(): boolean
}
🟢 Use Cases Layer
classDiagram
class LoginUseCase {
-authRepository: AuthRepository
+execute(email, password): Promise~AuthResult~
}
class GetUsersUseCase {
-userRepository: UserRepository
+execute(filters): Promise~User[]~
}
class CreateOrderUseCase {
-orderRepository: OrderRepository
-userRepository: UserRepository
+execute(orderData): Promise~Order~
}
class AuthRepository {
<<interface>>
+login(credentials): Promise~AuthResult~
+logout(): Promise~void~
+getCurrentUser(): Promise~User~
}
class UserRepository {
<<interface>>
+getAll(filters): Promise~User[]~
+getById(id): Promise~User~
+create(user): Promise~User~
}
LoginUseCase --> AuthRepository
GetUsersUseCase --> UserRepository
CreateOrderUseCase --> UserRepository
🟡 Interface Adapters Layer
classDiagram
class UserListPresenter {
-getUsersUseCase: GetUsersUseCase
+users$: Observable~UserViewModel[]~
+loading$: Observable~boolean~
+loadUsers(filters): void
+refreshUsers(): void
}
class UserApiRepository {
-userApiService: UserApiService
+getAll(filters): Promise~User[]~
+getById(id): Promise~User~
+create(user): Promise~User~
}
class UserViewModel {
+id: string
+displayName: string
+email: string
+statusLabel: string
+canEdit: boolean
}
UserListPresenter --> GetUsersUseCase
UserApiRepository --> UserApiService
🔴 External Layer (Components)
classDiagram
class UserListComponent {
-presenter: UserListPresenter
+users$: Observable~UserViewModel[]~
+loading$: Observable~boolean~
+ngOnInit(): void
+onRefresh(): void
+onFilterChange(filters): void
}
class UserApiService {
-http: HttpClient
+getUsers(filters): Observable~UserApiModel[]~
+getUserById(id): Observable~UserApiModel~
+createUser(user): Observable~UserApiModel~
}
UserListComponent --> UserListPresenter
UserApiService --> HttpClient
🔄 Flujo de Datos
sequenceDiagram
participant C as Component
participant P as Presenter
participant UC as Use Case
participant R as Repository
participant API as API Service
C->>P: loadUsers()
P->>UC: execute(filters)
UC->>R: getAll(filters)
R->>API: getUsers(filters)
API-->>R: UserApiModel[]
R-->>UC: User[]
UC-->>P: User[]
P-->>C: UserViewModel[]
C->>C: updateView()
🎯 Beneficios de Clean Architecture
✅ Ventajas
- Testabilidad: Cada capa se puede probar independientemente
- Independencia de Framework: Lógica no depende de Angular
- Flexibilidad: Fácil cambiar implementaciones
- Mantenibilidad: Separación clara de responsabilidades
- Reutilización: Use cases reutilizables
📊 Comparación con Arquitectura Tradicional
graph TB
subgraph "❌ Arquitectura Tradicional"
A[Component] --> B[Service]
B --> C[HTTP Client]
A --> D[Business Logic]
A --> E[Validation]
A --> F[State Management]
end
subgraph "✅ Clean Architecture"
G[Component] --> H[Presenter]
H --> I[Use Case]
I --> J[Repository Interface]
J --> K[Repository Implementation]
K --> L[API Service]
end
🧪 Testing Strategy
Unit Tests por Capa
graph LR
subgraph "Testing Layers"
A[Entity Tests] --> B[Use Case Tests]
B --> C[Presenter Tests]
C --> D[Component Tests]
E[Repository Tests] --> F[API Service Tests]
end
subgraph "Test Types"
G[Unit Tests]
H[Integration Tests]
I[E2E Tests]
end
🔧 Implementación Práctica
Dependency Injection Setup
// app.module.ts
providers: [
// Use Cases
{ provide: LoginUseCase, useClass: LoginUseCase },
{ provide: GetUsersUseCase, useClass: GetUsersUseCase },
// Repositories
{ provide: 'AuthRepository', useClass: AuthApiRepository },
{ provide: 'UserRepository', useClass: UserApiRepository },
// Presenters
UserListPresenter,
LoginPresenter
]
Feature Module Structure
// user.module.ts
@NgModule({
declarations: [
UserListComponent,
UserCreateComponent
],
providers: [
GetUsersUseCase,
CreateUserUseCase,
UserListPresenter,
UserCreatePresenter,
{ provide: 'UserRepository', useClass: UserApiRepository }
]
})
export class UserModule { }
🎨 Patrones Complementarios
MVVM (Model-View-ViewModel)
classDiagram
class View {
+template: HTML
+bindings: Data Binding
}
class ViewModel {
+properties: Observable
+commands: Methods
+state: ViewState
}
class Model {
+entities: Domain Objects
+useCases: Business Logic
}
View --> ViewModel
ViewModel --> Model
Repository Pattern
classDiagram
class Repository {
<<interface>>
+getAll(): Promise~T[]~
+getById(id): Promise~T~
+create(entity): Promise~T~
+update(entity): Promise~T~
+delete(id): Promise~void~
}
class ApiRepository {
+getAll(): Promise~T[]~
+getById(id): Promise~T~
+create(entity): Promise~T~
}
class LocalRepository {
+getAll(): Promise~T[]~
+getById(id): Promise~T~
+create(entity): Promise~T~
}
Repository <|.. ApiRepository
Repository <|.. LocalRepository
📱 Adaptación para Otros Frameworks
React
src/
├── core/ # Same as Angular
├── data/ # Same as Angular
├── presentation/
│ ├── hooks/ # Custom hooks (Presenters)
│ ├── components/ # React Components
│ └── pages/ # Page Components
└── shared/
Vue.js
src/
├── core/ # Same as Angular
├── data/ # Same as Angular
├── presentation/
│ ├── composables/ # Vue Composables (Presenters)
│ ├── components/ # Vue Components
│ └── views/ # Page Views
└── shared/