- 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
101 KiB
Модуль 2: Микросервисная архитектура
Содержание модуля
- Основы микросервисной архитектуры
- Domain-Driven Design (DDD)
- Коммуникация между сервисами
- Управление данными в микросервисах
- Микрофронтенды
- Serverless архитектура и FaaS
- Паттерны микросервисов
- Антипаттерны и проблемы
Основы микросервисной архитектуры
Определение микросервисов
Микросервис — это небольшая, независимая, слабо связанная служба, которая:
- Имеет единственную ответственность
- Развертывается независимо
- Имеет собственные данные
- Коммуницирует через четко определенные API
Принципы микросервисной архитектуры
1. Single Responsibility Principle
Каждый микросервис должен решать одну бизнес-задачу.
Пример:
User Service → управление пользователями
Payment Service → обработка платежей
Order Service → управление заказами
Notification Service → отправка уведомлений
Технические примеры:
Java (Spring Boot) - User Service:
// 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 - только обработка платежей:
// 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:
// 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:
// 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:
// 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:
# 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 (Сущность)
Определение: Объект с уникальной идентичностью.
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 (Объект-значение)
Определение: Объект без идентичности, определяемый только своими атрибутами.
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 (Агрегат)
Определение: Граница консистентности данных.
Правила агрегатов:
- Один агрегат = одна транзакция
- Ссылки только на корень агрегата
- Обновления только через корень
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 (Репозиторий)
Определение: Интерфейс для доступа к агрегатам.
public interface OrderRepository {
Order findById(OrderId id);
void save(Order order);
List<Order> findByCustomerId(CustomerId customerId);
}
5. Domain Service (Доменный сервис)
Определение: Операция, которая не принадлежит ни одной сущности.
public class OrderPricingService {
public Money calculateTotalPrice(Order order, PricingRules rules) {
// сложная логика расчета цены
// не принадлежит ни Order, ни PricingRules
}
}
6. Domain Event (Доменное событие)
Определение: Событие, произошедшее в домене.
public class OrderCreatedEvent {
private final OrderId orderId;
private final CustomerId customerId;
private final LocalDateTime occurredAt;
// конструктор и геттеры
}
Event Storming (Мозговой штурм событий)
Event Storming — методология для изучения домена через события.
Процесс:
- Доменные события — что происходит в системе
- Команды — что вызывает события
- Агрегаты — что обрабатывает команды
- Политики — бизнес-правила
- Read Models — что нужно для отображения
Пример:
Команда: CreateOrder
↓
Агрегат: Order
↓
Событие: OrderCreated
↓
Политика: SendConfirmationEmail
↓
Команда: SendEmail
Коммуникация между сервисами
Синхронная коммуникация
HTTP/REST
Преимущества:
- Простота
- Широкая поддержка
- Стандартизация
Недостатки:
- Связанность
- Блокирующие вызовы
- Проблемы с производительностью
Технический пример - HTTP/REST коммуникация:
// 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:
// 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
@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
@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
// 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
@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)
Определение: Разделение команд и запросов.
// 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
Определение: Хранение событий вместо состояния.
@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)
// 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
// 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
<!-- 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
// 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
// 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
// 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
// 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
# 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
// 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
// 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
# 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
# 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
# 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
Преимущества
-
Автоматическое масштабирование
- От 0 до тысяч экземпляров
- Оплата только за использование
- Нет необходимости в управлении серверами
-
Быстрая разработка
- Фокус на бизнес-логике
- Минимальная инфраструктура
- Быстрое развертывание
-
Стоимость
- Оплата только за время выполнения
- Нет простоя серверов
- Эффективное использование ресурсов
Недостатки
-
Cold Start
- Задержка при первом запуске
- Влияние на производительность
- Необходимость оптимизации
-
Ограничения выполнения
- Таймауты (обычно 15 минут)
- Ограничения памяти
- Ограничения размера пакета
-
Vendor Lock-in
- Привязка к конкретному провайдеру
- Сложность миграции
- Зависимость от API провайдера
Оптимизация Serverless функций
1. Уменьшение Cold Start
# Оптимизация 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. Мониторинг и логирование
# Мониторинг 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
- Кэширование
- Мониторинг
# 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
Определение: Паттерн для предотвращения каскадных отказов.
@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
Определение: Изоляция ресурсов для предотвращения каскадных отказов.
@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
Определение: Повторные попытки при временных сбоях.
@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
Определение: Ограничение времени ожидания ответа.
@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:
# 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: Проектирование микросервисов
Спроектируйте микросервисную архитектуру для системы интернет-магазина:
- Определите домены и ограниченные контексты
- Спроектируйте сервисы и их взаимодействие
- Определите данные для каждого сервиса
- Создайте контекстную карту
Задание 2: Реализация Saga
Реализуйте Saga для процесса создания заказа:
- Создание заказа
- Резервирование товаров
- Обработка платежа
- Подтверждение заказа
- Компенсирующие транзакции
Задание 3: Микрофронтенды
Создайте простую систему микрофронтендов:
- Shell приложение
- Product микрофронтенд
- Cart микрофронтенд
- Коммуникация между ними
Дополнительные ресурсы
Книги
- "Building Microservices" by Sam Newman
- "Microservices Patterns" by Chris Richardson
- "Domain-Driven Design" by Eric Evans
- "Implementing Domain-Driven Design" by Vaughn Vernon
Онлайн ресурсы
Инструменты
- Service Mesh: Istio, Linkerd, Consul Connect
- API Gateway: Kong, Zuul, AWS API Gateway
- Event Streaming: Apache Kafka, Apache Pulsar
- Service Discovery: Consul, Eureka, etcd
Контрольные вопросы
- Что такое микросервисная архитектура и каковы её принципы?
- Объясните концепцию Domain-Driven Design.
- Какие паттерны коммуникации между сервисами вы знаете?
- Как управлять данными в микросервисах?
- Что такое микрофронтенды и как они работают?
- Какие антипаттерны микросервисов вы знаете?
- Как обеспечить отказоустойчивость в микросервисах?
- Когда стоит использовать микросервисы, а когда нет?