Что такое архитектура приложения?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектура приложения — это набор высокоуровневых решений об организации кода, структуре проекта и взаимодействии компонентов. Хорошая архитектура делает код поддерживаемым, масштабируемым и понятным для других разработчиков.
Зачем нужна архитектура
Представьте, что вы строите дом:
- Без архитектуры — стены в случайных местах, окна не подходят к стене, провода везде
- С архитектурой — план, каждая комната на своём месте, все провода в стене
То же самое с кодом.
Основные паттерны архитектуры
1. MVC (Model-View-Controller)
Класс шаблон для веб-приложений, разделяющий ответственность:
┌──────────────┐
│ USER │
└──────┬───────┘
│
▼
┌──────────────┐
│ CONTROLLER │ (обрабатывает запрос)
│ │
└──────┬───────┘
│
▼
┌──────────────┐
│ MODEL │ (бизнес логика, БД)
│ │
└──────┬───────┘
│
▼
┌──────────────┐
│ VIEW │ (ответ пользователю)
│ │
└──────┬───────┘
│
▼
┌──────────────┐
│ USER │
└──────────────┘
Пример на Express:
// routes.js (Controller)
router.get('/users/:id', userController.getUser);
// controllers/userController.js
exports.getUser = async (req, res) => {
const user = await User.findById(req.params.id); // Model
res.render('user', { user }); // View
};
// models/User.js
const userSchema = new Schema({ name: String, email: String });
2. Layered Architecture (Слойная архитектура)
Код организуется слоями, где каждый слой имеет чёткую ответственность:
┌─────────────────────────────────┐
│ Presentation Layer │ (контроллеры, HTTP responses)
├─────────────────────────────────┤
│ Application/Business Layer │ (use cases, бизнес логика)
├─────────────────────────────────┤
│ Domain/Service Layer │ (доменные модели, интерфейсы)
├─────────────────────────────────┤
│ Data/Infrastructure Layer │ (БД, внешние API, кеш)
└─────────────────────────────────┘
Правило: зависимости должны идти только вниз (сверху к низу).
// Presentation Layer
@Post('/users')
creatUser(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto); // вызов слоя ниже
}
// Application/Business Layer
@Injectable()
export class UserService {
create(dto: CreateUserDto) {
// бизнес-логика
return this.userRepository.save(dto); // вызов слоя ниже
}
}
// Data/Infrastructure Layer
@Injectable()
export class UserRepository {
save(user: User) {
return database.query('INSERT ...');
}
}
3. DDD (Domain-Driven Design)
Фокус на доменной логике, а не на техническом стеке:
┌─────────────────────────────────┐
│ User Interface (HTTP API) │
├─────────────────────────────────┤
│ Application Services │ (команды, запросы)
│ (CreateUserCommand) │
├─────────────────────────────────┤
│ Domain Models & Services │ (User, UserService)
│ (Entities, Value Objects) │
├─────────────────────────────────┤
│ Infrastructure │ (Repository, Database)
└─────────────────────────────────┘
Структура папок:
src/
├── modules/
│ └── users/
│ ├── application/ (use cases)
│ ├── domain/ (entities, value objects)
│ ├── infrastructure/ (repository, database)
│ └── presentation/ (controllers)
4. Clean Architecture (Чистая архитектура)
Полная изоляция бизнес-логики от деталей реализации:
┌────────────────────────────────┐
│ Enterprise Business Rules │ (User, Product entities)
├────────────────────────────────┤
│ Application Business Rules │ (use cases, services)
├────────────────────────────────┤
│ Interface Adapters │ (controllers, presenters)
├────────────────────────────────┤
│ Frameworks & Drivers │ (web, DB, external APIs)
└────────────────────────────────┘
Ключевой принцип: зависимости должны указывать в центр (внутрь).
Практический пример: простое приложение
// 1. DOMAIN LAYER - бизнес логика
class User {
constructor(id, email, password) {
this.id = id;
this.email = email;
this.password = password;
}
isPasswordValid(plainPassword) {
return bcrypt.compareSync(plainPassword, this.password);
}
}
// 2. SERVICE LAYER - бизнес сценарии
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async register(email, password) {
if (await this.userRepository.findByEmail(email)) {
throw new Error('User already exists');
}
const hashedPassword = bcrypt.hashSync(password, 10);
return this.userRepository.save(new User(uuid(), email, hashedPassword));
}
async login(email, password) {
const user = await this.userRepository.findByEmail(email);
if (!user || !user.isPasswordValid(password)) {
throw new Error('Invalid credentials');
}
return user;
}
}
// 3. INFRASTRUCTURE LAYER - работа с данными
class UserRepository {
async findByEmail(email) {
return database.query('SELECT * FROM users WHERE email = ?', [email]);
}
async save(user) {
return database.query(
'INSERT INTO users (id, email, password) VALUES (?, ?, ?)',
[user.id, user.email, user.password]
);
}
}
// 4. PRESENTATION LAYER - обработка HTTP
router.post('/register', async (req, res) => {
try {
const user = await userService.register(req.body.email, req.body.password);
res.json({ success: true, user });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
Выбор архитектуры
| Архитектура | Когда использовать | Сложность |
|---|---|---|
| MVC | Простые приложения | Низкая |
| Layered | Средние CRUD приложения | Средняя |
| DDD | Сложная бизнес логика | Высокая |
| Clean | Большие масштабируемые системы | Очень высокая |
SOLID принципы в архитектуре
- S — Single Responsibility — один класс, одна ответственность
- O — Open/Closed — открыто для расширения, закрыто для модификации
- L — Liskov Substitution — наследники заменяемы родителями
- I — Interface Segregation — много маленьких интерфейсов лучше одного большого
- D — Dependency Inversion — зависимости от абстракций, а не реализаций
Вывод
Хорошая архитектура:
- Облегчает тестирование
- Упрощает добавление новых фич
- Делает код понятнее
- Облегчает работу в команде
- Снижает технический долг
Не усложняйте архитектуру без причины, но и не игнорируйте её полностью. Выбирайте архитектуру, соответствующую размеру и сложности проекта.