Lapa Knowledge Base
Веб-архитектуры

Монолитная архитектура

Монолитная архитектура приложений

Что такое монолитная архитектура?

Монолитная архитектура — это традиционный подход к разработке программного обеспечения, при котором все компоненты приложения объединены в единое развертываемое приложение. Все функции, модули и компоненты работают как единое целое в рамках одного процесса.

Основные характеристики

Структура монолита

┌─────────────────────────────────────┐
│           Монолитное приложение     │
├─────────────────────────────────────┤
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│  │   UI    │ │Business │ │Database │ │
│  │ Layer   │ │ Logic   │ │ Layer   │ │
│  └─────────┘ └─────────┘ └─────────┘ │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│  │   API   │ │Services │ │   ORM   │ │
│  │ Layer   │ │ Layer   │ │ Layer   │ │
│  └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────┘

Ключевые особенности:

  • Единое развертывание — все компоненты развертываются вместе
  • Общая база данных — все модули используют одну БД
  • Единый процесс — приложение работает как один процесс
  • Общие ресурсы — память, CPU, файловая система
  • Единый язык — обычно один технологический стек

Типы монолитной архитектуры

1. Простой монолит

// Все в одном файле/модуле
class SimpleMonolith {
    constructor() {
        this.users = [];
        this.products = [];
        this.orders = [];
    }
    
    // Все методы в одном классе
    createUser(userData) { /* ... */ }
    createProduct(productData) { /* ... */ }
    createOrder(orderData) { /* ... */ }
}

2. Модульный монолит

// Разделение на модули, но единое развертывание
// users/userService.js
class UserService {
    createUser(userData) { /* ... */ }
    getUserById(id) { /* ... */ }
}

// products/productService.js
class ProductService {
    createProduct(productData) { /* ... */ }
    getProductById(id) { /* ... */ }
}

// orders/orderService.js
class OrderService {
    createOrder(orderData) { /* ... */ }
    getOrderById(id) { /* ... */ }
}

// main.js - все модули в одном приложении
const userService = new UserService();
const productService = new ProductService();
const orderService = new OrderService();

3. Монолит с разделением слоев

// Четкое разделение на слои
// presentation/controllers/userController.js
class UserController {
    constructor(userService) {
        this.userService = userService;
    }
    
    async createUser(req, res) {
        const user = await this.userService.createUser(req.body);
        res.json(user);
    }
}

// business/services/userService.js
class UserService {
    constructor(userRepository) {
        this.userRepository = userRepository;
    }
    
    async createUser(userData) {
        // Бизнес-логика
        const user = await this.userRepository.save(userData);
        return user;
    }
}

// data/repositories/userRepository.js
class UserRepository {
    async save(userData) {
        // Работа с БД
        return await db.users.create(userData);
    }
}

Преимущества монолитной архитектуры

Простота разработки

  • Единая кодовая база — все в одном месте
  • Простое тестирование — можно тестировать все сразу
  • Быстрая разработка — нет сложности межсервисного взаимодействия

Простота развертывания

  • Одно приложение — развертывается как единое целое
  • Простая инфраструктура — один сервер, одна БД
  • Минимум конфигурации — меньше настроек

Производительность

  • Нет сетевых вызовов — все в памяти
  • Быстрые транзакции — локальные вызовы
  • Оптимизация запросов — можно оптимизировать БД запросы

Отладка и мониторинг

  • Простая отладка — все в одном процессе
  • Единый лог — все логи в одном месте
  • Простой мониторинг — один процесс для отслеживания

Недостатки монолитной архитектуры

Сложность масштабирования

  • Вертикальное масштабирование — только увеличение ресурсов сервера
  • Нельзя масштабировать части — все приложение целиком
  • Ограниченная гибкость — сложно оптимизировать отдельные части

Технологические ограничения

  • Единый технологический стек — сложно использовать разные технологии
  • Связанность компонентов — изменения в одной части влияют на другие
  • Сложность обновлений — обновление требует перезапуска всего приложения

Командная работа

  • Конфликты в коде — несколько разработчиков работают с одним кодом
  • Сложность параллельной разработки — мешают друг другу
  • Единая точка отказа — падение одной части ломает все приложение

Когда использовать монолитную архитектуру

Идеальные случаи:

Небольшие команды (1-5 разработчиков):

  • Простота координации
  • Быстрая разработка
  • Минимум сложности

Простые приложения:

  • CRUD операции
  • Небольшая бизнес-логика
  • Стандартные требования

Начальные этапы проекта:

  • Быстрый MVP
  • Итеративная разработка
  • Прототипирование

Стабильные требования:

  • Четко определенный функционал
  • Редкие изменения
  • Предсказуемая нагрузка

Критерии выбора:

Вставить таблицу!


Примеры реализации

Node.js + Express монолит

// app.js - основной файл приложения
const express = require('express');
const mongoose = require('mongoose');

const app = express();

// Подключение к БД
mongoose.connect('mongodb://localhost:27017/monolith-app');

// Middleware
app.use(express.json());

// Routes
app.use('/api/users', require('./routes/users'));
app.use('/api/products', require('./routes/products'));
app.use('/api/orders', require('./routes/orders'));

// Запуск сервера
app.listen(3000, () => {
    console.log('Монолитное приложение запущено на порту 3000');
});
// routes/users.js
const express = require('express');
const User = require('../models/User');
const router = express.Router();

// GET /api/users
router.get('/', async (req, res) => {
    try {
        const users = await User.find();
        res.json(users);
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

// POST /api/users
router.post('/', async (req, res) => {
    try {
        const user = new User(req.body);
        await user.save();
        res.status(201).json(user);
    } catch (error) {
        res.status(400).json({ error: error.message });
    }
});

module.exports = router;
// models/User.js
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    name: { type: String, required: true },
    email: { type: String, required: true, unique: true },
    password: { type: String, required: true },
    createdAt: { type: Date, default: Date.now }
});

module.exports = mongoose.model('User', userSchema);

Java Spring Boot монолит

// UserController.java
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
    
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }
}
// UserService.java
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    public User createUser(User user) {
        return userRepository.save(user);
    }
}
// UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

Миграция от монолита

Стратегии миграции:

1. Strangler Fig Pattern:

// Постепенная замена функциональности
class LegacyMonolith {
    // Старая функциональность
    async getUser(id) {
        // Старый код
    }
}

class NewMicroservice {
    // Новая функциональность
    async getUser(id) {
        // Новый код
    }
}

class MigrationProxy {
    constructor(legacy, newService) {
        this.legacy = legacy;
        this.newService = newService;
    }
    
    async getUser(id) {
        // Постепенно переключаемся на новый сервис
        if (this.shouldUseNewService(id)) {
            return await this.newService.getUser(id);
        }
        return await this.legacy.getUser(id);
    }
}

2. Database per Service:

// Разделение БД по сервисам
// users-service/database/users.db
// products-service/database/products.db
// orders-service/database/orders.db

3. API Gateway:

// Единая точка входа для всех сервисов
class APIGateway {
    constructor() {
        this.userService = new UserService();
        this.productService = new ProductService();
        this.orderService = new OrderService();
    }
    
    async routeRequest(path, method, data) {
        if (path.startsWith('/users')) {
            return await this.userService.handleRequest(path, method, data);
        }
        if (path.startsWith('/products')) {
            return await this.productService.handleRequest(path, method, data);
        }
        if (path.startsWith('/orders')) {
            return await this.orderService.handleRequest(path, method, data);
        }
    }
}

Лучшие практики для монолитов

Организация кода:

1. Модульная структура:

src/
├── controllers/     # Контроллеры
├── services/       # Бизнес-логика
├── repositories/   # Работа с данными
├── models/         # Модели данных
├── middleware/     # Middleware
├── utils/          # Утилиты
└── config/         # Конфигурация

2. Dependency Injection:

// Внедрение зависимостей для тестируемости
class UserService {
    constructor(userRepository, emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
    
    async createUser(userData) {
        const user = await this.userRepository.save(userData);
        await this.emailService.sendWelcomeEmail(user.email);
        return user;
    }
}

3. Интерфейсы и абстракции:

// Абстракция для легкой замены
class IUserRepository {
    async save(user) { throw new Error('Not implemented'); }
    async findById(id) { throw new Error('Not implemented'); }
}

class MongoUserRepository extends IUserRepository {
    async save(user) { /* MongoDB реализация */ }
    async findById(id) { /* MongoDB реализация */ }
}

class PostgresUserRepository extends IUserRepository {
    async save(user) { /* PostgreSQL реализация */ }
    async findById(id) { /* PostgreSQL реализация */ }
}

Мониторинг и отладка

Инструменты мониторинга:

1. Логирование:

const winston = require('winston');

const logger = winston.createLogger({
    level: 'info',
    format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.json()
    ),
    transports: [
        new winston.transports.File({ filename: 'app.log' }),
        new winston.transports.Console()
    ]
});

// Использование
logger.info('User created', { userId: 123, email: 'user@example.com' });
logger.error('Database connection failed', { error: error.message });

2. Метрики производительности:

const prometheus = require('prom-client');

// Счетчики
const httpRequestsTotal = new prometheus.Counter({
    name: 'http_requests_total',
    help: 'Total number of HTTP requests',
    labelNames: ['method', 'route', 'status']
});

// Гистограммы
const httpRequestDuration = new prometheus.Histogram({
    name: 'http_request_duration_seconds',
    help: 'Duration of HTTP requests in seconds',
    labelNames: ['method', 'route']
});

// Middleware для сбора метрик
app.use((req, res, next) => {
    const start = Date.now();
    
    res.on('finish', () => {
        const duration = (Date.now() - start) / 1000;
        httpRequestsTotal.inc({
            method: req.method,
            route: req.route?.path || req.path,
            status: res.statusCode
        });
        httpRequestDuration.observe({
            method: req.method,
            route: req.route?.path || req.path
        }, duration);
    });
    
    next();
});

Заключение

Ключевые выводы:

Монолитная архитектура подходит для:

  • Небольших команд и проектов
  • Быстрой разработки MVP
  • Простых приложений с предсказуемыми требованиями
  • Начальных этапов развития продукта

Основные преимущества:

  • Простота разработки и развертывания
  • Высокая производительность
  • Легкая отладка и мониторинг

Основные недостатки:

  • Ограниченная масштабируемость
  • Технологические ограничения
  • Сложность командной работы

Рекомендации:

  • Начинайте с монолита для простых проектов
  • Используйте модульную архитектуру внутри монолита
  • Планируйте миграцию к микросервисам при росте
  • Инвестируйте в качество кода и тестирование

Помните: монолит — это не антипаттерн, а архитектурный выбор, который может быть оптимальным для определенных ситуаций!

Copyright © 2026