Initial commit: Complete Software Architecture course with 11 modules

- Added comprehensive course structure with 11 modules
- Included theoretical content, practical examples, and tasks
- Multi-language code examples (Java, Rust, Node.js, Python)
- Detailed analysis of architectural approaches and trade-offs
- Complete caching strategies documentation
- Architecture examples from real-world systems (Twitter, Netflix, Uber)
- Structured navigation and learning path
- Ready for educational use
This commit is contained in:
Alexander Abakumov committed 2025-09-09 22:08:07 +03:00
commit 15e744da49
107 files changed
+61762

No files matched your search

@@ -0,0 +1,967 @@
# Основы микросервисной архитектуры
## Навигация
- [🏠 Главная страница курса](../../../README.md)
- [📚 Модуль 2](../README.md)
---
## Определение микросервисов
**Микросервис** — это небольшая, независимая, слабо связанная служба, которая:
- Имеет единственную ответственность
- Развертывается независимо
- Имеет собственные данные
- Коммуницирует через четко определенные API
## Принципы микросервисной архитектуры
### 1. Single Responsibility Principle
Каждый микросервис должен решать одну бизнес-задачу.
**Пример:**
```
User Service → управление пользователями
Payment Service → обработка платежей
Order Service → управление заказами
Notification Service → отправка уведомлений
```
**Технические примеры:**
**Java (Spring Boot) - User Service:**
```java
// User Service - только управление пользователями
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) {
User user = userService.createUser(request);
return ResponseEntity.ok(user);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.getUser(id);
return ResponseEntity.ok(user);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody UpdateUserRequest request) {
User user = userService.updateUser(id, request);
return ResponseEntity.ok(user);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.ok().build();
}
}
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
public User createUser(CreateUserRequest request) {
// Валидация
if (userRepository.existsByEmail(request.getEmail())) {
throw new UserAlreadyExistsException("User with email already exists");
}
// Создание пользователя
User user = new User();
user.setName(request.getName());
user.setEmail(request.getEmail());
user.setPasswordHash(passwordEncoder.encode(request.getPassword()));
user.setCreatedAt(LocalDateTime.now());
return userRepository.save(user);
}
public User getUser(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
public User updateUser(Long id, UpdateUserRequest request) {
User user = getUser(id);
user.setName(request.getName());
user.setUpdatedAt(LocalDateTime.now());
return userRepository.save(user);
}
public void deleteUser(Long id) {
User user = getUser(id);
userRepository.delete(user);
}
}
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String name;
@Column(name = "password_hash", nullable = false)
private String passwordHash;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// геттеры и сеттеры
}
```
**Payment Service - только обработка платежей:**
```java
// Payment Service - только обработка платежей
@SpringBootApplication
public class PaymentServiceApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentServiceApplication.class, args);
}
}
@RestController
@RequestMapping("/api/payments")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@PostMapping("/process")
public ResponseEntity<PaymentResult> processPayment(@RequestBody PaymentRequest request) {
PaymentResult result = paymentService.processPayment(request);
return ResponseEntity.ok(result);
}
@GetMapping("/{id}")
public ResponseEntity<Payment> getPayment(@PathVariable Long id) {
Payment payment = paymentService.getPayment(id);
return ResponseEntity.ok(payment);
}
@PostMapping("/{id}/refund")
public ResponseEntity<RefundResult> refundPayment(@PathVariable Long id, @RequestBody RefundRequest request) {
RefundResult result = paymentService.refundPayment(id, request);
return ResponseEntity.ok(result);
}
}
@Service
@Transactional
public class PaymentService {
@Autowired
private PaymentRepository paymentRepository;
@Autowired
private PaymentGatewayClient paymentGatewayClient;
public PaymentResult processPayment(PaymentRequest request) {
// Валидация платежа
validatePaymentRequest(request);
// Создание записи о платеже
Payment payment = new Payment();
payment.setAmount(request.getAmount());
payment.setCurrency(request.getCurrency());
payment.setPaymentMethod(request.getPaymentMethod());
payment.setStatus(PaymentStatus.PENDING);
payment.setCreatedAt(LocalDateTime.now());
payment = paymentRepository.save(payment);
try {
// Обработка через платежный шлюз
PaymentGatewayResponse response = paymentGatewayClient.processPayment(request);
if (response.isSuccess()) {
payment.setStatus(PaymentStatus.COMPLETED);
payment.setTransactionId(response.getTransactionId());
payment.setProcessedAt(LocalDateTime.now());
} else {
payment.setStatus(PaymentStatus.FAILED);
payment.setFailureReason(response.getErrorMessage());
}
payment = paymentRepository.save(payment);
return new PaymentResult(
payment.getId(),
payment.getStatus(),
response.getTransactionId(),
response.getErrorMessage()
);
} catch (Exception e) {
payment.setStatus(PaymentStatus.FAILED);
payment.setFailureReason(e.getMessage());
paymentRepository.save(payment);
throw new PaymentProcessingException("Payment processing failed", e);
}
}
private void validatePaymentRequest(PaymentRequest request) {
if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new InvalidPaymentAmountException("Payment amount must be positive");
}
if (request.getPaymentMethod() == null) {
throw new InvalidPaymentMethodException("Payment method is required");
}
}
public Payment getPayment(Long id) {
return paymentRepository.findById(id)
.orElseThrow(() -> new PaymentNotFoundException("Payment not found"));
}
public RefundResult refundPayment(Long id, RefundRequest request) {
Payment payment = getPayment(id);
if (payment.getStatus() != PaymentStatus.COMPLETED) {
throw new InvalidRefundException("Only completed payments can be refunded");
}
// Логика возврата средств
RefundGatewayResponse response = paymentGatewayClient.refundPayment(
payment.getTransactionId(),
request.getAmount()
);
if (response.isSuccess()) {
payment.setStatus(PaymentStatus.REFUNDED);
payment.setRefundedAt(LocalDateTime.now());
paymentRepository.save(payment);
}
return new RefundResult(response.isSuccess(), response.getRefundId());
}
}
@Entity
@Table(name = "payments")
public class Payment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private BigDecimal amount;
@Column(nullable = false)
private String currency;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PaymentMethod paymentMethod;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PaymentStatus status;
@Column(name = "transaction_id")
private String transactionId;
@Column(name = "failure_reason")
private String failureReason;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
@Column(name = "processed_at")
private LocalDateTime processedAt;
@Column(name = "refunded_at")
private LocalDateTime refundedAt;
// геттеры и сеттеры
}
```
**Node.js (Express + TypeScript + Prisma) - User Service:**
```typescript
// User Service - только управление пользователями
import express, { Request, Response, NextFunction } from 'express';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
import bcrypt from 'bcrypt';
const app = express();
const prisma = new PrismaClient();
// Interfaces
interface CreateUserRequest {
name: string;
email: string;
password: string;
}
interface UpdateUserRequest {
name: string;
}
interface User {
id: number;
name: string;
email: string;
passwordHash: string;
createdAt: Date;
updatedAt: Date | null;
}
// User Controller
class UserController {
constructor(private userService: UserService) {}
async createUser(req: Request, res: Response, next: NextFunction) {
try {
const user = await this.userService.createUser(req.body);
res.json(user);
} catch (error) {
next(error);
}
}
async getUser(req: Request, res: Response, next: NextFunction) {
try {
const id = parseInt(req.params.id);
const user = await this.userService.getUser(id);
res.json(user);
} catch (error) {
next(error);
}
}
async updateUser(req: Request, res: Response, next: NextFunction) {
try {
const id = parseInt(req.params.id);
const user = await this.userService.updateUser(id, req.body);
res.json(user);
} catch (error) {
next(error);
}
}
async deleteUser(req: Request, res: Response, next: NextFunction) {
try {
const id = parseInt(req.params.id);
await this.userService.deleteUser(id);
res.status(204).send();
} catch (error) {
next(error);
}
}
}
// User Service
class UserService {
constructor(private userRepository: UserRepository) {}
async createUser(request: CreateUserRequest): Promise<User> {
// Валидация
const existingUser = await this.userRepository.findByEmail(request.email);
if (existingUser) {
throw new Error('User with email already exists');
}
// Создание пользователя
const passwordHash = await bcrypt.hash(request.password, 10);
const user = {
name: request.name,
email: request.email,
passwordHash,
createdAt: new Date(),
updatedAt: null
};
return await this.userRepository.save(user);
}
async getUser(id: number): Promise<User> {
const user = await this.userRepository.findById(id);
if (!user) {
throw new Error('User not found');
}
return user;
}
async updateUser(id: number, request: UpdateUserRequest): Promise<User> {
const user = await this.getUser(id);
user.name = request.name;
user.updatedAt = new Date();
return await this.userRepository.save(user);
}
async deleteUser(id: number): Promise<void> {
const user = await this.getUser(id);
await this.userRepository.delete(user.id);
}
}
// User Repository
interface UserRepository {
save(user: Omit<User, 'id'>): Promise<User>;
findById(id: number): Promise<User | null>;
findByEmail(email: string): Promise<User | null>;
delete(id: number): Promise<void>;
}
class PrismaUserRepository implements UserRepository {
async save(user: Omit<User, 'id'>): Promise<User> {
const savedUser = await prisma.user.create({
data: {
name: user.name,
email: user.email,
passwordHash: user.passwordHash,
createdAt: user.createdAt,
updatedAt: user.updatedAt
}
});
return {
id: savedUser.id,
name: savedUser.name,
email: savedUser.email,
passwordHash: savedUser.passwordHash,
createdAt: savedUser.createdAt,
updatedAt: savedUser.updatedAt
};
}
async findById(id: number): Promise<User | null> {
const user = await prisma.user.findUnique({
where: { id }
});
if (!user) return null;
return {
id: user.id,
name: user.name,
email: user.email,
passwordHash: user.passwordHash,
createdAt: user.createdAt,
updatedAt: user.updatedAt
};
}
async findByEmail(email: string): Promise<User | null> {
const user = await prisma.user.findUnique({
where: { email }
});
if (!user) return null;
return {
id: user.id,
name: user.name,
email: user.email,
passwordHash: user.passwordHash,
createdAt: user.createdAt,
updatedAt: user.updatedAt
};
}
async delete(id: number): Promise<void> {
await prisma.user.delete({
where: { id }
});
}
}
// Validation schemas
const createUserSchema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Valid email is required'),
password: z.string().min(6, 'Password must be at least 6 characters')
});
const updateUserSchema = z.object({
name: z.string().min(1, 'Name is required')
});
// Dependency injection
const userRepository = new PrismaUserRepository();
const userService = new UserService(userRepository);
const userController = new UserController(userService);
// Routes
app.post('/api/users', async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedData = createUserSchema.parse(req.body);
await userController.createUser({ ...req, body: validatedData }, res, next);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ error: error.errors });
} else {
next(error);
}
}
});
app.get('/api/users/:id', (req: Request, res: Response, next: NextFunction) => {
userController.getUser(req, res, next);
});
app.put('/api/users/:id', async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedData = updateUserSchema.parse(req.body);
await userController.updateUser({ ...req, body: validatedData }, res, next);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ error: error.errors });
} else {
next(error);
}
}
});
app.delete('/api/users/:id', (req: Request, res: Response, next: NextFunction) => {
userController.deleteUser(req, res, next);
});
// Error handling middleware
app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
});
app.listen(3001, () => {
console.log('User Service running on port 3001');
});
```
**Rust (Actix Web + Diesel) - User Service:**
```rust
// User Service - только управление пользователями
use actix_web::{web, App, HttpResponse, HttpServer, Result};
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Serialize, Deserialize)]
struct CreateUserRequest {
name: String,
email: String,
password: String,
}
#[derive(Serialize, Deserialize)]
struct UpdateUserRequest {
name: String,
}
#[derive(Serialize, Deserialize, Queryable)]
struct User {
id: Uuid,
name: String,
email: String,
password_hash: String,
created_at: chrono::DateTime<chrono::Utc>,
updated_at: Option<chrono::DateTime<chrono::Utc>>,
}
// User Service
struct UserService {
pool: diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<PgConnection>>,
}
impl UserService {
fn new(database_url: &str) -> Self {
let manager = diesel::r2d2::ConnectionManager::<PgConnection>::new(database_url);
let pool = diesel::r2d2::Pool::builder()
.build(manager)
.expect("Failed to create pool");
Self { pool }
}
async fn create_user(&self, request: CreateUserRequest) -> Result<User, Box<dyn std::error::Error>> {
use crate::schema::users::dsl::*;
// Проверка существования пользователя
let existing_user = users
.filter(email.eq(&request.email))
.first::<User>(&mut self.pool.get()?)
.optional()?;
if existing_user.is_some() {
return Err("User with email already exists".into());
}
// Хеширование пароля
let password_hash = bcrypt::hash(&request.password, bcrypt::DEFAULT_COST)?;
let new_user = NewUser {
id: Uuid::new_v4(),
name: request.name,
email: request.email,
password_hash,
created_at: chrono::Utc::now(),
updated_at: None,
};
let user = diesel::insert_into(users)
.values(&new_user)
.get_result::<User>(&mut self.pool.get()?)?;
Ok(user)
}
async fn get_user(&self, user_id: Uuid) -> Result<User, Box<dyn std::error::Error>> {
use crate::schema::users::dsl::*;
let user = users
.filter(id.eq(user_id))
.first::<User>(&mut self.pool.get()?)?;
Ok(user)
}
async fn update_user(&self, user_id: Uuid, request: UpdateUserRequest) -> Result<User, Box<dyn std::error::Error>> {
use crate::schema::users::dsl::*;
let user = diesel::update(users.filter(id.eq(user_id)))
.set((
name.eq(request.name),
updated_at.eq(Some(chrono::Utc::now())),
))
.get_result::<User>(&mut self.pool.get()?)?;
Ok(user)
}
async fn delete_user(&self, user_id: Uuid) -> Result<(), Box<dyn std::error::Error>> {
use crate::schema::users::dsl::*;
diesel::delete(users.filter(id.eq(user_id)))
.execute(&mut self.pool.get()?)?;
Ok(())
}
}
#[derive(Insertable)]
#[diesel(table_name = users)]
struct NewUser {
id: Uuid,
name: String,
email: String,
password_hash: String,
created_at: chrono::DateTime<chrono::Utc>,
updated_at: Option<chrono::DateTime<chrono::Utc>>,
}
// Handlers
async fn create_user(
user_service: web::Data<UserService>,
request: web::Json<CreateUserRequest>,
) -> Result<HttpResponse, actix_web::Error> {
match user_service.create_user(request.into_inner()).await {
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(e) => Ok(HttpResponse::BadRequest().json(serde_json::json!({
"error": e.to_string()
}))),
}
}
async fn get_user(
user_service: web::Data<UserService>,
path: web::Path<Uuid>,
) -> Result<HttpResponse, actix_web::Error> {
let user_id = path.into_inner();
match user_service.get_user(user_id).await {
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::NotFound().json(serde_json::json!({
"error": "User not found"
}))),
}
}
async fn update_user(
user_service: web::Data<UserService>,
path: web::Path<Uuid>,
request: web::Json<UpdateUserRequest>,
) -> Result<HttpResponse, actix_web::Error> {
let user_id = path.into_inner();
match user_service.update_user(user_id, request.into_inner()).await {
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(e) => Ok(HttpResponse::BadRequest().json(serde_json::json!({
"error": e.to_string()
}))),
}
}
async fn delete_user(
user_service: web::Data<UserService>,
path: web::Path<Uuid>,
) -> Result<HttpResponse, actix_web::Error> {
let user_id = path.into_inner();
match user_service.delete_user(user_id).await {
Ok(_) => Ok(HttpResponse::NoContent().finish()),
Err(e) => Ok(HttpResponse::BadRequest().json(serde_json::json!({
"error": e.to_string()
}))),
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let database_url = std::env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
let user_service = UserService::new(&database_url);
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(user_service.clone()))
.route("/api/users", web::post().to(create_user))
.route("/api/users/{id}", web::get().to(get_user))
.route("/api/users/{id}", web::put().to(update_user))
.route("/api/users/{id}", web::delete().to(delete_user))
})
.bind("127.0.0.1:3001")?
.run()
.await
}
```
**Python (FastAPI + SQLAlchemy) - User Service:**
```python
# User Service - только управление пользователей
from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy import create_engine, Column, String, DateTime, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from pydantic import BaseModel, EmailStr
from passlib.context import CryptContext
from datetime import datetime
from typing import Optional
import uuid
app = FastAPI(title="User Service", version="1.0.0")
# Database setup
DATABASE_URL = "postgresql://user:password@localhost/userdb"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Database Models
class User(Base):
__tablename__ = "users"
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
name = Column(String, nullable=False)
email = Column(String, unique=True, nullable=False)
password_hash = Column(String, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, nullable=True)
# Pydantic Models
class CreateUserRequest(BaseModel):
name: str
email: EmailStr
password: str
class UpdateUserRequest(BaseModel):
name: str
class UserResponse(BaseModel):
id: str
name: str
email: str
created_at: datetime
updated_at: Optional[datetime]
class Config:
from_attributes = True
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# User Service
class UserService:
def __init__(self, db: Session):
self.db = db
def create_user(self, request: CreateUserRequest) -> User:
# Проверка существования пользователя
existing_user = self.db.query(User).filter(User.email == request.email).first()
if existing_user:
raise HTTPException(status_code=400, detail="User with email already exists")
# Хеширование пароля
password_hash = pwd_context.hash(request.password)
# Создание пользователя
user = User(
name=request.name,
email=request.email,
password_hash=password_hash
)
self.db.add(user)
self.db.commit()
self.db.refresh(user)
return user
def get_user(self, user_id: str) -> User:
user = self.db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
def update_user(self, user_id: str, request: UpdateUserRequest) -> User:
user = self.get_user(user_id)
user.name = request.name
user.updated_at = datetime.utcnow()
self.db.commit()
self.db.refresh(user)
return user
def delete_user(self, user_id: str) -> None:
user = self.get_user(user_id)
self.db.delete(user)
self.db.commit()
# API Endpoints
@app.post("/api/users", response_model=UserResponse)
async def create_user(
request: CreateUserRequest,
db: Session = Depends(get_db)
):
user_service = UserService(db)
user = user_service.create_user(request)
return user
@app.get("/api/users/{user_id}", response_model=UserResponse)
async def get_user(
user_id: str,
db: Session = Depends(get_db)
):
user_service = UserService(db)
user = user_service.get_user(user_id)
return user
@app.put("/api/users/{user_id}", response_model=UserResponse)
async def update_user(
user_id: str,
request: UpdateUserRequest,
db: Session = Depends(get_db)
):
user_service = UserService(db)
user = user_service.update_user(user_id, request)
return user
@app.delete("/api/users/{user_id}")
async def delete_user(
user_id: str,
db: Session = Depends(get_db)
):
user_service = UserService(db)
user_service.delete_user(user_id)
return {"message": "User deleted successfully"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=3001)
```
### 2. Decentralized Governance
- Каждая команда выбирает свои технологии
- Нет единого технологического стека
- Самостоятельное принятие решений
### 3. Decentralized Data Management
- Каждый сервис владеет своими данными
- Нет единой базы данных
- Разные типы БД для разных сервисов
### 4. Infrastructure Automation
- Автоматизированное развертывание
- CI/CD пайплайны
- Мониторинг и логирование
### 5. Design for Failure
- Сервисы могут отказывать
- Необходима отказоустойчивость
- Circuit breaker паттерны
## Преимущества микросервисов
### Технические преимущества
- **Независимое развертывание** — можно обновлять сервисы отдельно
- **Технологическое разнообразие** — разные технологии для разных задач
- **Масштабируемость** — масштабирование только нужных сервисов
- **Отказоустойчивость** — отказ одного сервиса не влияет на другие
### Организационные преимущества
- **Независимые команды** — каждая команда работает со своим сервисом
- **Быстрая разработка** — параллельная работа команд
- **Специализация** — команды могут специализироваться на доменах
## Недостатки микросервисов
### Сложность
- **Распределенная система** — сложнее отладка и мониторинг
- **Сетевая задержка** — коммуникация через сеть
- **Сложность данных** — распределенные транзакции
### Операционные проблемы
- **Мониторинг** — нужно отслеживать множество сервисов
- **Развертывание** — сложность управления множеством сервисов
- **Тестирование** — интеграционное тестирование усложняется
## Когда использовать микросервисы
### Подходящие случаи:
- Большие команды (8+ разработчиков)
- Сложные домены
- Высокие требования к масштабируемости
- Необходимость независимого развертывания
- Разные технологические требования
### Неподходящие случаи:
- Маленькие команды
- Простые приложения
- Низкие требования к масштабируемости
- Недостаток DevOps экспертизы
@@ -0,0 +1,173 @@
# Domain-Driven Design (DDD)
## Навигация
- [🏠 Главная страница курса](../../../README.md)
- [📚 Модуль 2](../README.md)
---
## Что такое DDD
**Domain-Driven Design** — это подход к разработке программного обеспечения, который:
- Фокусируется на бизнес-домене
- Использует единый язык (Ubiquitous Language)
- Разделяет систему на ограниченные контексты (Bounded Contexts)
## Стратегическое проектирование
### 1. Bounded Context (Ограниченный контекст)
**Определение:** Четко определенная граница, внутри которой модель домена применима.
**Пример:**
```
E-commerce система:
- Order Context → заказы, платежи
- Inventory Context → товары, склад
- Customer Context → клиенты, профили
- Shipping Context → доставка, логистика
```
### 2. Context Map (Контекстная карта)
**Определение:** Диаграмма, показывающая отношения между ограниченными контекстами.
**Типы отношений:**
- **Shared Kernel** — общая модель
- **Customer-Supplier** — клиент-поставщик
- **Conformist** — соответствие
- **Anticorruption Layer** — антикоррупционный слой
- **Open Host Service** — открытый хост-сервис
- **Published Language** — опубликованный язык
### 3. Ubiquitous Language (Единый язык)
**Определение:** Общий язык, используемый командой для описания домена.
**Принципы:**
- Используется в коде
- Используется в документации
- Используется в общении
- Эволюционирует с пониманием домена
## Тактическое проектирование
### 1. Entity (Сущность)
**Определение:** Объект с уникальной идентичностью.
```java
public class Order {
private OrderId id; // уникальный идентификатор
private CustomerId customerId;
private List<OrderItem> items;
private OrderStatus status;
// методы бизнес-логики
public void addItem(ProductId productId, int quantity) {
// бизнес-правила
}
}
```
### 2. Value Object (Объект-значение)
**Определение:** Объект без идентичности, определяемый только своими атрибутами.
```java
public class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
// методы для работы с деньгами
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Different currencies");
}
return new Money(this.amount.add(other.amount), this.currency);
}
}
```
### 3. Aggregate (Агрегат)
**Определение:** Граница консистентности данных.
**Правила агрегатов:**
- Один агрегат = одна транзакция
- Ссылки только на корень агрегата
- Обновления только через корень
```java
public class Order { // корень агрегата
private OrderId id;
private CustomerId customerId;
private List<OrderItem> items; // сущности внутри агрегата
private OrderStatus status;
// методы для изменения состояния
public void addItem(ProductId productId, int quantity) {
// валидация бизнес-правил
items.add(new OrderItem(productId, quantity));
}
}
```
### 4. Repository (Репозиторий)
**Определение:** Интерфейс для доступа к агрегатам.
```java
public interface OrderRepository {
Order findById(OrderId id);
void save(Order order);
List<Order> findByCustomerId(CustomerId customerId);
}
```
### 5. Domain Service (Доменный сервис)
**Определение:** Операция, которая не принадлежит ни одной сущности.
```java
public class OrderPricingService {
public Money calculateTotalPrice(Order order, PricingRules rules) {
// сложная логика расчета цены
// не принадлежит ни Order, ни PricingRules
}
}
```
### 6. Domain Event (Доменное событие)
**Определение:** Событие, произошедшее в домене.
```java
public class OrderCreatedEvent {
private final OrderId orderId;
private final CustomerId customerId;
private final LocalDateTime occurredAt;
// конструктор и геттеры
}
```
## Event Storming (Мозговой штурм событий)
**Event Storming** — методология для изучения домена через события.
### Процесс:
1. **Доменные события** — что происходит в системе
2. **Команды** — что вызывает события
3. **Агрегаты** — что обрабатывает команды
4. **Политики** — бизнес-правила
5. **Read Models** — что нужно для отображения
### Пример:
```
Команда: CreateOrder
↓
Агрегат: Order
↓
Событие: OrderCreated
↓
Политика: SendConfirmationEmail
↓
Команда: SendEmail
```
@@ -0,0 +1,541 @@
# Коммуникация между сервисами
## Навигация
- [🏠 Главная страница курса](../../../README.md)
- [📚 Модуль 2](../README.md)
---
## Синхронная коммуникация
### HTTP/REST
**Преимущества:**
- Простота
- Широкая поддержка
- Стандартизация
**Недостатки:**
- Связанность
- Блокирующие вызовы
- Проблемы с производительностью
**Технический пример - HTTP/REST коммуникация:**
```java
// Order Service - клиент для Payment Service
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private PaymentServiceClient paymentServiceClient;
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
try {
// Синхронный вызов Payment Service
PaymentRequest paymentRequest = new PaymentRequest(
request.getAmount(),
request.getCurrency(),
request.getPaymentMethod(),
request.getPaymentDetails()
);
PaymentResult paymentResult = paymentServiceClient.processPayment(paymentRequest);
if (paymentResult.isSuccess()) {
// Создаем заказ только после успешной оплаты
Order order = orderService.createOrder(request, paymentResult.getTransactionId());
return ResponseEntity.ok(order);
} else {
return ResponseEntity.badRequest()
.body(new ErrorResponse("Payment failed: " + paymentResult.getErrorMessage()));
}
} catch (PaymentServiceException e) {
// Обработка ошибок коммуникации
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(new ErrorResponse("Payment service unavailable"));
}
}
}
// HTTP клиент для Payment Service
@Component
public class PaymentServiceClient {
@Autowired
private RestTemplate restTemplate;
private static final String PAYMENT_SERVICE_URL = "http://payment-service:8080";
public PaymentResult processPayment(PaymentRequest request) {
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<PaymentRequest> entity = new HttpEntity<>(request, headers);
ResponseEntity<PaymentResult> response = restTemplate.postForEntity(
PAYMENT_SERVICE_URL + "/api/payments/process",
entity,
PaymentResult.class
);
if (response.getStatusCode() == HttpStatus.OK) {
return response.getBody();
} else {
throw new PaymentServiceException("Payment service returned error: " + response.getStatusCode());
}
} catch (RestClientException e) {
throw new PaymentServiceException("Failed to communicate with payment service", e);
}
}
public PaymentResult refundPayment(String transactionId, BigDecimal amount) {
try {
RefundRequest refundRequest = new RefundRequest(transactionId, amount);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<RefundRequest> entity = new HttpEntity<>(refundRequest, headers);
ResponseEntity<RefundResult> response = restTemplate.postForEntity(
PAYMENT_SERVICE_URL + "/api/payments/refund",
entity,
RefundResult.class
);
if (response.getStatusCode() == HttpStatus.OK) {
RefundResult refundResult = response.getBody();
return new PaymentResult(
null,
refundResult.isSuccess() ? PaymentStatus.REFUNDED : PaymentStatus.FAILED,
transactionId,
refundResult.isSuccess() ? null : "Refund failed"
);
} else {
throw new PaymentServiceException("Refund service returned error: " + response.getStatusCode());
}
} catch (RestClientException e) {
throw new PaymentServiceException("Failed to communicate with refund service", e);
}
}
}
// Конфигурация RestTemplate с таймаутами и retry
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000); // 5 секунд на подключение
factory.setReadTimeout(10000); // 10 секунд на чтение
RestTemplate restTemplate = new RestTemplate(factory);
// Добавляем retry логику
restTemplate.setInterceptors(Arrays.asList(new RetryInterceptor()));
return restTemplate;
}
}
// Interceptor для retry логики
public class RetryInterceptor implements ClientHttpRequestInterceptor {
private static final int MAX_RETRIES = 3;
@Override
public ClientHttpResponse intercept(
HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution) throws IOException {
int retryCount = 0;
IOException lastException = null;
while (retryCount < MAX_RETRIES) {
try {
return execution.execute(request, body);
} catch (IOException e) {
lastException = e;
retryCount++;
if (retryCount < MAX_RETRIES) {
try {
Thread.sleep(1000 * retryCount); // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new IOException("Retry interrupted", ie);
}
}
}
}
throw lastException;
}
}
```
### GraphQL
**Преимущества:**
- Гибкость запросов
- Типизация
- Единая точка входа
**Недостатки:**
- Сложность кэширования
- Потенциальные проблемы с производительностью
## Асинхронная коммуникация
### Очереди сообщений
**Преимущества:**
- Слабая связанность
- Отказоустойчивость
- Масштабируемость
**Недостатки:**
- Сложность отладки
- Потенциальные проблемы с порядком
**Технический пример - асинхронная коммуникация с RabbitMQ:**
```java
// Order Service - публикует события
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private RabbitTemplate rabbitTemplate;
public Order createOrder(CreateOrderRequest request) {
// Создаем заказ
Order order = new Order();
order.setCustomerId(request.getCustomerId());
order.setItems(request.getItems());
order.setStatus(OrderStatus.CREATED);
order.setCreatedAt(LocalDateTime.now());
order = orderRepository.save(order);
// Публикуем событие о создании заказа
OrderCreatedEvent event = new OrderCreatedEvent(
order.getId(),
order.getCustomerId(),
order.getItems(),
order.getTotalAmount(),
order.getCreatedAt()
);
rabbitTemplate.convertAndSend("order.exchange", "order.created", event);
return order;
}
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException("Order not found"));
if (order.getStatus() == OrderStatus.CREATED) {
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
// Публикуем событие об отмене заказа
OrderCancelledEvent event = new OrderCancelledEvent(
order.getId(),
order.getCustomerId(),
order.getItems(),
order.getTotalAmount(),
LocalDateTime.now()
);
rabbitTemplate.convertAndSend("order.exchange", "order.cancelled", event);
}
}
}
// Notification Service - подписывается на события
@Component
public class OrderEventHandler {
@Autowired
private NotificationService notificationService;
@RabbitListener(queues = "order.created.queue")
public void handleOrderCreated(OrderCreatedEvent event) {
try {
// Отправляем подтверждение заказа
notificationService.sendOrderConfirmation(
event.getCustomerId(),
event.getOrderId(),
event.getTotalAmount()
);
// Отправляем уведомление о статусе заказа
notificationService.sendOrderStatusUpdate(
event.getCustomerId(),
event.getOrderId(),
"Your order has been created and is being processed"
);
} catch (Exception e) {
// Логируем ошибку, но не прерываем обработку
log.error("Failed to send order confirmation for order: " + event.getOrderId(), e);
}
}
@RabbitListener(queues = "order.cancelled.queue")
public void handleOrderCancelled(OrderCancelledEvent event) {
try {
// Отправляем уведомление об отмене
notificationService.sendOrderCancellation(
event.getCustomerId(),
event.getOrderId(),
event.getTotalAmount()
);
} catch (Exception e) {
log.error("Failed to send order cancellation for order: " + event.getOrderId(), e);
}
}
}
// Inventory Service - подписывается на события
@Component
public class InventoryEventHandler {
@Autowired
private InventoryService inventoryService;
@RabbitListener(queues = "order.created.queue")
public void handleOrderCreated(OrderCreatedEvent event) {
try {
// Резервируем товары
for (OrderItem item : event.getItems()) {
inventoryService.reserveItem(item.getProductId(), item.getQuantity());
}
// Публикуем событие о резервировании
InventoryReservedEvent inventoryEvent = new InventoryReservedEvent(
event.getOrderId(),
event.getItems(),
LocalDateTime.now()
);
rabbitTemplate.convertAndSend("inventory.exchange", "inventory.reserved", inventoryEvent);
} catch (InsufficientStockException e) {
// Если товаров недостаточно, отменяем заказ
OrderCancelledEvent cancelEvent = new OrderCancelledEvent(
event.getOrderId(),
event.getCustomerId(),
event.getItems(),
event.getTotalAmount(),
LocalDateTime.now()
);
rabbitTemplate.convertAndSend("order.exchange", "order.cancelled", cancelEvent);
} catch (Exception e) {
log.error("Failed to reserve inventory for order: " + event.getOrderId(), e);
}
}
@RabbitListener(queues = "order.cancelled.queue")
public void handleOrderCancelled(OrderCancelledEvent event) {
try {
// Освобождаем зарезервированные товары
for (OrderItem item : event.getItems()) {
inventoryService.releaseReservation(item.getProductId(), item.getQuantity());
}
} catch (Exception e) {
log.error("Failed to release inventory for order: " + event.getOrderId(), e);
}
}
}
// Конфигурация RabbitMQ
@Configuration
@EnableRabbit
public class RabbitMQConfig {
// Exchange для заказов
@Bean
public TopicExchange orderExchange() {
return new TopicExchange("order.exchange");
}
// Очереди для событий заказов
@Bean
public Queue orderCreatedQueue() {
return new Queue("order.created.queue", true);
}
@Bean
public Queue orderCancelledQueue() {
return new Queue("order.cancelled.queue", true);
}
// Привязки очередей к exchange
@Bean
public Binding orderCreatedBinding() {
return BindingBuilder
.bind(orderCreatedQueue())
.to(orderExchange())
.with("order.created");
}
@Bean
public Binding orderCancelledBinding() {
return BindingBuilder
.bind(orderCancelledQueue())
.to(orderExchange())
.with("order.cancelled");
}
// Exchange для инвентаря
@Bean
public TopicExchange inventoryExchange() {
return new TopicExchange("inventory.exchange");
}
@Bean
public Queue inventoryReservedQueue() {
return new Queue("inventory.reserved.queue", true);
}
@Bean
public Binding inventoryReservedBinding() {
return BindingBuilder
.bind(inventoryReservedQueue())
.to(inventoryExchange())
.with("inventory.reserved");
}
// Конфигурация RabbitTemplate
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(new Jackson2JsonMessageConverter());
return template;
}
// Конфигурация Jackson для сериализации
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
// События (DTO)
public class OrderCreatedEvent {
private Long orderId;
private Long customerId;
private List<OrderItem> items;
private BigDecimal totalAmount;
private LocalDateTime createdAt;
// конструкторы, геттеры, сеттеры
}
public class OrderCancelledEvent {
private Long orderId;
private Long customerId;
private List<OrderItem> items;
private BigDecimal totalAmount;
private LocalDateTime cancelledAt;
// конструкторы, геттеры, сеттеры
}
public class InventoryReservedEvent {
private Long orderId;
private List<OrderItem> items;
private LocalDateTime reservedAt;
// конструкторы, геттеры, сеттеры
}
```
### Потоковая обработка событий
**Преимущества:**
- Высокая пропускная способность
- Исторические данные
- Real-time обработка
**Технологии:**
- Apache Kafka
- Apache Pulsar
- AWS Kinesis
## Паттерны коммуникации
### 1. Request-Response
```
Client → Service A → Service B
Client ← Service A ← Service B
```
### 2. Publish-Subscribe
```
Publisher → Event Bus → Subscriber 1
→ Subscriber 2
→ Subscriber 3
```
### 3. Request-Reply
```
Client → Message Queue → Service
Client ← Message Queue ← Service
```
### 4. Event Sourcing
```
Command → Aggregate → Event Store
Query → Read Model ← Event Store
```
## Обнаружение сервисов
**Проблема:** Как сервисы находят друг друга в динамической среде?
### Решения:
#### 1. Client-Side Discovery
```java
@Service
public class PaymentServiceClient {
@Autowired
private ServiceDiscovery serviceDiscovery;
public PaymentResult processPayment(PaymentRequest request) {
// клиент сам находит сервис
ServiceInstance instance = serviceDiscovery.getInstance("payment-service");
String url = "http://" + instance.getHost() + ":" + instance.getPort();
return restTemplate.postForObject(url + "/payments", request, PaymentResult.class);
}
}
```
#### 2. Server-Side Discovery
```java
@Service
public class PaymentServiceClient {
public PaymentResult processPayment(PaymentRequest request) {
// сервис находится через load balancer
String url = "http://payment-service/payments";
return restTemplate.postForObject(url, request, PaymentResult.class);
}
}
```
### Инструменты:
- **Consul** — service discovery и configuration
- **Eureka** — Netflix service discovery
- **Kubernetes** — встроенный service discovery
@@ -0,0 +1,201 @@
# Управление данными в микросервисах
## Навигация
- [🏠 Главная страница курса](../../../README.md)
- [📚 Модуль 2](../README.md)
---
## База данных на сервис
**Принцип:** Каждый микросервис имеет собственную базу данных.
### Преимущества:
- Независимость данных
- Разные типы БД для разных задач
- Независимое масштабирование
### Недостатки:
- Сложность транзакций
- Проблемы с консистентностью
- Дублирование данных
## Паттерны управления данными
### 1. Saga Pattern
**Определение:** Паттерн для управления распределенными транзакциями.
#### Choreography Saga
```java
// Order Service
public class OrderService {
public void createOrder(CreateOrderRequest request) {
Order order = new Order(request);
orderRepository.save(order);
// публикуем событие
eventPublisher.publish(new OrderCreatedEvent(order.getId(), request.getItems()));
}
}
// Inventory Service
@EventHandler
public class InventoryEventHandler {
public void handleOrderCreated(OrderCreatedEvent event) {
try {
inventoryService.reserveItems(event.getItems());
eventPublisher.publish(new ItemsReservedEvent(event.getOrderId()));
} catch (InsufficientStockException e) {
eventPublisher.publish(new OrderCancelledEvent(event.getOrderId()));
}
}
}
// Payment Service
@EventHandler
public class PaymentEventHandler {
public void handleItemsReserved(ItemsReservedEvent event) {
try {
paymentService.processPayment(event.getOrderId());
eventPublisher.publish(new PaymentProcessedEvent(event.getOrderId()));
} catch (PaymentFailedException e) {
eventPublisher.publish(new OrderCancelledEvent(event.getOrderId()));
}
}
}
```
#### Orchestration Saga
```java
@Service
public class OrderOrchestrator {
public void processOrder(CreateOrderRequest request) {
Order order = orderService.createOrder(request);
try {
// шаг 1: резервирование товаров
inventoryService.reserveItems(order.getId(), request.getItems());
// шаг 2: обработка платежа
paymentService.processPayment(order.getId(), request.getPayment());
// шаг 3: подтверждение заказа
orderService.confirmOrder(order.getId());
} catch (Exception e) {
// компенсирующие транзакции
compensateOrder(order.getId());
}
}
private void compensateOrder(OrderId orderId) {
try {
inventoryService.releaseItems(orderId);
paymentService.refundPayment(orderId);
orderService.cancelOrder(orderId);
} catch (Exception e) {
// логирование ошибок компенсации
}
}
}
```
### 2. CQRS (Command Query Responsibility Segregation)
**Определение:** Разделение команд и запросов.
```java
// Command Side
@Service
public class OrderCommandService {
public void createOrder(CreateOrderCommand command) {
Order order = new Order(command);
orderRepository.save(order);
// обновляем read model
orderReadModelService.updateReadModel(order);
}
}
// Query Side
@Service
public class OrderQueryService {
public OrderView getOrderView(OrderId orderId) {
return orderReadModelRepository.findById(orderId);
}
public List<OrderView> getOrdersByCustomer(CustomerId customerId) {
return orderReadModelRepository.findByCustomerId(customerId);
}
}
```
### 3. Event Sourcing
**Определение:** Хранение событий вместо состояния.
```java
@Entity
public class OrderEvent {
private Long id;
private String aggregateId;
private String eventType;
private String eventData;
private LocalDateTime occurredAt;
}
@Service
public class OrderEventStore {
public void saveEvent(OrderEvent event) {
eventRepository.save(event);
}
public List<OrderEvent> getEvents(String aggregateId) {
return eventRepository.findByAggregateIdOrderByOccurredAt(aggregateId);
}
}
@Service
public class OrderProjectionService {
public OrderView buildOrderView(String orderId) {
List<OrderEvent> events = eventStore.getEvents(orderId);
OrderView view = new OrderView();
for (OrderEvent event : events) {
applyEvent(view, event);
}
return view;
}
}
```
## Консистентность данных
### Типы консистентности:
#### 1. Strong Consistency
- Все узлы видят одинаковые данные
- Сложно в распределенных системах
- Высокая задержка
#### 2. Eventual Consistency
- Данные в итоге станут консистентными
- Подходит для большинства случаев
- Низкая задержка
#### 3. Weak Consistency
- Нет гарантий консистентности
- Используется редко
### Паттерны для консистентности:
#### 1. Two-Phase Commit (2PC)
**Проблема:** Блокирующий протокол
**Решение:** Использовать Saga вместо 2PC
#### 2. Three-Phase Commit (3PC)
**Улучшение:** Меньше блокировок
**Проблема:** Все еще сложный
#### 3. Consensus Algorithms
- **Raft** — выбор лидера
- **Paxos** — консенсус в распределенных системах
@@ -0,0 +1,257 @@
# Serverless архитектура и FaaS
## Навигация
- [🏠 Главная страница курса](../../../README.md)
- [📚 Модуль 2](../README.md)
---
## Что такое Serverless
**Serverless** — это модель облачных вычислений, где разработчики не управляют серверами, а платформа автоматически выделяет ресурсы по требованию.
## Ключевые характеристики Serverless
### 1. Event-Driven Execution
- Функции запускаются по событиям
- Автоматическое масштабирование
- Оплата только за время выполнения
### 2. Stateless Functions
- Функции не хранят состояние
- Внешнее хранение данных
- Горизонтальное масштабирование
## FaaS (Функция как сервис)
**FaaS** — это подмодель Serverless, где код выполняется в виде отдельных функций.
### AWS Lambda
```python
# AWS Lambda Function
import json
import boto3
def lambda_handler(event, context):
"""
Lambda function для обработки заказов
"""
# Парсинг события
order_data = json.loads(event['body'])
# Валидация данных
if not validate_order(order_data):
return {
'statusCode': 400,
'body': json.dumps({'error': 'Invalid order data'})
}
# Обработка заказа
order_id = process_order(order_data)
# Отправка уведомления
send_notification(order_id)
return {
'statusCode': 200,
'body': json.dumps({
'orderId': order_id,
'status': 'processed'
})
}
def validate_order(order_data):
"""Валидация данных заказа"""
required_fields = ['customerId', 'items', 'total']
return all(field in order_data for field in required_fields)
def process_order(order_data):
"""Обработка заказа"""
# Логика обработки заказа
return f"ORD-{order_data.get('customerId')}-{int(time.time())}"
def send_notification(order_id):
"""Отправка уведомления"""
sns = boto3.client('sns')
sns.publish(
TopicArn='arn:aws:sns:us-east-1:123456789012:order-notifications',
Message=f'Order {order_id} has been processed',
Subject='Order Processed'
)
```
### Azure Functions
```csharp
// Azure Function
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
public static class OrderProcessor
{
[FunctionName("ProcessOrder")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "orders")] HttpRequest req,
ILogger log)
{
log.LogInformation("Processing order request");
try
{
// Чтение данных запроса
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var orderData = JsonSerializer.Deserialize<OrderData>(requestBody);
// Валидация
if (!ValidateOrder(orderData))
{
return new BadRequestObjectResult("Invalid order data");
}
// Обработка заказа
var orderId = await ProcessOrderAsync(orderData);
// Отправка уведомления
await SendNotificationAsync(orderId);
return new OkObjectResult(new { orderId, status = "processed" });
}
catch (Exception ex)
{
log.LogError(ex, "Error processing order");
return new StatusCodeResult(500);
}
}
private static bool ValidateOrder(OrderData orderData)
{
return !string.IsNullOrEmpty(orderData.CustomerId) &&
orderData.Items?.Count > 0 &&
orderData.Total > 0;
}
private static async Task<string> ProcessOrderAsync(OrderData orderData)
{
// Логика обработки заказа
await Task.Delay(100); // Имитация обработки
return $"ORD-{orderData.CustomerId}-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
}
private static async Task SendNotificationAsync(string orderId)
{
// Отправка уведомления
await Task.Delay(50); // Имитация отправки
}
}
public class OrderData
{
public string CustomerId { get; set; }
public List<OrderItem> Items { get; set; }
public decimal Total { get; set; }
}
```
### Google Cloud Functions
```javascript
// Google Cloud Function
const functions = require('@google-cloud/functions-framework');
const { PubSub } = require('@google-cloud/pubsub');
const pubsub = new PubSub();
functions.http('processOrder', async (req, res) => {
// Установка CORS
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', 'POST');
res.set('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
if (req.method !== 'POST') {
res.status(405).send('Method Not Allowed');
return;
}
try {
const orderData = req.body;
// Валидация данных
if (!validateOrder(orderData)) {
res.status(400).json({ error: 'Invalid order data' });
return;
}
// Обработка заказа
const orderId = await processOrder(orderData);
// Отправка уведомления
await sendNotification(orderId);
res.status(200).json({
orderId: orderId,
status: 'processed'
});
} catch (error) {
console.error('Error processing order:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
function validateOrder(orderData) {
const requiredFields = ['customerId', 'items', 'total'];
return requiredFields.every(field => orderData.hasOwnProperty(field));
}
async function processOrder(orderData) {
// Логика обработки заказа
await new Promise(resolve => setTimeout(resolve, 100));
return `ORD-${orderData.customerId}-${Date.now()}`;
}
async function sendNotification(orderId) {
const topic = pubsub.topic('order-notifications');
const message = {
data: Buffer.from(JSON.stringify({
orderId: orderId,
message: `Order ${orderId} has been processed`
}))
};
await topic.publish(message);
}
```
## Преимущества Serverless
### Технические:
- Автоматическое масштабирование
- Оплата только за использование
- Отсутствие управления серверами
- Быстрое развертывание
### Экономические:
- Снижение операционных расходов
- Предсказуемые затраты
- Отсутствие простоев серверов
## Недостатки Serverless
### Ограничения:
- Cold Start задержки
- Ограничения времени выполнения
- Ограничения памяти
- Vendor Lock-in
### Сложности:
- Сложность отладки
- Ограниченный контроль
- Проблемы с состоянием
@@ -0,0 +1,240 @@
# Микрофронтенды
## Навигация
- [🏠 Главная страница курса](../../../README.md)
- [📚 Модуль 2](../README.md)
---
## Что такое микрофронтенды
**Микрофронтенды** — это архитектурный подход, где фронтенд приложения разбивается на независимые части.
## Принципы микрофронтендов
### 1. Независимость
- Независимая разработка
- Независимое развертывание
- Независимые команды
### 2. Технологическое разнообразие
- Разные фреймворки
- Разные библиотеки
- Разные подходы
### 3. Единый пользовательский интерфейс
- Общий дизайн
- Общие компоненты
- Общий пользовательский опыт
## Архитектурные паттерны
### 1. Module Federation (Webpack 5)
```javascript
// shell/ModuleFederationPlugin.js
new ModuleFederationPlugin({
name: 'shell',
remotes: {
'product': 'product@http://localhost:3001/remoteEntry.js',
'cart': 'cart@http://localhost:3002/remoteEntry.js',
},
});
// product/webpack.config.js
new ModuleFederationPlugin({
name: 'product',
filename: 'remoteEntry.js',
exposes: {
'./ProductList': './src/ProductList',
},
});
```
### 2. Single-SPA
```javascript
// main.js
import { registerApplication, start } from 'single-spa';
registerApplication({
name: 'product',
app: () => System.import('product'),
activeWhen: ['/products'],
});
registerApplication({
name: 'cart',
app: () => System.import('cart'),
activeWhen: ['/cart'],
});
start();
```
### 3. iframe-based
```html
<!-- shell/index.html -->
<div id="product-microfrontend">
<iframe src="http://localhost:3001/products"
width="100%"
height="600px">
</iframe>
</div>
<div id="cart-microfrontend">
<iframe src="http://localhost:3002/cart"
width="100%"
height="400px">
</iframe>
</div>
```
## Управление состоянием
### 1. Shared State
```javascript
// shared-state/store.js
class SharedStore {
constructor() {
this.state = {};
this.subscribers = [];
}
setState(newState) {
this.state = { ...this.state, ...newState };
this.notifySubscribers();
}
subscribe(callback) {
this.subscribers.push(callback);
}
notifySubscribers() {
this.subscribers.forEach(callback => callback(this.state));
}
}
export const sharedStore = new SharedStore();
```
### 2. Event Bus
```javascript
// shared/eventBus.js
class EventBus {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
}
export const eventBus = new EventBus();
```
## Коммуникация между микрофронтендами
### 1. Props/Events
```javascript
// shell/App.js
import ProductMicrofrontend from 'product/ProductList';
import CartMicrofrontend from 'cart/CartList';
function App() {
const [cartItems, setCartItems] = useState([]);
const handleAddToCart = (product) => {
setCartItems([...cartItems, product]);
};
return (
<div>
<ProductMicrofrontend onAddToCart={handleAddToCart} />
<CartMicrofrontend items={cartItems} />
</div>
);
}
```
### 2. Custom Events
```javascript
// product/ProductList.js
function ProductList() {
const handleAddToCart = (product) => {
// отправляем событие
window.dispatchEvent(new CustomEvent('addToCart', {
detail: product
}));
};
return (
<div>
{products.map(product => (
<button onClick={() => handleAddToCart(product)}>
Add to Cart
</button>
))}
</div>
);
}
// cart/CartList.js
function CartList() {
const [items, setItems] = useState([]);
useEffect(() => {
const handleAddToCart = (event) => {
setItems([...items, event.detail]);
};
window.addEventListener('addToCart', handleAddToCart);
return () => {
window.removeEventListener('addToCart', handleAddToCart);
};
}, [items]);
return (
<div>
{items.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
```
## Преимущества микрофронтендов
### Технические:
- Независимая разработка
- Технологическое разнообразие
- Независимое развертывание
- Масштабируемость команд
### Организационные:
- Независимые команды
- Быстрая разработка
- Специализация команд
## Недостатки микрофронтендов
### Сложность:
- Сложность интеграции
- Дублирование кода
- Проблемы с производительностью
- Сложность тестирования
### Операционные:
- Сложность развертывания
- Проблемы с версионированием
- Сложность мониторинга
@@ -0,0 +1,31 @@
# Практические задания
## Навигация
- [🏠 Главная страница курса](../../../README.md)
- [📚 Модуль 2](../README.md)
---
### Задание 1: Проектирование микросервисов
Спроектируйте микросервисную архитектуру для системы интернет-магазина:
1. Определите домены и ограниченные контексты
2. Спроектируйте сервисы и их взаимодействие
3. Определите данные для каждого сервиса
4. Создайте контекстную карту
### Задание 2: Реализация Saga
Реализуйте Saga для процесса создания заказа:
1. Создание заказа
2. Резервирование товаров
3. Обработка платежа
4. Подтверждение заказа
5. Компенсирующие транзакции
### Задание 3: Микрофронтенды
Создайте простую систему микрофронтендов:
1. Shell приложение
2. Product микрофронтенд
3. Cart микрофронтенд
4. Коммуникация между ними
---