- 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
3368 lines
101 KiB
Markdown
3368 lines
101 KiB
Markdown
# Модуль 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. Когда стоит использовать микросервисы, а когда нет?
|