- Заменены HTML вкладки на нативные скрывающиеся блоки (details/summary) - Добавлены эмодзи иконки для каждого языка программирования - Удален CSS и JavaScript код для упрощения - Улучшена совместимость с любыми Markdown редакторами - Все примеры кода теперь в collapsible секциях
1405 lines
54 KiB
Markdown
1405 lines
54 KiB
Markdown
# Введение в архитектуру ПО
|
|
|
|
## Навигация
|
|
- [🏠 Главная страница курса](../../../README.md)
|
|
- [📚 Модуль 1](../README.md)
|
|
|
|
---
|
|
|
|
## Определение архитектуры ПО
|
|
|
|
**Архитектура программного обеспечения** — это структура системы, включающая:
|
|
- **Компоненты системы и их взаимодействие** — основные части системы и способы их связи
|
|
- **Внешние интерфейсы** — точки взаимодействия с внешними системами и пользователями
|
|
- **Ограничения и требования** — технические и бизнес-ограничения, которым должна соответствовать система
|
|
- **Принципы проектирования** — руководящие принципы для принятия архитектурных решений
|
|
|
|
> **💡 Важно**: Архитектура — это не только код, но и решения о том, как система будет развиваться, масштабироваться и поддерживаться.
|
|
|
|
## Ключевые характеристики архитектуры
|
|
|
|
1. **Структурность** — архитектура определяет структуру системы
|
|
- Организует компоненты в логические группы
|
|
- Определяет иерархию и зависимости между компонентами
|
|
- Создает основу для понимания системы
|
|
|
|
2. **Абстрактность** — скрывает детали реализации
|
|
- Фокусируется на "что" делает система, а не "как"
|
|
- Позволяет обсуждать систему на высоком уровне
|
|
- Упрощает понимание для разных ролей в команде
|
|
|
|
3. **Системность** — рассматривает систему как целое
|
|
- Учитывает взаимодействие всех компонентов
|
|
- Анализирует влияние изменений на всю систему
|
|
- Обеспечивает целостность архитектурных решений
|
|
|
|
4. **Эволюционность** — может изменяться со временем
|
|
- Адаптируется к изменяющимся требованиям
|
|
- Позволяет постепенное развитие системы
|
|
- Учитывает технический долг и рефакторинг
|
|
|
|
## Зачем нужна архитектура?
|
|
|
|
- **Управление сложностью** — разбиение сложной системы на понятные части
|
|
- Разделение ответственности между компонентами
|
|
- Создание четких границ и интерфейсов
|
|
- Упрощение понимания и поддержки системы
|
|
|
|
- **Коммуникация** — общий язык для обсуждения системы
|
|
- Единая терминология для команды разработки
|
|
- Визуальное представление системы через диаграммы
|
|
- Облегчение передачи знаний между разработчиками
|
|
|
|
- **Планирование** — основа для принятия технических решений
|
|
- Анализ влияния изменений на систему
|
|
- Оценка рисков и последствий решений
|
|
- Планирование развития и масштабирования
|
|
|
|
- **Переиспользование** — возможность использования компонентов в других проектах
|
|
- Создание переиспользуемых модулей
|
|
- Стандартизация подходов к решению задач
|
|
- Ускорение разработки новых проектов
|
|
|
|
## Архитектурные решения
|
|
|
|
**Архитектурное решение** — это выбор, который:
|
|
- **Влияет на структуру системы** — изменяет организацию компонентов и их взаимодействие
|
|
- **Имеет долгосрочные последствия** — последствия решения будут ощущаться месяцы и годы
|
|
- **Трудно изменить в будущем** — требует значительных усилий и времени для изменения
|
|
- **Влияет на качественные атрибуты** — производительность, масштабируемость, надежность, безопасность
|
|
|
|
> **⚠️ Важно**: Архитектурные решения должны приниматься осознанно, с пониманием всех последствий и альтернатив.
|
|
|
|
### Типы архитектурных решений:
|
|
|
|
1. **Структурные решения** — выбор архитектурных стилей и паттернов
|
|
2. **Технологические решения** — выбор технологий и инструментов
|
|
3. **Интеграционные решения** — способы взаимодействия с внешними системами
|
|
4. **Операционные решения** — подходы к развертыванию и мониторингу
|
|
|
|
## Анализ плюсов и минусов подходов
|
|
|
|
### 1. Монолит vs Микросервисы
|
|
|
|
#### Монолитная архитектура
|
|
|
|
**✅ Плюсы:**
|
|
- **Простота разработки** — один репозиторий, одна база данных, один процесс
|
|
- **Простота тестирования** — все компоненты в одном процессе
|
|
- **Простота развертывания** — один артефакт для развертывания
|
|
- **Производительность** — нет сетевых вызовов между компонентами
|
|
- **Консистентность данных** — ACID транзакции в рамках одной БД
|
|
- **Простота отладки** — все логи в одном месте
|
|
- **Быстрый старт** — идеально для MVP и небольших команд
|
|
|
|
**❌ Минусы:**
|
|
- **Сложность масштабирования** — нужно масштабировать весь монолит
|
|
- **Технологическая привязка** — сложно использовать разные технологии
|
|
- **Риск каскадных сбоев** — сбой одного компонента влияет на весь монолит
|
|
- **Сложность команд** — большие команды работают над одним кодом
|
|
- **Медленное развертывание** — изменение одного компонента требует пересборки всего
|
|
- **Технический долг** — сложно рефакторить большие монолиты
|
|
|
|
#### Микросервисная архитектура
|
|
|
|
**✅ Плюсы:**
|
|
- **Независимое масштабирование** — каждый сервис масштабируется отдельно
|
|
- **Технологическое разнообразие** — каждый сервис может использовать свою технологию
|
|
- **Независимые команды** — команды могут работать независимо
|
|
- **Отказоустойчивость** — сбой одного сервиса не влияет на другие
|
|
- **Быстрое развертывание** — можно развертывать сервисы независимо
|
|
- **Переиспользование** — сервисы можно переиспользовать в разных проектах
|
|
|
|
**❌ Минусы:**
|
|
- **Сложность разработки** — нужно управлять множеством сервисов
|
|
- **Сетевая задержка** — вызовы между сервисами по сети
|
|
- **Сложность тестирования** — нужно тестировать интеграцию между сервисами
|
|
- **Сложность отладки** — логи разбросаны по разным сервисам
|
|
- **Проблемы с данными** — сложно обеспечить консистентность между сервисами
|
|
- **Операционная сложность** — нужно управлять множеством сервисов в продакшене
|
|
|
|
#### Примеры реализации
|
|
|
|
<details>
|
|
<summary><strong>🔧 Java (Spring Boot)</strong></summary>
|
|
|
|
```java
|
|
// Монолитная архитектура
|
|
@SpringBootApplication
|
|
public class EcommerceApplication {
|
|
public static void main(String[] args) {
|
|
SpringApplication.run(EcommerceApplication.class, args);
|
|
}
|
|
}
|
|
|
|
// Все сервисы в одном приложении
|
|
@RestController
|
|
@RequestMapping("/api")
|
|
public class EcommerceController {
|
|
|
|
@Autowired
|
|
private UserService userService;
|
|
|
|
@Autowired
|
|
private OrderService orderService;
|
|
|
|
@Autowired
|
|
private PaymentService paymentService;
|
|
|
|
@PostMapping("/orders")
|
|
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
|
|
// Вся логика в одном месте
|
|
User user = userService.findById(request.getUserId());
|
|
Order order = orderService.createOrder(request);
|
|
PaymentResult payment = paymentService.processPayment(order);
|
|
return ResponseEntity.ok(order);
|
|
}
|
|
}
|
|
|
|
// Микросервисная архитектура
|
|
// User Service
|
|
@RestController
|
|
@RequestMapping("/api/users")
|
|
public class UserController {
|
|
@Autowired
|
|
private UserService userService;
|
|
|
|
@GetMapping("/{id}")
|
|
public ResponseEntity<User> getUser(@PathVariable Long id) {
|
|
User user = userService.findById(id);
|
|
return ResponseEntity.ok(user);
|
|
}
|
|
}
|
|
|
|
// Order Service
|
|
@RestController
|
|
@RequestMapping("/api/orders")
|
|
public class OrderController {
|
|
@Autowired
|
|
private OrderService orderService;
|
|
|
|
@PostMapping
|
|
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
|
|
Order order = orderService.createOrder(request);
|
|
return ResponseEntity.ok(order);
|
|
}
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>🦀 Rust (Actix Web)</strong></summary>
|
|
|
|
```rust
|
|
// Монолитная архитектура
|
|
use actix_web::{web, App, HttpServer, Result};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct User {
|
|
id: u64,
|
|
name: String,
|
|
email: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct Order {
|
|
id: u64,
|
|
user_id: u64,
|
|
status: String,
|
|
total: f64,
|
|
}
|
|
|
|
// Все сервисы в одном приложении
|
|
async fn create_order(
|
|
order_data: web::Json<CreateOrderRequest>,
|
|
user_service: web::Data<UserService>,
|
|
order_service: web::Data<OrderService>,
|
|
) -> Result<web::Json<Order>> {
|
|
// Вся логика в одном месте
|
|
let user = user_service.find_by_id(order_data.user_id).await?;
|
|
let order = order_service.create_order(order_data.into_inner()).await?;
|
|
Ok(web::Json(order))
|
|
}
|
|
|
|
// Микросервисная архитектура
|
|
// User Service
|
|
async fn get_user(
|
|
path: web::Path<u64>,
|
|
user_service: web::Data<UserService>,
|
|
) -> Result<web::Json<User>> {
|
|
let user = user_service.find_by_id(path.into_inner()).await?;
|
|
Ok(web::Json(user))
|
|
}
|
|
|
|
// Order Service
|
|
async fn create_order(
|
|
order_data: web::Json<CreateOrderRequest>,
|
|
order_service: web::Data<OrderService>,
|
|
) -> Result<web::Json<Order>> {
|
|
let order = order_service.create_order(order_data.into_inner()).await?;
|
|
Ok(web::Json(order))
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>🟨 JavaScript (Node.js)</strong></summary>
|
|
|
|
```javascript
|
|
// Монолитная архитектура
|
|
const fastify = require('fastify')({ logger: true });
|
|
|
|
// Все сервисы в одном приложении
|
|
fastify.post('/api/orders', async (request, reply) => {
|
|
// Вся логика в одном месте
|
|
const user = await userService.findById(request.body.userId);
|
|
const order = await orderService.createOrder(request.body);
|
|
const payment = await paymentService.processPayment(order);
|
|
return order;
|
|
});
|
|
|
|
// Микросервисная архитектура
|
|
// User Service
|
|
fastify.get('/api/users/:id', async (request, reply) => {
|
|
const user = await userService.findById(request.params.id);
|
|
return user;
|
|
});
|
|
|
|
// Order Service
|
|
fastify.post('/api/orders', async (request, reply) => {
|
|
const order = await orderService.createOrder(request.body);
|
|
return order;
|
|
});
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>🐍 Python (FastAPI)</strong></summary>
|
|
|
|
```python
|
|
# Монолитная архитектура
|
|
from fastapi import FastAPI, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
app = FastAPI()
|
|
|
|
# Все сервисы в одном приложении
|
|
@app.post("/api/orders")
|
|
async def create_order(request: CreateOrderRequest):
|
|
# Вся логика в одном месте
|
|
user = await user_service.find_by_id(request.user_id)
|
|
order = await order_service.create_order(request)
|
|
payment = await payment_service.process_payment(order)
|
|
return order
|
|
|
|
# Микросервисная архитектура
|
|
# User Service
|
|
@app.get("/api/users/{user_id}")
|
|
async def get_user(user_id: int):
|
|
user = await user_service.find_by_id(user_id)
|
|
return user
|
|
|
|
# Order Service
|
|
@app.post("/api/orders")
|
|
async def create_order(request: CreateOrderRequest):
|
|
order = await order_service.create_order(request)
|
|
return order
|
|
```
|
|
|
|
</details>
|
|
|
|
### 2. SQL vs NoSQL
|
|
|
|
#### SQL базы данных (PostgreSQL, MySQL)
|
|
|
|
**✅ Плюсы:**
|
|
- **ACID транзакции** — гарантированная консистентность данных
|
|
- **Сложные запросы** — мощный SQL для аналитики и отчетов
|
|
- **Схема данных** — четкая структура и валидация данных
|
|
- **Зрелость** — проверенные временем решения
|
|
- **Стандартизация** — SQL стандарт, легко мигрировать между БД
|
|
- **Инструменты** — богатая экосистема инструментов
|
|
- **Сообщество** — большое сообщество и документация
|
|
|
|
**❌ Минусы:**
|
|
- **Сложность масштабирования** — вертикальное масштабирование дорого
|
|
- **Схема данных** — сложно изменять структуру в больших системах
|
|
- **Производительность** — может быть медленнее для простых операций
|
|
- **Горизонтальное масштабирование** — сложно шардировать
|
|
- **Стоимость** — лицензии и ресурсы для больших систем
|
|
|
|
#### NoSQL базы данных (MongoDB, Cassandra)
|
|
|
|
**✅ Плюсы:**
|
|
- **Горизонтальное масштабирование** — легко масштабировать горизонтально
|
|
- **Гибкая схема** — можно изменять структуру данных без миграций
|
|
- **Производительность** — оптимизированы для конкретных паттернов доступа
|
|
- **Простота разработки** — документная модель близка к объектам в коде
|
|
- **Высокая доступность** — репликация из коробки
|
|
- **Стоимость** — часто дешевле для больших объемов данных
|
|
|
|
**❌ Минусы:**
|
|
- **Ограниченная консистентность** — eventual consistency вместо ACID
|
|
- **Сложные запросы** — ограниченные возможности для аналитики
|
|
- **Дублирование данных** — данные могут дублироваться между документами
|
|
- **Ограниченные транзакции** — сложно обеспечить ACID между документами
|
|
- **Специфичность** — каждая NoSQL БД имеет свои особенности
|
|
- **Обучение** — команде нужно изучать новые концепции
|
|
|
|
#### Примеры реализации
|
|
|
|
<details>
|
|
<summary><strong>🔧 Java (Spring Boot + JPA)</strong></summary>
|
|
|
|
```java
|
|
// SQL подход (PostgreSQL)
|
|
@Entity
|
|
@Table(name = "users")
|
|
public class User {
|
|
@Id
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
private Long id;
|
|
|
|
@Column(nullable = false)
|
|
private String name;
|
|
|
|
@Column(unique = true, nullable = false)
|
|
private String email;
|
|
|
|
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
|
|
private List<Order> orders = new ArrayList<>();
|
|
|
|
// ACID транзакции
|
|
@Transactional
|
|
public User createUserWithOrder(CreateUserRequest request) {
|
|
User user = new User();
|
|
user.setName(request.getName());
|
|
user.setEmail(request.getEmail());
|
|
user = userRepository.save(user);
|
|
|
|
Order order = new Order();
|
|
order.setUser(user);
|
|
order.setStatus("pending");
|
|
order.setTotal(0.0);
|
|
orderRepository.save(order);
|
|
|
|
return user;
|
|
}
|
|
}
|
|
|
|
// NoSQL подход (MongoDB)
|
|
@Document(collection = "users")
|
|
public class MongoUser {
|
|
@Id
|
|
private String id;
|
|
|
|
private String name;
|
|
private String email;
|
|
|
|
// Встроенные документы
|
|
private List<Order> orders = new ArrayList<>();
|
|
|
|
// Гибкая схема данных
|
|
private Map<String, Object> additionalProperties = new HashMap<>();
|
|
|
|
// Простая работа с данными
|
|
public void addOrder(Order order) {
|
|
this.orders.add(order);
|
|
}
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>🦀 Rust (SQLx)</strong></summary>
|
|
|
|
```rust
|
|
// SQL подход (PostgreSQL)
|
|
use sqlx::{PgPool, Row};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct User {
|
|
id: i64,
|
|
name: String,
|
|
email: String,
|
|
}
|
|
|
|
// ACID транзакции
|
|
async fn create_user_with_order(
|
|
pool: &PgPool,
|
|
request: CreateUserRequest,
|
|
) -> Result<User, sqlx::Error> {
|
|
let mut tx = pool.begin().await?;
|
|
|
|
// Создаем пользователя
|
|
let user = sqlx::query_as!(
|
|
User,
|
|
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email",
|
|
request.name,
|
|
request.email
|
|
)
|
|
.fetch_one(&mut *tx)
|
|
.await?;
|
|
|
|
// Создаем заказ
|
|
sqlx::query!(
|
|
"INSERT INTO orders (user_id, status, total) VALUES ($1, $2, $3)",
|
|
user.id,
|
|
"pending",
|
|
0.0
|
|
)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
tx.commit().await?;
|
|
Ok(user)
|
|
}
|
|
|
|
// NoSQL подход (MongoDB)
|
|
use mongodb::bson::{doc, Document};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct MongoUser {
|
|
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
|
id: Option<bson::oid::ObjectId>,
|
|
name: String,
|
|
email: String,
|
|
orders: Vec<Order>,
|
|
// Гибкая схема данных
|
|
additional_properties: Option<Document>,
|
|
}
|
|
|
|
// Простая работа с данными
|
|
async fn create_user_with_order(
|
|
collection: &mongodb::Collection<MongoUser>,
|
|
request: CreateUserRequest,
|
|
) -> Result<MongoUser, mongodb::error::Error> {
|
|
let user = MongoUser {
|
|
id: None,
|
|
name: request.name,
|
|
email: request.email,
|
|
orders: vec![Order {
|
|
id: None,
|
|
status: "pending".to_string(),
|
|
total: 0.0,
|
|
}],
|
|
additional_properties: None,
|
|
};
|
|
|
|
let result = collection.insert_one(&user, None).await?;
|
|
Ok(user)
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>🟨 JavaScript (Node.js)</strong></summary>
|
|
|
|
```javascript
|
|
// SQL подход (PostgreSQL)
|
|
const { Pool } = require('pg');
|
|
const mongoose = require('mongoose');
|
|
|
|
// Подключение к PostgreSQL
|
|
const pool = new Pool({
|
|
user: 'user',
|
|
host: 'localhost',
|
|
database: 'ecommerce',
|
|
password: 'password',
|
|
port: 5432,
|
|
});
|
|
|
|
// SQL модели (без ORM)
|
|
class User {
|
|
constructor(id, name, email) {
|
|
this.id = id;
|
|
this.name = name;
|
|
this.email = email;
|
|
}
|
|
}
|
|
|
|
// ACID транзакции
|
|
async function createUserWithOrderSQL(request) {
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
// Создаем пользователя
|
|
const userResult = await client.query(
|
|
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email',
|
|
[request.name, request.email]
|
|
);
|
|
const user = new User(userResult.rows[0].id, userResult.rows[0].name, userResult.rows[0].email);
|
|
|
|
// Создаем заказ
|
|
const orderResult = await client.query(
|
|
'INSERT INTO orders (user_id, status, total) VALUES ($1, $2, $3) RETURNING id, user_id, status, total',
|
|
[user.id, 'pending', 0]
|
|
);
|
|
const order = new Order(orderResult.rows[0].id, orderResult.rows[0].user_id, orderResult.rows[0].status, orderResult.rows[0].total);
|
|
|
|
await client.query('COMMIT');
|
|
return user;
|
|
} catch (error) {
|
|
await client.query('ROLLBACK');
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// NoSQL подход (MongoDB)
|
|
// Схема пользователя
|
|
const userSchema = new mongoose.Schema({
|
|
name: { type: String, required: true },
|
|
email: { type: String, required: true, unique: true },
|
|
orders: [{
|
|
id: String,
|
|
status: String,
|
|
total: Number
|
|
}],
|
|
// Гибкая схема данных
|
|
additionalProperties: { type: Map, of: mongoose.Schema.Types.Mixed }
|
|
});
|
|
|
|
const MongoUser = mongoose.model('User', userSchema);
|
|
|
|
// Простая работа с данными
|
|
async function createUserWithOrderMongo(request) {
|
|
const user = new MongoUser({
|
|
name: request.name,
|
|
email: request.email,
|
|
orders: [{
|
|
id: new mongoose.Types.ObjectId().toString(),
|
|
status: 'pending',
|
|
total: 0
|
|
}],
|
|
additionalProperties: new Map()
|
|
});
|
|
|
|
return await user.save();
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>🐍 Python (SQLAlchemy)</strong></summary>
|
|
|
|
```python
|
|
# SQL подход (PostgreSQL)
|
|
from sqlalchemy import create_engine, Column, Integer, String, Float, ForeignKey
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from sqlalchemy.orm import sessionmaker, relationship
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
|
|
Base = declarative_base()
|
|
|
|
class User(Base):
|
|
__tablename__ = 'users'
|
|
|
|
id = Column(Integer, primary_key=True)
|
|
name = Column(String(100), nullable=False)
|
|
email = Column(String(100), unique=True, nullable=False)
|
|
orders = relationship("Order", back_populates="user")
|
|
|
|
class Order(Base):
|
|
__tablename__ = 'orders'
|
|
|
|
id = Column(Integer, primary_key=True)
|
|
user_id = Column(Integer, ForeignKey('users.id'))
|
|
status = Column(String(50))
|
|
total = Column(Float)
|
|
user = relationship("User", back_populates="orders")
|
|
|
|
# ACID транзакции
|
|
def create_user_with_order_sql(request):
|
|
session = Session()
|
|
try:
|
|
session.begin()
|
|
|
|
# Создаем пользователя
|
|
user = User(name=request.name, email=request.email)
|
|
session.add(user)
|
|
session.flush() # Получаем ID
|
|
|
|
# Создаем заказ
|
|
order = Order(user_id=user.id, status='pending', total=0.0)
|
|
session.add(order)
|
|
|
|
session.commit()
|
|
return user
|
|
except SQLAlchemyError as e:
|
|
session.rollback()
|
|
raise e
|
|
finally:
|
|
session.close()
|
|
|
|
# NoSQL подход (MongoDB)
|
|
from pymongo import MongoClient
|
|
from bson import ObjectId
|
|
|
|
client = MongoClient('mongodb://localhost:27017/')
|
|
db = client['ecommerce']
|
|
|
|
# Простая работа с данными
|
|
def create_user_with_order_mongo(request):
|
|
user = {
|
|
'name': request.name,
|
|
'email': request.email,
|
|
'orders': [{
|
|
'id': str(ObjectId()),
|
|
'status': 'pending',
|
|
'total': 0
|
|
}],
|
|
# Гибкая схема данных
|
|
'additional_properties': {}
|
|
}
|
|
|
|
result = db.users.insert_one(user)
|
|
user['_id'] = result.inserted_id
|
|
return user
|
|
```
|
|
|
|
</details>
|
|
|
|
### 3. REST vs GraphQL
|
|
|
|
#### REST API
|
|
|
|
**✅ Плюсы:**
|
|
- **Простота** — понятный HTTP интерфейс
|
|
- **Кэширование** — HTTP кэширование работает из коробки
|
|
- **Стандартизация** — REST принципы понятны всем
|
|
- **Инструменты** — множество инструментов для тестирования и документации
|
|
- **Статус коды** — HTTP статус коды для обработки ошибок
|
|
- **Безопасность** — можно использовать стандартные HTTP методы аутентификации
|
|
|
|
**❌ Минусы:**
|
|
- **Over-fetching** — клиент получает больше данных чем нужно
|
|
- **Under-fetching** — нужно делать несколько запросов для получения связанных данных
|
|
- **Версионирование** — сложно версионировать API
|
|
- **N+1 проблема** — множественные запросы для связанных данных
|
|
- **Жесткость** — сложно адаптировать под потребности клиентов
|
|
|
|
#### GraphQL
|
|
|
|
**✅ Плюсы:**
|
|
- **Точные запросы** — клиент получает только нужные данные
|
|
- **Единая точка входа** — один endpoint для всех запросов
|
|
- **Типизация** — строгая типизация схемы
|
|
- **Интроспекция** — автоматическая документация API
|
|
- **Версионирование** — эволюция схемы без breaking changes
|
|
- **Реальные данные** — клиент запрашивает данные в нужном формате
|
|
|
|
**❌ Минусы:**
|
|
- **Сложность** — более сложный для понимания и реализации
|
|
- **Кэширование** — сложнее кэшировать чем REST
|
|
- **Производительность** — может быть медленнее для простых запросов
|
|
- **Безопасность** — сложнее контролировать доступ к данным
|
|
- **Обучение** — команде нужно изучать GraphQL
|
|
- **Инструменты** — меньше инструментов чем для REST
|
|
|
|
#### Примеры реализации
|
|
|
|
<details>
|
|
<summary><strong>🔧 Java (Spring Boot)</strong></summary>
|
|
|
|
```java
|
|
// REST API
|
|
@RestController
|
|
@RequestMapping("/api/users")
|
|
public class UserController {
|
|
@GetMapping("/{id}")
|
|
public ResponseEntity<User> getUser(@PathVariable Long id) {
|
|
User user = userService.findById(id);
|
|
return ResponseEntity.ok(user);
|
|
}
|
|
|
|
@GetMapping("/{id}/orders")
|
|
public ResponseEntity<List<Order>> getUserOrders(@PathVariable Long id) {
|
|
List<Order> orders = orderService.findByUserId(id);
|
|
return ResponseEntity.ok(orders);
|
|
}
|
|
}
|
|
|
|
// GraphQL
|
|
@Component
|
|
public class UserResolver implements GraphQLResolver<User> {
|
|
public List<Order> orders(User user) {
|
|
return orderService.findByUserId(user.getId());
|
|
}
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>🦀 Rust (Actix Web)</strong></summary>
|
|
|
|
```rust
|
|
// REST API
|
|
async fn get_user(path: web::Path<u64>) -> Result<web::Json<User>, actix_web::Error> {
|
|
let user = user_service.find_by_id(path.into_inner()).await?;
|
|
Ok(web::Json(user))
|
|
}
|
|
|
|
// GraphQL
|
|
#[juniper::object]
|
|
impl User {
|
|
fn orders(&self) -> Vec<Order> {
|
|
order_service.find_by_user_id(self.id)
|
|
}
|
|
}
|
|
```
|
|
|
|
**Node.js (Fastify + Mercurius)**
|
|
|
|
```javascript
|
|
// REST API
|
|
fastify.get('/api/users/:id', async (request, reply) => {
|
|
const user = await userService.findById(request.params.id);
|
|
return user;
|
|
});
|
|
|
|
// GraphQL
|
|
const resolvers = {
|
|
User: {
|
|
orders: async (user) => {
|
|
return await orderService.findByUserId(user.id);
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>🟨 JavaScript (Node.js)</strong></summary>
|
|
|
|
```javascript
|
|
// REST API
|
|
fastify.get('/api/users/:id', async (request, reply) => {
|
|
const user = await userService.findById(request.params.id);
|
|
return user;
|
|
});
|
|
|
|
// GraphQL
|
|
const resolvers = {
|
|
User: {
|
|
orders: async (user) => {
|
|
return await orderService.findByUserId(user.id);
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>🐍 Python (FastAPI + Strawberry)</strong></summary>
|
|
|
|
```python
|
|
# REST API
|
|
@app.get("/api/users/{user_id}")
|
|
async def get_user(user_id: int):
|
|
user = await user_service.find_by_id(user_id)
|
|
return user
|
|
|
|
# GraphQL
|
|
@strawberry.type
|
|
class User:
|
|
@strawberry.field
|
|
async def orders(self) -> List[Order]:
|
|
return await order_service.find_by_user_id(self.id)
|
|
```
|
|
|
|
</details>
|
|
|
|
### 4. Стратегии кэширования
|
|
|
|
#### Cache-Aside (Lazy Loading)
|
|
|
|
**Описание:** Приложение само управляет кэшем. При чтении сначала проверяется кэш, при записи обновляется и кэш, и основное хранилище.
|
|
|
|
**Как работает:**
|
|
1. **Чтение**: Проверить кэш → если есть, вернуть данные → если нет, загрузить из БД и сохранить в кэш
|
|
2. **Запись**: Записать в БД → обновить кэш
|
|
|
|
**✅ Плюсы:**
|
|
- **Простота реализации** — легко понять и реализовать
|
|
- **Гибкость** — можно кэшировать только нужные данные
|
|
- **Отказоустойчивость** — при сбое кэша система продолжает работать
|
|
- **Контроль** — полный контроль над тем, что кэшируется
|
|
- **Независимость** — кэш и БД работают независимо
|
|
|
|
**❌ Минусы:**
|
|
- **Cache miss penalty** — при промахе кэша нужно обращаться к БД
|
|
- **Сложность логики** — нужно управлять кэшем в коде приложения
|
|
- **Дублирование кода** — логика кэширования может дублироваться
|
|
- **Race conditions** — возможны состояния гонки при параллельных запросах
|
|
|
|
**Когда использовать:**
|
|
- Сложная логика кэширования
|
|
- Нужен полный контроль над кэшем
|
|
- Разные стратегии для разных типов данных
|
|
|
|
#### Write-Through
|
|
|
|
**Описание:** При записи данные сохраняются одновременно в кэш и в основное хранилище. При чтении данные всегда берутся из кэша.
|
|
|
|
**Как работает:**
|
|
1. **Чтение**: Всегда читать из кэша
|
|
2. **Запись**: Записать в БД → записать в кэш
|
|
|
|
**✅ Плюсы:**
|
|
- **Консистентность** — данные в кэше всегда актуальны
|
|
- **Простота чтения** — не нужно проверять кэш при чтении
|
|
- **Надежность** — данные сохраняются в БД сразу
|
|
- **Простота реализации** — логика записи простая
|
|
|
|
**❌ Минусы:**
|
|
- **Медленная запись** — каждая запись идет через БД
|
|
- **Производительность** — может быть медленнее для write-heavy нагрузок
|
|
- **Сложность** — нужно синхронизировать кэш и БД
|
|
- **Задержка записи** — запись ждет подтверждения от БД
|
|
|
|
**Когда использовать:**
|
|
- Критична консистентность данных
|
|
- Write-heavy нагрузка невысокая
|
|
- Простая логика кэширования
|
|
|
|
#### Write-Behind (Write-Back)
|
|
|
|
**Описание:** При записи данные сначала сохраняются в кэш, а затем асинхронно записываются в основное хранилище. При чтении данные берутся из кэша.
|
|
|
|
**Как работает:**
|
|
1. **Чтение**: Всегда читать из кэша
|
|
2. **Запись**: Записать в кэш → асинхронно записать в БД
|
|
|
|
**✅ Плюсы:**
|
|
- **Высокая производительность** — быстрая запись в кэш
|
|
- **Batch операции** — можно группировать записи в БД
|
|
- **Масштабируемость** — хорошо для write-heavy нагрузок
|
|
- **Отзывчивость** — пользователь не ждет записи в БД
|
|
|
|
**❌ Минусы:**
|
|
- **Риск потери данных** — данные могут быть потеряны при сбое
|
|
- **Сложность** — сложнее реализовать и отладить
|
|
- **Консистентность** — возможны временные несоответствия
|
|
- **Сложность отладки** — асинхронные операции сложнее отслеживать
|
|
|
|
**Когда использовать:**
|
|
- Высокая write-heavy нагрузка
|
|
- Можно допустить потерю небольшого количества данных
|
|
- Критична производительность записи
|
|
|
|
#### Refresh-Ahead
|
|
|
|
**Описание:** Кэш обновляется до истечения срока действия, чтобы избежать cache miss. Используется для данных с предсказуемым паттерном доступа.
|
|
|
|
**Как работает:**
|
|
1. **Чтение**: Читать из кэша
|
|
2. **Обновление**: Асинхронно обновлять кэш до истечения TTL
|
|
3. **Прогнозирование**: Анализировать паттерны доступа для предсказания
|
|
|
|
**✅ Плюсы:**
|
|
- **Минимум cache miss** — данные обновляются заранее
|
|
- **Высокая производительность** — пользователь всегда получает данные из кэша
|
|
- **Предсказуемость** — можно прогнозировать нагрузку
|
|
|
|
**❌ Минусы:**
|
|
- **Сложность реализации** — нужна логика прогнозирования
|
|
- **Избыточные обновления** — могут обновляться неиспользуемые данные
|
|
- **Ресурсы** — тратит ресурсы на обновление данных
|
|
|
|
**Когда использовать:**
|
|
- Предсказуемые паттерны доступа
|
|
- Критична производительность
|
|
- Можно предсказать, какие данные понадобятся
|
|
|
|
#### Write-Around
|
|
|
|
**Описание:** При записи данные записываются только в основное хранилище, минуя кэш. Кэш обновляется только при чтении.
|
|
|
|
**Как работает:**
|
|
1. **Чтение**: Проверить кэш → если есть, вернуть → если нет, загрузить из БД и сохранить в кэш
|
|
2. **Запись**: Записать только в БД
|
|
|
|
**✅ Плюсы:**
|
|
- **Простота записи** — не нужно обновлять кэш при записи
|
|
- **Экономия ресурсов** — не тратит ресурсы на обновление кэша
|
|
- **Простота реализации** — логика записи очень простая
|
|
|
|
**❌ Минусы:**
|
|
- **Cache miss** — после записи данные не будут в кэше
|
|
- **Производительность чтения** — может быть много cache miss
|
|
- **Неэффективность** — не использует кэш для записи
|
|
|
|
**Когда использовать:**
|
|
- Write-heavy нагрузка
|
|
- Данные редко читаются после записи
|
|
- Простая логика кэширования
|
|
|
|
#### Cache-Only
|
|
|
|
**Описание:** Все данные хранятся только в кэше, без основного хранилища. Подходит для временных данных или данных, которые можно восстановить.
|
|
|
|
**Как работает:**
|
|
1. **Чтение**: Всегда читать из кэша
|
|
2. **Запись**: Записать только в кэш
|
|
|
|
**✅ Плюсы:**
|
|
- **Максимальная производительность** — нет обращений к БД
|
|
- **Простота** — нет сложной логики синхронизации
|
|
- **Масштабируемость** — легко масштабировать кэш
|
|
|
|
**❌ Минусы:**
|
|
- **Риск потери данных** — данные могут быть потеряны при сбое
|
|
- **Ограниченный объем** — ограничен размером кэша
|
|
- **Временность** — данные могут быть удалены
|
|
|
|
**Когда использовать:**
|
|
- Временные данные
|
|
- Данные можно восстановить
|
|
- Критична производительность
|
|
- Ограниченный объем данных
|
|
|
|
#### Multi-Level Caching
|
|
|
|
**Описание:** Использование нескольких уровней кэширования (L1, L2, L3) с разными характеристиками производительности и объема.
|
|
|
|
**Как работает:**
|
|
1. **L1 Cache** — быстрый, небольшой (CPU cache, in-memory)
|
|
2. **L2 Cache** — средний по скорости и объему (Redis, Memcached)
|
|
3. **L3 Cache** — медленный, большой (Database, File system)
|
|
|
|
**✅ Плюсы:**
|
|
- **Оптимальная производительность** — баланс скорости и объема
|
|
- **Гибкость** — можно настроить под конкретные нужды
|
|
- **Масштабируемость** — можно добавлять уровни
|
|
|
|
**❌ Минусы:**
|
|
- **Сложность** — сложно реализовать и поддерживать
|
|
- **Синхронизация** — нужно синхронизировать между уровнями
|
|
- **Отладка** — сложно отлаживать многоуровневую систему
|
|
|
|
**Когда использовать:**
|
|
- Сложные системы с разными требованиями
|
|
- Нужен баланс производительности и объема
|
|
- Есть ресурсы на поддержку сложной системы
|
|
|
|
#### Cache-As-Sidecar
|
|
|
|
**Описание:** Кэш работает как отдельный сервис рядом с основным приложением, но в том же контейнере или процессе.
|
|
|
|
**Как работает:**
|
|
1. **Основное приложение** — обрабатывает бизнес-логику
|
|
2. **Sidecar кэш** — обрабатывает кэширование
|
|
3. **Коммуникация** — через локальные вызовы
|
|
|
|
**✅ Плюсы:**
|
|
- **Производительность** — локальные вызовы быстрее сетевых
|
|
- **Простота развертывания** — один контейнер
|
|
- **Изоляция** — кэш изолирован от основного приложения
|
|
|
|
**❌ Минусы:**
|
|
- **Сложность** — нужно управлять двумя процессами
|
|
- **Ресурсы** — тратит ресурсы на кэш-сервис
|
|
- **Масштабирование** — сложно масштабировать независимо
|
|
|
|
**Когда использовать:**
|
|
- Нужна высокая производительность кэша
|
|
- Простота развертывания важнее независимого масштабирования
|
|
- Ограниченные сетевые ресурсы
|
|
|
|
#### Примеры реализации
|
|
|
|
<details>
|
|
<summary><strong>🔧 Java (Spring Boot + Redis)</strong></summary>
|
|
|
|
```java
|
|
// Cache-Aside Pattern
|
|
@Service
|
|
public class UserService {
|
|
@Autowired
|
|
private RedisTemplate<String, Object> redisTemplate;
|
|
|
|
@Autowired
|
|
private UserRepository userRepository;
|
|
|
|
private static final String CACHE_KEY = "user:";
|
|
private static final long CACHE_TTL = 3600; // 1 час
|
|
|
|
public User findById(Long id) {
|
|
String cacheKey = CACHE_KEY + id;
|
|
|
|
// 1. Проверяем кэш
|
|
User cachedUser = (User) redisTemplate.opsForValue().get(cacheKey);
|
|
if (cachedUser != null) {
|
|
return cachedUser;
|
|
}
|
|
|
|
// 2. Загружаем из БД
|
|
User user = userRepository.findById(id);
|
|
if (user != null) {
|
|
// 3. Сохраняем в кэш
|
|
redisTemplate.opsForValue().set(cacheKey, user, CACHE_TTL, TimeUnit.SECONDS);
|
|
}
|
|
|
|
return user;
|
|
}
|
|
}
|
|
|
|
// Write-Through Pattern
|
|
@Service
|
|
public class UserServiceWriteThrough {
|
|
@Autowired
|
|
private RedisTemplate<String, Object> redisTemplate;
|
|
|
|
@Autowired
|
|
private UserRepository userRepository;
|
|
|
|
public User save(User user) {
|
|
// 1. Сохраняем в БД
|
|
User savedUser = userRepository.save(user);
|
|
|
|
// 2. Обновляем кэш
|
|
String cacheKey = CACHE_KEY + savedUser.getId();
|
|
redisTemplate.opsForValue().set(cacheKey, savedUser, CACHE_TTL, TimeUnit.SECONDS);
|
|
|
|
return savedUser;
|
|
}
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>🦀 Rust (Redis + Tokio)</strong></summary>
|
|
|
|
```rust
|
|
// Cache-Aside Pattern
|
|
use redis::Commands;
|
|
use tokio::time::{sleep, Duration};
|
|
|
|
pub struct UserService {
|
|
redis_client: redis::Client,
|
|
user_repository: UserRepository,
|
|
}
|
|
|
|
impl UserService {
|
|
pub async fn find_by_id(&self, id: u64) -> Result<Option<User>, Box<dyn std::error::Error>> {
|
|
let cache_key = format!("user:{}", id);
|
|
let mut conn = self.redis_client.get_async_connection().await?;
|
|
|
|
// 1. Проверяем кэш
|
|
if let Ok(cached_user) = conn.get::<String, String>(&cache_key).await {
|
|
if let Ok(user) = serde_json::from_str::<User>(&cached_user) {
|
|
return Ok(Some(user));
|
|
}
|
|
}
|
|
|
|
// 2. Загружаем из БД
|
|
if let Some(user) = self.user_repository.find_by_id(id).await? {
|
|
// 3. Сохраняем в кэш
|
|
let user_json = serde_json::to_string(&user)?;
|
|
let _: () = conn.set_ex(&cache_key, user_json, 3600).await?;
|
|
return Ok(Some(user));
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
// Write-Behind Pattern
|
|
impl UserService {
|
|
pub async fn save_async(&self, user: User) -> Result<User, Box<dyn std::error::Error>> {
|
|
let cache_key = format!("user:{}", user.id);
|
|
let mut conn = self.redis_client.get_async_connection().await?;
|
|
|
|
// 1. Обновляем кэш сразу
|
|
let user_json = serde_json::to_string(&user)?;
|
|
let _: () = conn.set_ex(&cache_key, user_json, 3600).await?;
|
|
|
|
// 2. Сохраняем в БД асинхронно
|
|
let user_clone = user.clone();
|
|
let repo = self.user_repository.clone();
|
|
tokio::spawn(async move {
|
|
sleep(Duration::from_millis(100)).await;
|
|
let _ = repo.save(user_clone).await;
|
|
});
|
|
|
|
Ok(user)
|
|
}
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>🟨 JavaScript (Node.js + Redis)</strong></summary>
|
|
|
|
```javascript
|
|
// Cache-Aside Pattern
|
|
const Redis = require('ioredis');
|
|
const redis = new Redis();
|
|
|
|
class UserService {
|
|
constructor(userRepository) {
|
|
this.userRepository = userRepository;
|
|
this.CACHE_TTL = 3600; // 1 час
|
|
}
|
|
|
|
async findById(id) {
|
|
const cacheKey = `user:${id}`;
|
|
|
|
try {
|
|
// 1. Проверяем кэш
|
|
const cachedUser = await redis.get(cacheKey);
|
|
if (cachedUser) {
|
|
return JSON.parse(cachedUser);
|
|
}
|
|
|
|
// 2. Загружаем из БД
|
|
const user = await this.userRepository.findById(id);
|
|
if (user) {
|
|
// 3. Сохраняем в кэш
|
|
await redis.setex(cacheKey, this.CACHE_TTL, JSON.stringify(user));
|
|
}
|
|
|
|
return user;
|
|
} catch (error) {
|
|
console.error('Cache error:', error);
|
|
// Fallback to database
|
|
return await this.userRepository.findById(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write-Through Pattern
|
|
class UserServiceWriteThrough {
|
|
async save(user) {
|
|
// 1. Сохраняем в БД
|
|
const savedUser = await this.userRepository.save(user);
|
|
|
|
// 2. Обновляем кэш
|
|
const cacheKey = `user:${savedUser.id}`;
|
|
await redis.setex(cacheKey, this.CACHE_TTL, JSON.stringify(savedUser));
|
|
|
|
return savedUser;
|
|
}
|
|
}
|
|
|
|
// Write-Behind Pattern
|
|
class UserServiceWriteBehind {
|
|
async saveAsync(user) {
|
|
const cacheKey = `user:${user.id}`;
|
|
|
|
// 1. Обновляем кэш сразу
|
|
await redis.setex(cacheKey, this.CACHE_TTL, JSON.stringify(user));
|
|
|
|
// 2. Сохраняем в БД асинхронно
|
|
setImmediate(async () => {
|
|
try {
|
|
await this.userRepository.save(user);
|
|
} catch (error) {
|
|
console.error('Async save error:', error);
|
|
}
|
|
});
|
|
|
|
return user;
|
|
}
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><strong>🐍 Python (FastAPI + Redis)</strong></summary>
|
|
|
|
```python
|
|
# Cache-Aside Pattern
|
|
import asyncio
|
|
import json
|
|
import aioredis
|
|
|
|
class UserService:
|
|
def __init__(self, user_repository):
|
|
self.user_repository = user_repository
|
|
self.redis = None
|
|
self.CACHE_TTL = 3600 # 1 час
|
|
|
|
async def get_redis(self):
|
|
if self.redis is None:
|
|
self.redis = await aioredis.from_url("redis://localhost")
|
|
return self.redis
|
|
|
|
async def find_by_id(self, user_id):
|
|
cache_key = f"user:{user_id}"
|
|
redis = await self.get_redis()
|
|
|
|
try:
|
|
# 1. Проверяем кэш
|
|
cached_user = await redis.get(cache_key)
|
|
if cached_user:
|
|
return json.loads(cached_user)
|
|
|
|
# 2. Загружаем из БД
|
|
user = await self.user_repository.find_by_id(user_id)
|
|
if user:
|
|
# 3. Сохраняем в кэш
|
|
await redis.setex(cache_key, self.CACHE_TTL, json.dumps(user.to_dict()))
|
|
|
|
return user
|
|
except Exception as e:
|
|
print(f"Cache error: {e}")
|
|
# Fallback to database
|
|
return await self.user_repository.find_by_id(user_id)
|
|
|
|
# Write-Through Pattern
|
|
async def save(self, user):
|
|
# 1. Сохраняем в БД
|
|
saved_user = await self.user_repository.save(user)
|
|
|
|
# 2. Обновляем кэш
|
|
cache_key = f"user:{saved_user.id}"
|
|
redis = await self.get_redis()
|
|
await redis.setex(cache_key, self.CACHE_TTL, json.dumps(saved_user.to_dict()))
|
|
|
|
return saved_user
|
|
|
|
# Write-Behind Pattern
|
|
async def save_async(self, user):
|
|
cache_key = f"user:{user.id}"
|
|
redis = await self.get_redis()
|
|
|
|
# 1. Обновляем кэш сразу
|
|
await redis.setex(cache_key, self.CACHE_TTL, json.dumps(user.to_dict()))
|
|
|
|
# 2. Сохраняем в БД асинхронно
|
|
asyncio.create_task(self._save_to_database(user))
|
|
|
|
return user
|
|
|
|
async def _save_to_database(self, user):
|
|
try:
|
|
await asyncio.sleep(0.1) # Небольшая задержка
|
|
await self.user_repository.save(user)
|
|
except Exception as e:
|
|
print(f"Async save error: {e}")
|
|
```
|
|
|
|
</details>
|
|
|
|
## Рекомендации по выбору
|
|
|
|
### Когда выбирать монолит:
|
|
- Команда до 10 человек
|
|
- Простой домен
|
|
- Быстрый MVP
|
|
- Нет опыта с микросервисами
|
|
|
|
### Когда выбирать микросервисы:
|
|
- Большая команда (10+ человек)
|
|
- Сложный домен
|
|
- Разные технологии для разных сервисов
|
|
- Необходимость независимого масштабирования
|
|
|
|
### Когда выбирать SQL:
|
|
- Сложные запросы и аналитика
|
|
- Критична консистентность данных
|
|
- Транзакции между сущностями
|
|
- Команда знает SQL
|
|
|
|
### Когда выбирать NoSQL:
|
|
- Большие объемы данных
|
|
- Простые запросы
|
|
- Горизонтальное масштабирование
|
|
- Гибкая схема данных
|
|
|
|
### Когда выбирать REST:
|
|
- Простые API
|
|
- HTTP кэширование важно
|
|
- Команда знает REST
|
|
- Много разных клиентов
|
|
|
|
### Когда выбирать GraphQL:
|
|
- Сложные клиентские приложения
|
|
- Мобильные приложения
|
|
- Важна производительность сети
|
|
- Единый endpoint для всех данных
|
|
|
|
### Когда выбирать стратегии кэширования:
|
|
|
|
#### Cache-Aside (Lazy Loading):
|
|
- Сложная логика кэширования
|
|
- Нужен полный контроль над кэшем
|
|
- Разные стратегии для разных типов данных
|
|
- Можно допустить cache miss
|
|
|
|
#### Write-Through:
|
|
- Критична консистентность данных
|
|
- Write-heavy нагрузка невысокая
|
|
- Простая логика кэширования
|
|
- Нужна надежность
|
|
|
|
#### Write-Behind (Write-Back):
|
|
- Высокая write-heavy нагрузка
|
|
- Можно допустить потерю небольшого количества данных
|
|
- Критична производительность записи
|
|
- Асинхронная обработка допустима
|
|
|
|
#### Refresh-Ahead:
|
|
- Предсказуемые паттерны доступа
|
|
- Критична производительность
|
|
- Можно предсказать, какие данные понадобятся
|
|
- Есть ресурсы на прогнозирование
|
|
|
|
#### Write-Around:
|
|
- Write-heavy нагрузка
|
|
- Данные редко читаются после записи
|
|
- Простая логика кэширования
|
|
- Экономия ресурсов важна
|
|
|
|
#### Cache-Only:
|
|
- Временные данные
|
|
- Данные можно восстановить
|
|
- Критична производительность
|
|
- Ограниченный объем данных
|
|
|
|
#### Multi-Level Caching:
|
|
- Сложные системы с разными требованиями
|
|
- Нужен баланс производительности и объема
|
|
- Есть ресурсы на поддержку сложной системы
|
|
- Разные типы данных требуют разных подходов
|
|
|
|
#### Cache-As-Sidecar:
|
|
- Нужна высокая производительность кэша
|
|
- Простота развертывания важнее независимого масштабирования
|
|
- Ограниченные сетевые ресурсы
|
|
- Локальная производительность критична
|
|
|
|
## Заключение
|
|
|
|
Архитектурные решения — это основа качественной системы. Каждое решение должно приниматься с пониманием:
|
|
|
|
- **Последствий** — как решение повлияет на систему
|
|
- **Альтернатив** — какие есть другие варианты
|
|
- **Торговых компромиссов** — что мы получаем и что теряем
|
|
- **Контекста** — в каких условиях решение будет работать
|
|
|
|
> **💡 Совет**: Документируйте архитектурные решения в Architecture Decision Records (ADR) для будущих поколений разработчиков.
|
|
|
|
|
|
|