← Назад к вопросам

Что такое Clean Architecture?

3.0 Senior🔥 201 комментариев
#REST API и микросервисы#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Clean Architecture: принципы и реализация

Clean Architecture — это архитектурный подход, предложенный Робертом Мартином (Uncle Bob), который обеспечивает создание систем, которые легко тестировать, изменять и поддерживать. Основная идея: изолировать бизнес-логику от деталей реализации.

Концентрические слои архитектуры

Clean Architecture представляет систему как набор концентрических слоёв:

┌─────────────────────────────────────────┐
│   Frameworks & Drivers (Presentation)   │  ← Web, UI, Controllers
├─────────────────────────────────────────┤
│   Interface Adapters (Gateways)         │  ← Controllers, Presenters
├─────────────────────────────────────────┤
│   Application Business Rules (Use Cases)│  ← Services, Interactors
├─────────────────────────────────────────┤
│   Enterprise Business Rules (Entities)  │  ← Domain, Models
└─────────────────────────────────────────┘

Правило зависимостей: The Dependency Rule

Критическое правило: Зависимости ВСЕГДА направлены ВНУТРЬ (к ядру).

  • Внешние слои могут зависеть от внутренних
  • Внутренние слои НЕ знают о внешних
  • Бизнес-логика независима от фреймворков
// ✗ НЕПРАВИЛЬНО: внутренний слой зависит от внешнего
package com.example.domain;
import org.springframework.web.bind.annotation.*;  // ❌ Framework в domain!

public class User {
    // ...
}

// ✓ ПРАВИЛЬНО: слои зависят внутрь
package com.example.domain;
public class User {  // Чистая бизнес-логика
    private String name;
    private String email;
    // Ноль зависимостей от framework
}

Четыре слоя Clean Architecture

1. Entities (Domain Layer) - ЯДРО

// Используется по всей системе
// Ноль зависимостей от framework
public class User {
    private Long id;
    private String email;
    private String password;
    
    public boolean isValidEmail() {
        return email != null && email.contains("@");
    }
    
    public void setEmail(String newEmail) {
        if (!isValidEmail()) {
            throw new InvalidEmailException();
        }
        this.email = newEmail;
    }
}

2. Use Cases (Application Business Rules)

// Бизнес-сценарии
// Использует entities
// НЕ знает о базе данных, фреймворке

public class CreateUserUseCase {
    private final UserRepository repository;
    private final EmailService emailService;
    
    public CreateUserUseCase(UserRepository repo, EmailService email) {
        this.repository = repo;
        this.emailService = email;
    }
    
    public User execute(CreateUserRequest request) {
        // Бизнес-логика
        User user = new User();
        user.setEmail(request.getEmail());
        
        if (repository.existsByEmail(user.getEmail())) {
            throw new UserAlreadyExistsException();
        }
        
        User saved = repository.save(user);
        emailService.sendWelcome(saved);  // Асинхронно
        return saved;
    }
}

3. Interface Adapters (Gateways)

// Конвертирует данные между слоями
// Реализует контракты (interfaces) для repository, gateway

@Repository
public class UserRepositoryImpl implements UserRepository {
    @Autowired
    private JpaUserRepository jpaRepo;  // Spring Data (деталь реализации)
    
    @Override
    public User save(User user) {
        UserEntity entity = new UserEntity();
        entity.setEmail(user.getEmail());
        UserEntity saved = jpaRepo.save(entity);
        return toDomain(saved);
    }
    
    @Override
    public boolean existsByEmail(String email) {
        return jpaRepo.existsByEmail(email);
    }
    
    private User toDomain(UserEntity entity) {
        User user = new User();
        user.setId(entity.getId());
        user.setEmail(entity.getEmail());
        return user;
    }
}

4. Frameworks & Drivers (Presentation)

// Зависит ОТ ВСЕХ внутренних слоёв
// Фреймворк может меняться

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private CreateUserUseCase createUserUseCase;  // Бизнес-логика
    
    @PostMapping
    public ResponseEntity<UserResponse> createUser(@RequestBody CreateUserRequest request) {
        User user = createUserUseCase.execute(request);  // Вызываем use case
        return ResponseEntity.ok(new UserResponse(user));
    }
}

Полный пример: структура проекта

src/main/java/com/example/
├── domain/
│   ├── User.java              ← Entities, чистая бизнес-логика
│   ├── UserRepository.java    ← Interface (контракт)
│   └── exceptions/
│       └── InvalidEmailException.java
├── application/
│   ├── CreateUserUseCase.java ← Use cases
│   └── GetUserUseCase.java
├── adapters/
│   ├── repository/
│   │   ├── UserRepositoryImpl.java
│   │   └── JpaUserRepository.java  ← Spring Data (деталь!)
│   └── email/
│       └── EmailServiceAdapter.java
└── presentation/
    ├── UserController.java     ← REST контроллер
    └── dto/
        ├── CreateUserRequest.java
        └── UserResponse.java

Преимущества Clean Architecture

// 1. Тестируемость - легко мокировать зависимости
@Test
public void testCreateUser() {
    UserRepository mockRepo = mock(UserRepository.class);
    EmailService mockEmail = mock(EmailService.class);
    
    CreateUserUseCase useCase = new CreateUserUseCase(mockRepo, mockEmail);
    // Тестируем чистую бизнес-логику, БЕЗ БД и фреймворка
}

// 2. Заменяемость - можем менять реализацию
// Была PostgreSQL?
UserRepository repo = new PostgresUserRepository();
// Переходим на MongoDB?
UserRepository repo = new MongoUserRepository();
// Use case не меняется!

// 3. Независимость - бизнес-логика не зависит от фреймворка
// Можно использовать в консольном приложении, REST, GraphQL

SOLID принципы в Clean Architecture

// Single Responsibility
class CreateUserUseCase { }      // Только создание пользователя
class GetUserUseCase { }         // Только получение

// Open/Closed
interface UserRepository { }     // Закрыта для изменений
// Открыта для расширения: PostgreSQL, Mongo, Redis реализации

// Liskov Substitution
UserRepository repo = new PostgresUserRepository();  // Подставляемы
UserRepository repo = new MongoUserRepository();

// Interface Segregation
interface UserRepository {       // Только нужные методы
    User save(User user);
    User findById(Long id);
}

// Dependency Inversion
class CreateUserUseCase {
    private UserRepository repo;  // Зависит от абстракции
    // НЕ зависит от конкретной реализации
}

Частые ошибки

// ❌ ОШИБКА: Use case зависит от фреймворка
public class CreateUserUseCase {
    @Autowired  // ← Spring в use case!
    private UserRepository repo;
}

// ✓ ПРАВИЛЬНО: зависимости через конструктор
public class CreateUserUseCase {
    private final UserRepository repo;
    
    public CreateUserUseCase(UserRepository repo) {
        this.repo = repo;
    }
}

// ❌ ОШИБКА: Entity зависит от ORM
@Entity
@Table(name = "users")  // ← JPA в domain!
public class User { }

// ✓ ПРАВИЛЬНО: Entity чист
public class User {
    private Long id;
    private String email;
}

Clean Architecture обеспечивает долгосрочную maintainability и flexibility приложения, делая бизнес-логику независимой от выбора фреймворков и технологий.

Что такое Clean Architecture? | PrepBro