Files
Alexander Abakumov 15e744da49 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
2025-09-09 22:08:07 +03:00

3368 lines
101 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Модуль 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<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');
});
```
**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<PaymentResult> {
// Валидация
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<any> {
const payment = await this.paymentRepository.findById(id);
if (!payment) {
throw new Error('Payment not found');
}
return payment;
}
async refundPayment(id: number, request: RefundRequest): Promise<RefundResult> {
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<any>;
findById(id: number): Promise<any>;
}
class PrismaPaymentRepository implements PaymentRepository {
async save(payment: any): Promise<any> {
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<any> {
return await prisma.payment.findUnique({
where: { id }
});
}
}
// Payment Gateway (mock implementation)
class PaymentGateway {
async processPayment(request: PaymentRequest): Promise<any> {
// 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<any> {
// 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<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 экспертизы
---
## 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<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
```
---
## Коммуникация между сервисами
### Синхронная коммуникация
#### 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
---
## Управление данными в микросервисах
### База данных на сервис
**Принцип:** Каждый микросервис имеет собственную базу данных.
#### Преимущества:
- Независимость данных
- Разные типы БД для разных задач
- Независимое масштабирование
#### Недостатки:
- Сложность транзакций
- Проблемы с консистентностью
- Дублирование данных
### Паттерны управления данными
#### 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** — консенсус в распределенных системах
---
## Микрофронтенды
### Что такое микрофронтенды
**Микрофронтенды** — это архитектурный подход, где фронтенд приложения разбивается на независимые части.
### Принципы микрофронтендов
#### 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>
);
}
```
### Преимущества микрофронтендов
#### Технические:
- Независимая разработка
- Технологическое разнообразие
- Независимое развертывание
- Масштабируемость команд
#### Организационные:
- Независимые команды
- Быстрая разработка
- Специализация команд
### Недостатки микрофронтендов
#### Сложность:
- Сложность интеграции
- Дублирование кода
- Проблемы с производительностью
- Сложность тестирования
#### Операционные:
- Сложность развертывания
- Проблемы с версионированием
- Сложность мониторинга
---
## 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<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;
}
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. Когда стоит использовать микросервисы, а когда нет?