# Модуль 2: Микросервисная архитектура ## Содержание модуля 1. [Основы микросервисной архитектуры](#основы-микросервисной-архитектуры) 2. [Domain-Driven Design (DDD)](#domain-driven-design-ddd) 3. [Коммуникация между сервисами](#коммуникация-между-сервисами) 4. [Управление данными в микросервисах](#управление-данными-в-микросервисах) 5. [Микрофронтенды](#микрофронтенды) 6. [Serverless архитектура и FaaS](#serverless-архитектура-и-faas) 7. [Паттерны микросервисов](#паттерны-микросервисов) 8. [Антипаттерны и проблемы](#антипаттерны-и-проблемы) --- ## Основы микросервисной архитектуры ### Определение микросервисов **Микросервис** — это небольшая, независимая, слабо связанная служба, которая: - Имеет единственную ответственность - Развертывается независимо - Имеет собственные данные - Коммуницирует через четко определенные 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 createUser(@RequestBody CreateUserRequest request) { User user = userService.createUser(request); return ResponseEntity.ok(user); } @GetMapping("/{id}") public ResponseEntity getUser(@PathVariable Long id) { User user = userService.getUser(id); return ResponseEntity.ok(user); } @PutMapping("/{id}") public ResponseEntity updateUser(@PathVariable Long id, @RequestBody UpdateUserRequest request) { User user = userService.updateUser(id, request); return ResponseEntity.ok(user); } @DeleteMapping("/{id}") public ResponseEntity 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 processPayment(@RequestBody PaymentRequest request) { PaymentResult result = paymentService.processPayment(request); return ResponseEntity.ok(result); } @GetMapping("/{id}") public ResponseEntity getPayment(@PathVariable Long id) { Payment payment = paymentService.getPayment(id); return ResponseEntity.ok(payment); } @PostMapping("/{id}/refund") public ResponseEntity 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 { // Валидация 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 { const user = await this.userRepository.findById(id); if (!user) { throw new Error('User not found'); } return user; } async updateUser(id: number, request: UpdateUserRequest): Promise { 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 { const user = await this.getUser(id); await this.userRepository.delete(user.id); } } // User Repository interface UserRepository { save(user: Omit): Promise; findById(id: number): Promise; findByEmail(email: string): Promise; delete(id: number): Promise; } class PrismaUserRepository implements UserRepository { async save(user: Omit): Promise { 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 { 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 { 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 { 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'); }); ``` **Node.js (Express + TypeScript + Prisma) - Payment Service:** ```typescript // Payment Service - только обработка платежей import express, { Request, Response, NextFunction } from 'express'; import { PrismaClient } from '@prisma/client'; import { z } from 'zod'; const app = express(); const prisma = new PrismaClient(); // Interfaces interface PaymentRequest { amount: number; currency: string; paymentMethod: string; orderId: number; customerId: number; } interface PaymentResult { id: number; status: string; transactionId?: string; errorMessage?: string; } interface RefundRequest { amount: number; reason: string; } interface RefundResult { id: number; status: string; refundId?: string; errorMessage?: string; } // Payment Controller class PaymentController { constructor(private paymentService: PaymentService) {} async processPayment(req: Request, res: Response, next: NextFunction) { try { const result = await this.paymentService.processPayment(req.body); res.json(result); } catch (error) { next(error); } } async getPayment(req: Request, res: Response, next: NextFunction) { try { const id = parseInt(req.params.id); const payment = await this.paymentService.getPayment(id); res.json(payment); } catch (error) { next(error); } } async refundPayment(req: Request, res: Response, next: NextFunction) { try { const id = parseInt(req.params.id); const result = await this.paymentService.refundPayment(id, req.body); res.json(result); } catch (error) { next(error); } } } // Payment Service class PaymentService { constructor( private paymentRepository: PaymentRepository, private paymentGateway: PaymentGateway ) {} async processPayment(request: PaymentRequest): Promise { // Валидация this.validatePaymentRequest(request); // Создание платежа const payment = { amount: request.amount, currency: request.currency, paymentMethod: request.paymentMethod, orderId: request.orderId, customerId: request.customerId, status: 'PENDING', createdAt: new Date(), processedAt: null, refundedAt: null }; const savedPayment = await this.paymentRepository.save(payment); try { // Обработка через платежный шлюз const response = await this.paymentGateway.processPayment(request); if (response.success) { savedPayment.status = 'COMPLETED'; savedPayment.transactionId = response.transactionId; savedPayment.processedAt = new Date(); } else { savedPayment.status = 'FAILED'; savedPayment.failureReason = response.errorMessage; } await this.paymentRepository.save(savedPayment); return new PaymentResult( savedPayment.id, savedPayment.status, response.transactionId, response.errorMessage ); } catch (error) { savedPayment.status = 'FAILED'; savedPayment.failureReason = error.message; await this.paymentRepository.save(savedPayment); throw new Error(`Payment processing failed: ${error.message}`); } } private validatePaymentRequest(request: PaymentRequest): void { if (request.amount <= 0) { throw new Error('Payment amount must be positive'); } if (!request.paymentMethod) { throw new Error('Payment method is required'); } } async getPayment(id: number): Promise { const payment = await this.paymentRepository.findById(id); if (!payment) { throw new Error('Payment not found'); } return payment; } async refundPayment(id: number, request: RefundRequest): Promise { const payment = await this.getPayment(id); if (payment.status !== 'COMPLETED') { throw new Error('Only completed payments can be refunded'); } try { const refundResponse = await this.paymentGateway.refundPayment({ transactionId: payment.transactionId, amount: request.amount, reason: request.reason }); payment.status = 'REFUNDED'; payment.refundedAt = new Date(); await this.paymentRepository.save(payment); return new RefundResult( payment.id, 'REFUNDED', refundResponse.refundId ); } catch (error) { throw new Error(`Refund failed: ${error.message}`); } } } // Payment Repository interface PaymentRepository { save(payment: any): Promise; findById(id: number): Promise; } class PrismaPaymentRepository implements PaymentRepository { async save(payment: any): Promise { return await prisma.payment.create({ data: { amount: payment.amount, currency: payment.currency, paymentMethod: payment.paymentMethod, orderId: payment.orderId, customerId: payment.customerId, status: payment.status, transactionId: payment.transactionId, failureReason: payment.failureReason, createdAt: payment.createdAt, processedAt: payment.processedAt, refundedAt: payment.refundedAt } }); } async findById(id: number): Promise { return await prisma.payment.findUnique({ where: { id } }); } } // Payment Gateway (mock implementation) class PaymentGateway { async processPayment(request: PaymentRequest): Promise { // Mock implementation - в реальности здесь будет интеграция с платежным шлюзом return new Promise((resolve) => { setTimeout(() => { if (Math.random() > 0.1) { // 90% success rate resolve({ success: true, transactionId: `txn_${Date.now()}`, errorMessage: null }); } else { resolve({ success: false, transactionId: null, errorMessage: 'Payment gateway error' }); } }, 1000); }); } async refundPayment(request: any): Promise { // Mock implementation return { refundId: `refund_${Date.now()}` }; } } // Validation schemas const paymentRequestSchema = z.object({ amount: z.number().positive('Amount must be positive'), currency: z.string().min(3, 'Currency is required'), paymentMethod: z.string().min(1, 'Payment method is required'), orderId: z.number().positive('Order ID is required'), customerId: z.number().positive('Customer ID is required') }); const refundRequestSchema = z.object({ amount: z.number().positive('Amount must be positive'), reason: z.string().min(1, 'Reason is required') }); // Dependency injection const paymentRepository = new PrismaPaymentRepository(); const paymentGateway = new PaymentGateway(); const paymentService = new PaymentService(paymentRepository, paymentGateway); const paymentController = new PaymentController(paymentService); // Routes app.post('/api/payments/process', async (req: Request, res: Response, next: NextFunction) => { try { const validatedData = paymentRequestSchema.parse(req.body); await paymentController.processPayment({ ...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/payments/:id', (req: Request, res: Response, next: NextFunction) => { paymentController.getPayment(req, res, next); }); app.post('/api/payments/:id/refund', async (req: Request, res: Response, next: NextFunction) => { try { const validatedData = refundRequestSchema.parse(req.body); await paymentController.refundPayment({ ...req, body: validatedData }, res, next); } catch (error) { if (error instanceof z.ZodError) { res.status(400).json({ error: error.errors }); } else { next(error); } } }); // 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(3002, () => { console.log('Payment Service running on port 3002'); }); ``` **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, updated_at: Option>, } // User Service struct UserService { pool: diesel::r2d2::Pool>, } impl UserService { fn new(database_url: &str) -> Self { let manager = diesel::r2d2::ConnectionManager::::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> { use crate::schema::users::dsl::*; // Проверка существования пользователя let existing_user = users .filter(email.eq(&request.email)) .first::(&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::(&mut self.pool.get()?)?; Ok(user) } async fn get_user(&self, user_id: Uuid) -> Result> { use crate::schema::users::dsl::*; let user = users .filter(id.eq(user_id)) .first::(&mut self.pool.get()?)?; Ok(user) } async fn update_user(&self, user_id: Uuid, request: UpdateUserRequest) -> Result> { 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::(&mut self.pool.get()?)?; Ok(user) } async fn delete_user(&self, user_id: Uuid) -> Result<(), Box> { 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, updated_at: Option>, } // Handlers async fn create_user( user_service: web::Data, request: web::Json, ) -> Result { 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, path: web::Path, ) -> Result { 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, path: web::Path, request: web::Json, ) -> Result { 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, path: web::Path, ) -> Result { 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 экспертизы --- ## Domain-Driven Design (DDD) ### Что такое 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 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 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 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 ``` --- ## Коммуникация между сервисами ### Синхронная коммуникация #### 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 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 entity = new HttpEntity<>(request, headers); ResponseEntity 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 entity = new HttpEntity<>(refundRequest, headers); ResponseEntity 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 items; private BigDecimal totalAmount; private LocalDateTime createdAt; // конструкторы, геттеры, сеттеры } public class OrderCancelledEvent { private Long orderId; private Long customerId; private List items; private BigDecimal totalAmount; private LocalDateTime cancelledAt; // конструкторы, геттеры, сеттеры } public class InventoryReservedEvent { private Long orderId; private List 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 --- ## Управление данными в микросервисах ### База данных на сервис **Принцип:** Каждый микросервис имеет собственную базу данных. #### Преимущества: - Независимость данных - Разные типы БД для разных задач - Независимое масштабирование #### Недостатки: - Сложность транзакций - Проблемы с консистентностью - Дублирование данных ### Паттерны управления данными #### 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 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 getEvents(String aggregateId) { return eventRepository.findByAggregateIdOrderByOccurredAt(aggregateId); } } @Service public class OrderProjectionService { public OrderView buildOrderView(String orderId) { List 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** — консенсус в распределенных системах --- ## Микрофронтенды ### Что такое микрофронтенды **Микрофронтенды** — это архитектурный подход, где фронтенд приложения разбивается на независимые части. ### Принципы микрофронтендов #### 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
``` ### Управление состоянием #### 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 (
); } ``` #### 2. Custom Events ```javascript // product/ProductList.js function ProductList() { const handleAddToCart = (product) => { // отправляем событие window.dispatchEvent(new CustomEvent('addToCart', { detail: product })); }; return (
{products.map(product => ( ))}
); } // 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 (
{items.map(item => (
{item.name}
))}
); } ``` ### Преимущества микрофронтендов #### Технические: - Независимая разработка - Технологическое разнообразие - Независимое развертывание - Масштабируемость команд #### Организационные: - Независимые команды - Быстрая разработка - Специализация команд ### Недостатки микрофронтендов #### Сложность: - Сложность интеграции - Дублирование кода - Проблемы с производительностью - Сложность тестирования #### Операционные: - Сложность развертывания - Проблемы с версионированием - Сложность мониторинга --- ## Serverless архитектура и FaaS ### Что такое 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 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(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 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 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; } try { // Валидация данных if (!validateOrder(req.body)) { res.status(400).json({ error: 'Invalid order data' }); return; } // Обработка заказа const orderId = await processOrder(req.body); // Отправка уведомления 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) { return orderData.customerId && orderData.items && orderData.items.length > 0 && orderData.total > 0; } async function processOrder(orderData) { // Логика обработки заказа const orderId = `ORD-${orderData.customerId}-${Date.now()}`; // Сохранение в базу данных // await saveOrderToDatabase(orderData, orderId); return orderId; } async function sendNotification(orderId) { const topic = pubsub.topic('order-notifications'); const message = { data: Buffer.from(JSON.stringify({ orderId: orderId, message: 'Order has been processed', timestamp: new Date().toISOString() })) }; await topic.publish(message); } ``` ### Архитектурные паттерны Serverless #### 1. Event-Driven Architecture ```yaml # Serverless Event-Driven Architecture apiVersion: v1 kind: ConfigMap metadata: name: event-driven-config data: # Event sources event-sources: | - name: order-created type: api-gateway handler: order-created-handler - name: payment-processed type: message-queue handler: payment-processed-handler - name: inventory-updated type: database-trigger handler: inventory-updated-handler # Event handlers event-handlers: | - name: order-created-handler function: process-order events: [order-created] - name: payment-processed-handler function: update-order-status events: [payment-processed] - name: inventory-updated-handler function: update-product-availability events: [inventory-updated] ``` #### 2. API Gateway + Lambda Pattern ```python # API Gateway + Lambda Pattern import json from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.utilities.typing import LambdaContext logger = Logger() tracer = Tracer() @tracer.capture_lambda_handler def lambda_handler(event: dict, context: LambdaContext) -> dict: """ API Gateway + Lambda для микросервиса пользователей """ try: # Извлечение HTTP метода и пути http_method = event['httpMethod'] path = event['path'] # Маршрутизация запросов if http_method == 'GET' and path == '/users': return get_users(event) elif http_method == 'GET' and path.startswith('/users/'): user_id = path.split('/')[-1] return get_user(user_id) elif http_method == 'POST' and path == '/users': return create_user(event) elif http_method == 'PUT' and path.startswith('/users/'): user_id = path.split('/')[-1] return update_user(user_id, event) elif http_method == 'DELETE' and path.startswith('/users/'): user_id = path.split('/')[-1] return delete_user(user_id) else: return { 'statusCode': 404, 'body': json.dumps({'error': 'Not found'}) } except Exception as e: logger.error(f"Error processing request: {str(e)}") return { 'statusCode': 500, 'body': json.dumps({'error': 'Internal server error'}) } @tracer.capture_method def get_users(event: dict) -> dict: """Получение списка пользователей""" # Логика получения пользователей users = [ {'id': '1', 'name': 'John Doe', 'email': 'john@example.com'}, {'id': '2', 'name': 'Jane Smith', 'email': 'jane@example.com'} ] return { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': json.dumps(users) } @tracer.capture_method def get_user(user_id: str) -> dict: """Получение пользователя по ID""" # Логика получения пользователя user = {'id': user_id, 'name': 'John Doe', 'email': 'john@example.com'} return { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': json.dumps(user) } @tracer.capture_method def create_user(event: dict) -> dict: """Создание нового пользователя""" try: body = json.loads(event['body']) # Валидация данных if not body.get('name') or not body.get('email'): return { 'statusCode': 400, 'body': json.dumps({'error': 'Name and email are required'}) } # Создание пользователя user = { 'id': str(int(time.time())), 'name': body['name'], 'email': body['email'] } return { 'statusCode': 201, 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, 'body': json.dumps(user) } except json.JSONDecodeError: return { 'statusCode': 400, 'body': json.dumps({'error': 'Invalid JSON'}) } ``` #### 3. Event Sourcing с Serverless ```python # Event Sourcing с Serverless import json from datetime import datetime from typing import List, Dict, Any class EventStore: """Event Store для Serverless функций""" def __init__(self, dynamodb_client): self.dynamodb = dynamodb_client self.table_name = 'events' def save_event(self, aggregate_id: str, event_type: str, event_data: Dict[Any, Any]) -> str: """Сохранение события""" event_id = f"{aggregate_id}_{int(datetime.utcnow().timestamp() * 1000)}" event = { 'eventId': event_id, 'aggregateId': aggregate_id, 'eventType': event_type, 'eventData': event_data, 'timestamp': datetime.utcnow().isoformat(), 'version': self.get_next_version(aggregate_id) } self.dynamodb.put_item( TableName=self.table_name, Item=event ) return event_id def get_events(self, aggregate_id: str) -> List[Dict[Any, Any]]: """Получение событий для агрегата""" response = self.dynamodb.query( TableName=self.table_name, KeyConditionExpression='aggregateId = :aggregate_id', ExpressionAttributeValues={':aggregate_id': aggregate_id} ) return response.get('Items', []) def get_next_version(self, aggregate_id: str) -> int: """Получение следующей версии агрегата""" events = self.get_events(aggregate_id) return len(events) + 1 # Lambda функция для обработки событий def process_order_event(event, context): """Обработка события заказа""" event_store = EventStore(boto3.client('dynamodb')) # Парсинг события order_data = json.loads(event['body']) # Создание события event_id = event_store.save_event( aggregate_id=order_data['orderId'], event_type='OrderCreated', event_data={ 'customerId': order_data['customerId'], 'items': order_data['items'], 'total': order_data['total'] } ) # Обработка события if order_data.get('paymentProcessed'): event_store.save_event( aggregate_id=order_data['orderId'], event_type='PaymentProcessed', event_data={'paymentId': order_data['paymentId']} ) return { 'statusCode': 200, 'body': json.dumps({ 'eventId': event_id, 'status': 'processed' }) } ``` ### Преимущества и недостатки Serverless #### Преимущества 1. **Автоматическое масштабирование** - От 0 до тысяч экземпляров - Оплата только за использование - Нет необходимости в управлении серверами 2. **Быстрая разработка** - Фокус на бизнес-логике - Минимальная инфраструктура - Быстрое развертывание 3. **Стоимость** - Оплата только за время выполнения - Нет простоя серверов - Эффективное использование ресурсов #### Недостатки 1. **Cold Start** - Задержка при первом запуске - Влияние на производительность - Необходимость оптимизации 2. **Ограничения выполнения** - Таймауты (обычно 15 минут) - Ограничения памяти - Ограничения размера пакета 3. **Vendor Lock-in** - Привязка к конкретному провайдеру - Сложность миграции - Зависимость от API провайдера ### Оптимизация Serverless функций #### 1. Уменьшение Cold Start ```python # Оптимизация Cold Start import json import boto3 # Инициализация клиентов вне handler'а dynamodb = boto3.client('dynamodb') sns = boto3.client('sns') # Кэширование соединений def get_database_connection(): # Переиспользование соединений pass def lambda_handler(event, context): """Оптимизированная Lambda функция""" # Минимальная инициализация в handler'е try: # Быстрая обработка result = process_request(event) return { 'statusCode': 200, 'body': json.dumps(result) } except Exception as e: return { 'statusCode': 500, 'body': json.dumps({'error': str(e)}) } def process_request(event): """Основная логика обработки""" # Использование предварительно инициализированных клиентов return {'message': 'Success'} ``` #### 2. Мониторинг и логирование ```python # Мониторинг Serverless функций import json import time from aws_lambda_powertools import Logger, Tracer, Metrics from aws_lambda_powertools.utilities.typing import LambdaContext logger = Logger() tracer = Tracer() metrics = Metrics() @tracer.capture_lambda_handler @metrics.log_metrics def lambda_handler(event: dict, context: LambdaContext) -> dict: """Lambda функция с мониторингом""" # Логирование начала выполнения logger.info("Function started", extra={ "requestId": context.aws_request_id, "functionName": context.function_name }) start_time = time.time() try: # Обработка запроса result = process_request(event) # Метрики успешного выполнения metrics.add_metric(name="SuccessCount", unit="Count", value=1) metrics.add_metric(name="Duration", unit="Milliseconds", value=(time.time() - start_time) * 1000) logger.info("Function completed successfully") return { 'statusCode': 200, 'body': json.dumps(result) } except Exception as e: # Метрики ошибок metrics.add_metric(name="ErrorCount", unit="Count", value=1) logger.error(f"Function failed: {str(e)}", extra={ "error": str(e), "requestId": context.aws_request_id }) return { 'statusCode': 500, 'body': json.dumps({'error': 'Internal server error'}) } def process_request(event): """Обработка запроса с трейсингом""" with tracer.capture_method("process_request"): # Логика обработки return {'message': 'Success'} ``` ### Serverless против Микросервисов | Аспект | Serverless | Микросервисы | |--------|------------|--------------| | **Управление инфраструктурой** | Полностью автоматизировано | Требует управления | | **Масштабирование** | Автоматическое | Ручное или полуавтоматическое | | **Стоимость** | Оплата за использование | Постоянные затраты | | **Производительность** | Cold start проблемы | Стабильная производительность | | **Сложность** | Низкая | Высокая | | **Контроль** | Ограниченный | Полный | | **Vendor Lock-in** | Высокий | Низкий | ### Когда использовать Serverless #### Подходящие случаи: - **Event-driven приложения** - **API с переменной нагрузкой** - **Обработка файлов и данных** - **Интеграции между системами** - **Быстрые прототипы** #### Неподходящие случаи: - **Длительные вычисления** - **Приложения с постоянной нагрузкой** - **Требования к низкой задержке** - **Сложные stateful приложения** - **Критичные к производительности системы** --- ## Паттерны микросервисов ### 1. API Gateway **Определение:** Единая точка входа для всех клиентских запросов. #### Функции: - Маршрутизация запросов - Аутентификация и авторизация - Rate limiting - Кэширование - Мониторинг ```yaml # nginx.conf upstream user_service { server user-service:8080; } upstream order_service { server order-service:8080; } server { listen 80; location /api/users/ { proxy_pass http://user_service/; } location /api/orders/ { proxy_pass http://order_service/; } } ``` ### 2. Circuit Breaker **Определение:** Паттерн для предотвращения каскадных отказов. ```java @Component public class PaymentServiceClient { private final CircuitBreaker circuitBreaker; public PaymentServiceClient() { this.circuitBreaker = CircuitBreaker.ofDefaults("payment-service"); } public PaymentResult processPayment(PaymentRequest request) { return circuitBreaker.executeSupplier(() -> { return restTemplate.postForObject( "http://payment-service/payments", request, PaymentResult.class ); }); } } ``` ### 3. Bulkhead **Определение:** Изоляция ресурсов для предотвращения каскадных отказов. ```java @Configuration public class BulkheadConfig { @Bean public ThreadPoolExecutor paymentExecutor() { return new ThreadPoolExecutor( 10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100) ); } @Bean public ThreadPoolExecutor notificationExecutor() { return new ThreadPoolExecutor( 5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(50) ); } } ``` ### 4. Retry **Определение:** Повторные попытки при временных сбоях. ```java @Retryable(value = {ConnectException.class}, maxAttempts = 3) public PaymentResult processPayment(PaymentRequest request) { return restTemplate.postForObject( "http://payment-service/payments", request, PaymentResult.class ); } @Recover public PaymentResult recover(ConnectException ex, PaymentRequest request) { // логика восстановления return new PaymentResult(false, "Service unavailable"); } ``` ### 5. Timeout **Определение:** Ограничение времени ожидания ответа. ```java @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); factory.setConnectTimeout(5000); factory.setReadTimeout(10000); return new RestTemplate(factory); } } ``` ### 6. Service Mesh **Определение:** Инфраструктурный слой для управления коммуникацией между сервисами. #### Пример Istio: ```yaml # istio-gateway.yaml apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: api-gateway spec: selector: istio: ingressgateway servers: - port: number: 80 name: http protocol: HTTP hosts: - api.example.com --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: api-routes spec: hosts: - api.example.com gateways: - api-gateway http: - match: - uri: prefix: /api/users route: - destination: host: user-service port: number: 8080 - match: - uri: prefix: /api/orders route: - destination: host: order-service port: number: 8080 ``` --- ## Антипаттерны и проблемы ### 1. Distributed Monolith **Проблема:** Микросервисы тесно связаны друг с другом. **Симптомы:** - Изменения в одном сервисе требуют изменений в других - Общие базы данных - Синхронные вызовы между всеми сервисами **Решение:** - Правильное разделение доменов - Асинхронная коммуникация - Независимые базы данных ### 2. Database per Service Anti-pattern **Проблема:** Неправильное разделение данных. **Симптомы:** - Сложные JOIN'ы между сервисами - Дублирование данных - Проблемы с консистентностью **Решение:** - Правильное разделение данных по доменам - Использование событий для синхронизации - CQRS для разделения чтения и записи ### 3. Chatty Services **Проблема:** Слишком много мелких запросов между сервисами. **Симптомы:** - Высокая сетевая задержка - Проблемы с производительностью - Сложность отладки **Решение:** - Batch операции - Кэширование - Агрегация данных ### 4. Shared Database **Проблема:** Несколько сервисов используют одну базу данных. **Симптомы:** - Связанность сервисов - Проблемы с развертыванием - Сложность масштабирования **Решение:** - Разделение баз данных - Миграция данных - Использование событий ### 5. Service Explosion **Проблема:** Слишком много мелких сервисов. **Симптомы:** - Сложность управления - Высокий overhead - Проблемы с мониторингом **Решение:** - Объединение связанных сервисов - Правильное разделение доменов - Использование модулей вместо сервисов ### 6. Versioning Hell **Проблема:** Проблемы с версионированием API. **Симптомы:** - Breaking changes - Сложность миграции - Проблемы с совместимостью **Решение:** - Semantic versioning - Backward compatibility - Graceful deprecation --- ## Практические задания ### Задание 1: Проектирование микросервисов Спроектируйте микросервисную архитектуру для системы интернет-магазина: 1. Определите домены и ограниченные контексты 2. Спроектируйте сервисы и их взаимодействие 3. Определите данные для каждого сервиса 4. Создайте контекстную карту ### Задание 2: Реализация Saga Реализуйте Saga для процесса создания заказа: 1. Создание заказа 2. Резервирование товаров 3. Обработка платежа 4. Подтверждение заказа 5. Компенсирующие транзакции ### Задание 3: Микрофронтенды Создайте простую систему микрофронтендов: 1. Shell приложение 2. Product микрофронтенд 3. Cart микрофронтенд 4. Коммуникация между ними --- ## Дополнительные ресурсы ### Книги - "Building Microservices" by Sam Newman - "Microservices Patterns" by Chris Richardson - "Domain-Driven Design" by Eric Evans - "Implementing Domain-Driven Design" by Vaughn Vernon ### Онлайн ресурсы - [Microservices.io](https://microservices.io/) - [Martin Fowler's Microservices](https://martinfowler.com/articles/microservices.html) - [Micro Frontends](https://micro-frontends.org/) ### Инструменты - **Service Mesh:** Istio, Linkerd, Consul Connect - **API Gateway:** Kong, Zuul, AWS API Gateway - **Event Streaming:** Apache Kafka, Apache Pulsar - **Service Discovery:** Consul, Eureka, etcd --- ## Контрольные вопросы 1. Что такое микросервисная архитектура и каковы её принципы? 2. Объясните концепцию Domain-Driven Design. 3. Какие паттерны коммуникации между сервисами вы знаете? 4. Как управлять данными в микросервисах? 5. Что такое микрофронтенды и как они работают? 6. Какие антипаттерны микросервисов вы знаете? 7. Как обеспечить отказоустойчивость в микросервисах? 8. Когда стоит использовать микросервисы, а когда нет?