← Назад к вопросам
Что такое принцип чистой архитектуры?
2.4 Senior🔥 191 комментариев
#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Clean Architecture: чистая архитектура
Определение
Clean Architecture это методология проектирования, разработанная Робертом Мартином (Uncle Bob). Цель: создавать системы которые:
- Независимы от фреймворков
- Тестируемы
- Независимы от UI
- Независимы от БД
- Независимы от любых external agency
Слои архитектуры
┌──────────────────────────────────┐
│ Frameworks & Drivers │ (Web, DB, UI)
├──────────────────────────────────┤
│ Interface Adapters │ (Controllers, Presenters)
├──────────────────────────────────┤
│ Application Business Rules │ (Use Cases)
├──────────────────────────────────┤
│ Enterprise Business Rules │ (Entities, Domain)
└──────────────────────────────────┘
4 Слоя
1. Entities (Сущности) Бизнес-правила уровня предприятия. Независимы от приложения.
class User {
id: number;
email: string;
password: string; // хэшированный
isValidPassword(plain: string): boolean {
return bcrypt.compareSync(plain, this.password);
}
}
2. Use Cases (Сценарии использования) Бизнес-правила приложения. Описывают что может делать приложение.
class LoginUseCase {
execute(email: string, password: string) {
const user = this.userRepo.findByEmail(email);
if (!user) throw new Error('User not found');
if (!user.isValidPassword(password)) throw new Error('Invalid password');
return { userId: user.id };
}
}
3. Interface Adapters (Адаптеры интерфейсов) Конвертируют между слоями. Controllers, Presenters, Gateways.
@Controller('/auth')
class LoginController {
@Post('/login')
async login(@Body() dto: LoginDto) {
const result = await this.loginUseCase.execute(dto.email, dto.password);
return { token: this.jwtService.sign(result) };
}
}
4. Frameworks & Drivers (Фреймворки и драйверы) Веб, БД, UI. Детали реализации.
// Express, NestJS, Database drivers
const app = express();
app.post('/auth/login', loginController.handle);
Правило зависимостей (Dependency Rule)
Все зависимости должны указывать внутрь:
Outer layers CAN depend on inner layers
Inner layers CANNOT depend on outer layers
✅ Controllers → Use Cases → Entities
❌ Entities → Controllers (запрещено!)
Структура проекта
src/
├── domain/ # Entities, Business Rules
│ ├── entities/
│ │ └── User.ts
│ └── interfaces/
│ └── UserRepository.ts
│
├── application/ # Use Cases
│ ├── usecases/
│ │ └── LoginUseCase.ts
│ └── dtos/
│ └── LoginDto.ts
│
├── infrastructure/ # Adapters, External Services
│ ├── repositories/
│ │ └── UserRepository.ts (реализация)
│ └── services/
│ └── JwtService.ts
│
└── presentation/ # Controllers, Views
├── controllers/
│ └── LoginController.ts
└── routes/
└── auth.routes.ts
DI Container
// Инверсия управления (IoC)
const loginUseCase = new LoginUseCase(
userRepository, // Dependency Injection
passwordHasher
);
const loginController = new LoginController(
loginUseCase
);
Пример: Полный Use Case
// domain/entities/User.ts
class User {
constructor(id: number, email: string, passwordHash: string) {
this.id = id;
this.email = email;
this.passwordHash = passwordHash;
}
isValidPassword(plain: string): boolean {
return bcrypt.compareSync(plain, this.passwordHash);
}
}
// domain/interfaces/UserRepository.ts
interface IUserRepository {
findByEmail(email: string): Promise<User | null>;
save(user: User): Promise<void>;
}
// application/usecases/LoginUseCase.ts
class LoginUseCase {
constructor(private userRepository: IUserRepository) {}
async execute(email: string, password: string) {
const user = await this.userRepository.findByEmail(email);
if (!user) throw new Error('User not found');
if (!user.isValidPassword(password)) throw new Error('Invalid credentials');
return { userId: user.id };
}
}
// infrastructure/repositories/UserRepository.ts
class UserRepository implements IUserRepository {
constructor(private db: Database) {}
async findByEmail(email: string): Promise<User | null> {
const record = await this.db.query('SELECT * FROM users WHERE email = ?', [email]);
return record ? new User(record.id, record.email, record.password_hash) : null;
}
async save(user: User): Promise<void> {
await this.db.query('INSERT INTO users VALUES (?, ?, ?)', [user.id, user.email, user.passwordHash]);
}
}
// presentation/controllers/LoginController.ts
@Controller('/auth')
class LoginController {
constructor(private loginUseCase: LoginUseCase) {}
@Post('/login')
async handle(@Body() dto: { email: string; password: string }) {
const result = await this.loginUseCase.execute(dto.email, dto.password);
return { token: jwt.sign({ userId: result.userId }) };
}
}
Тестирование в Clean Architecture
// Легко тестировать Use Cases
class MockUserRepository implements IUserRepository {
async findByEmail(email: string) {
return new User(1, email, 'hashed_password');
}
}
test('LoginUseCase should return userId', async () => {
const mockRepo = new MockUserRepository();
const useCase = new LoginUseCase(mockRepo);
const result = await useCase.execute('test@example.com', 'password');
expect(result.userId).toBe(1);
});
Плюсы
✅ Независимость от фреймворков
✅ Тестируемость
✅ Простота изменения деталей
✅ Четкое разделение ответственности
✅ Переиспользуемый бизнес-код
Минусы
❌ Более сложный setup
❌ Больше файлов и классов
❌ Learning curve
❌ Может быть over-engineering для маленьких проектов
Когда использовать
✅ Для больших проектов
✅ Долгосрочная разработка
✅ Команда разработчиков
✅ Нужна высокая testability
❌ Маленькие скрипты
❌ MVP и prototypes
❌ Один разработчик, простой проект