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

101 KiB
Raw Permalink Blame History

Модуль 2: Микросервисная архитектура

Содержание модуля

  1. Основы микросервисной архитектуры
  2. Domain-Driven Design (DDD)
  3. Коммуникация между сервисами
  4. Управление данными в микросервисах
  5. Микрофронтенды
  6. Serverless архитектура и FaaS
  7. Паттерны микросервисов
  8. Антипаттерны и проблемы

Основы микросервисной архитектуры

Определение микросервисов

Микросервис — это небольшая, независимая, слабо связанная служба, которая:

  • Имеет единственную ответственность
  • Развертывается независимо
  • Имеет собственные данные
  • Коммуницирует через четко определенные API

Принципы микросервисной архитектуры

1. Single Responsibility Principle

Каждый микросервис должен решать одну бизнес-задачу.

Пример:

User Service → управление пользователями
Payment Service → обработка платежей
Order Service → управление заказами
Notification Service → отправка уведомлений

Технические примеры:

Java (Spring Boot) - User Service:

// User Service - только управление пользователями
@SpringBootApplication
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;
    
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) {
        User user = userService.createUser(request);
        return ResponseEntity.ok(user);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.getUser(id);
        return ResponseEntity.ok(user);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody UpdateUserRequest request) {
        User user = userService.updateUser(id, request);
        return ResponseEntity.ok(user);
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.ok().build();
    }
}

@Service
@Transactional
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User createUser(CreateUserRequest request) {
        // Валидация
        if (userRepository.existsByEmail(request.getEmail())) {
            throw new UserAlreadyExistsException("User with email already exists");
        }
        
        // Создание пользователя
        User user = new User();
        user.setName(request.getName());
        user.setEmail(request.getEmail());
        user.setPasswordHash(passwordEncoder.encode(request.getPassword()));
        user.setCreatedAt(LocalDateTime.now());
        
        return userRepository.save(user);
    }
    
    public User getUser(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found"));
    }
    
    public User updateUser(Long id, UpdateUserRequest request) {
        User user = getUser(id);
        user.setName(request.getName());
        user.setUpdatedAt(LocalDateTime.now());
        return userRepository.save(user);
    }
    
    public void deleteUser(Long id) {
        User user = getUser(id);
        userRepository.delete(user);
    }
}

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(unique = true, nullable = false)
    private String email;
    
    @Column(nullable = false)
    private String name;
    
    @Column(name = "password_hash", nullable = false)
    private String passwordHash;
    
    @Column(name = "created_at", nullable = false)
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    // геттеры и сеттеры
}

Payment Service - только обработка платежей:

// Payment Service - только обработка платежей
@SpringBootApplication
public class PaymentServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(PaymentServiceApplication.class, args);
    }
}

@RestController
@RequestMapping("/api/payments")
public class PaymentController {
    @Autowired
    private PaymentService paymentService;
    
    @PostMapping("/process")
    public ResponseEntity<PaymentResult> processPayment(@RequestBody PaymentRequest request) {
        PaymentResult result = paymentService.processPayment(request);
        return ResponseEntity.ok(result);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<Payment> getPayment(@PathVariable Long id) {
        Payment payment = paymentService.getPayment(id);
        return ResponseEntity.ok(payment);
    }
    
    @PostMapping("/{id}/refund")
    public ResponseEntity<RefundResult> refundPayment(@PathVariable Long id, @RequestBody RefundRequest request) {
        RefundResult result = paymentService.refundPayment(id, request);
        return ResponseEntity.ok(result);
    }
}

@Service
@Transactional
public class PaymentService {
    @Autowired
    private PaymentRepository paymentRepository;
    @Autowired
    private PaymentGatewayClient paymentGatewayClient;
    
    public PaymentResult processPayment(PaymentRequest request) {
        // Валидация платежа
        validatePaymentRequest(request);
        
        // Создание записи о платеже
        Payment payment = new Payment();
        payment.setAmount(request.getAmount());
        payment.setCurrency(request.getCurrency());
        payment.setPaymentMethod(request.getPaymentMethod());
        payment.setStatus(PaymentStatus.PENDING);
        payment.setCreatedAt(LocalDateTime.now());
        
        payment = paymentRepository.save(payment);
        
        try {
            // Обработка через платежный шлюз
            PaymentGatewayResponse response = paymentGatewayClient.processPayment(request);
            
            if (response.isSuccess()) {
                payment.setStatus(PaymentStatus.COMPLETED);
                payment.setTransactionId(response.getTransactionId());
                payment.setProcessedAt(LocalDateTime.now());
            } else {
                payment.setStatus(PaymentStatus.FAILED);
                payment.setFailureReason(response.getErrorMessage());
            }
            
            payment = paymentRepository.save(payment);
            
            return new PaymentResult(
                payment.getId(),
                payment.getStatus(),
                response.getTransactionId(),
                response.getErrorMessage()
            );
            
        } catch (Exception e) {
            payment.setStatus(PaymentStatus.FAILED);
            payment.setFailureReason(e.getMessage());
            paymentRepository.save(payment);
            
            throw new PaymentProcessingException("Payment processing failed", e);
        }
    }
    
    private void validatePaymentRequest(PaymentRequest request) {
        if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new InvalidPaymentAmountException("Payment amount must be positive");
        }
        
        if (request.getPaymentMethod() == null) {
            throw new InvalidPaymentMethodException("Payment method is required");
        }
    }
    
    public Payment getPayment(Long id) {
        return paymentRepository.findById(id)
            .orElseThrow(() -> new PaymentNotFoundException("Payment not found"));
    }
    
    public RefundResult refundPayment(Long id, RefundRequest request) {
        Payment payment = getPayment(id);
        
        if (payment.getStatus() != PaymentStatus.COMPLETED) {
            throw new InvalidRefundException("Only completed payments can be refunded");
        }
        
        // Логика возврата средств
        RefundGatewayResponse response = paymentGatewayClient.refundPayment(
            payment.getTransactionId(), 
            request.getAmount()
        );
        
        if (response.isSuccess()) {
            payment.setStatus(PaymentStatus.REFUNDED);
            payment.setRefundedAt(LocalDateTime.now());
            paymentRepository.save(payment);
        }
        
        return new RefundResult(response.isSuccess(), response.getRefundId());
    }
}

@Entity
@Table(name = "payments")
public class Payment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private BigDecimal amount;
    
    @Column(nullable = false)
    private String currency;
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private PaymentMethod paymentMethod;
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private PaymentStatus status;
    
    @Column(name = "transaction_id")
    private String transactionId;
    
    @Column(name = "failure_reason")
    private String failureReason;
    
    @Column(name = "created_at", nullable = false)
    private LocalDateTime createdAt;
    
    @Column(name = "processed_at")
    private LocalDateTime processedAt;
    
    @Column(name = "refunded_at")
    private LocalDateTime refundedAt;
    
    // геттеры и сеттеры
}

Node.js (Express + TypeScript + Prisma) - User Service:

// User Service - только управление пользователями
import express, { Request, Response, NextFunction } from 'express';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
import bcrypt from 'bcrypt';

const app = express();
const prisma = new PrismaClient();

// Interfaces
interface CreateUserRequest {
  name: string;
  email: string;
  password: string;
}

interface UpdateUserRequest {
  name: string;
}

interface User {
  id: number;
  name: string;
  email: string;
  passwordHash: string;
  createdAt: Date;
  updatedAt: Date | null;
}

// User Controller
class UserController {
  constructor(private userService: UserService) {}

  async createUser(req: Request, res: Response, next: NextFunction) {
    try {
      const user = await this.userService.createUser(req.body);
      res.json(user);
    } catch (error) {
      next(error);
    }
  }

  async getUser(req: Request, res: Response, next: NextFunction) {
    try {
      const id = parseInt(req.params.id);
      const user = await this.userService.getUser(id);
      res.json(user);
    } catch (error) {
      next(error);
    }
  }

  async updateUser(req: Request, res: Response, next: NextFunction) {
    try {
      const id = parseInt(req.params.id);
      const user = await this.userService.updateUser(id, req.body);
      res.json(user);
    } catch (error) {
      next(error);
    }
  }

  async deleteUser(req: Request, res: Response, next: NextFunction) {
    try {
      const id = parseInt(req.params.id);
      await this.userService.deleteUser(id);
      res.status(204).send();
    } catch (error) {
      next(error);
    }
  }
}

// User Service
class UserService {
  constructor(private userRepository: UserRepository) {}

  async createUser(request: CreateUserRequest): Promise<User> {
    // Валидация
    const existingUser = await this.userRepository.findByEmail(request.email);
    if (existingUser) {
      throw new Error('User with email already exists');
    }

    // Создание пользователя
    const passwordHash = await bcrypt.hash(request.password, 10);
    const user = {
      name: request.name,
      email: request.email,
      passwordHash,
      createdAt: new Date(),
      updatedAt: null
    };

    return await this.userRepository.save(user);
  }

  async getUser(id: number): Promise<User> {
    const user = await this.userRepository.findById(id);
    if (!user) {
      throw new Error('User not found');
    }
    return user;
  }

  async updateUser(id: number, request: UpdateUserRequest): Promise<User> {
    const user = await this.getUser(id);
    user.name = request.name;
    user.updatedAt = new Date();
    return await this.userRepository.save(user);
  }

  async deleteUser(id: number): Promise<void> {
    const user = await this.getUser(id);
    await this.userRepository.delete(user.id);
  }
}

// User Repository
interface UserRepository {
  save(user: Omit<User, 'id'>): Promise<User>;
  findById(id: number): Promise<User | null>;
  findByEmail(email: string): Promise<User | null>;
  delete(id: number): Promise<void>;
}

class PrismaUserRepository implements UserRepository {
  async save(user: Omit<User, 'id'>): Promise<User> {
    const savedUser = await prisma.user.create({
      data: {
        name: user.name,
        email: user.email,
        passwordHash: user.passwordHash,
        createdAt: user.createdAt,
        updatedAt: user.updatedAt
      }
    });

    return {
      id: savedUser.id,
      name: savedUser.name,
      email: savedUser.email,
      passwordHash: savedUser.passwordHash,
      createdAt: savedUser.createdAt,
      updatedAt: savedUser.updatedAt
    };
  }

  async findById(id: number): Promise<User | null> {
    const user = await prisma.user.findUnique({
      where: { id }
    });

    if (!user) return null;

    return {
      id: user.id,
      name: user.name,
      email: user.email,
      passwordHash: user.passwordHash,
      createdAt: user.createdAt,
      updatedAt: user.updatedAt
    };
  }

  async findByEmail(email: string): Promise<User | null> {
    const user = await prisma.user.findUnique({
      where: { email }
    });

    if (!user) return null;

    return {
      id: user.id,
      name: user.name,
      email: user.email,
      passwordHash: user.passwordHash,
      createdAt: user.createdAt,
      updatedAt: user.updatedAt
    };
  }

  async delete(id: number): Promise<void> {
    await prisma.user.delete({
      where: { id }
    });
  }
}

// Validation schemas
const createUserSchema = z.object({
  name: z.string().min(1, 'Name is required'),
  email: z.string().email('Valid email is required'),
  password: z.string().min(6, 'Password must be at least 6 characters')
});

const updateUserSchema = z.object({
  name: z.string().min(1, 'Name is required')
});

// Dependency injection
const userRepository = new PrismaUserRepository();
const userService = new UserService(userRepository);
const userController = new UserController(userService);

// Routes
app.post('/api/users', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const validatedData = createUserSchema.parse(req.body);
    await userController.createUser({ ...req, body: validatedData }, res, next);
  } catch (error) {
    if (error instanceof z.ZodError) {
      res.status(400).json({ error: error.errors });
    } else {
      next(error);
    }
  }
});

app.get('/api/users/:id', (req: Request, res: Response, next: NextFunction) => {
  userController.getUser(req, res, next);
});

app.put('/api/users/:id', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const validatedData = updateUserSchema.parse(req.body);
    await userController.updateUser({ ...req, body: validatedData }, res, next);
  } catch (error) {
    if (error instanceof z.ZodError) {
      res.status(400).json({ error: error.errors });
    } else {
      next(error);
    }
  }
});

app.delete('/api/users/:id', (req: Request, res: Response, next: NextFunction) => {
  userController.deleteUser(req, res, next);
});

// Error handling middleware
app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(error);
  res.status(500).json({ error: 'Internal server error' });
});

app.listen(3001, () => {
  console.log('User Service running on port 3001');
});

Node.js (Express + TypeScript + Prisma) - Payment Service:

// Payment Service - только обработка платежей
import express, { Request, Response, NextFunction } from 'express';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';

const app = express();
const prisma = new PrismaClient();

// Interfaces
interface PaymentRequest {
  amount: number;
  currency: string;
  paymentMethod: string;
  orderId: number;
  customerId: number;
}

interface PaymentResult {
  id: number;
  status: string;
  transactionId?: string;
  errorMessage?: string;
}

interface RefundRequest {
  amount: number;
  reason: string;
}

interface RefundResult {
  id: number;
  status: string;
  refundId?: string;
  errorMessage?: string;
}

// Payment Controller
class PaymentController {
  constructor(private paymentService: PaymentService) {}

  async processPayment(req: Request, res: Response, next: NextFunction) {
    try {
      const result = await this.paymentService.processPayment(req.body);
      res.json(result);
    } catch (error) {
      next(error);
    }
  }

  async getPayment(req: Request, res: Response, next: NextFunction) {
    try {
      const id = parseInt(req.params.id);
      const payment = await this.paymentService.getPayment(id);
      res.json(payment);
    } catch (error) {
      next(error);
    }
  }

  async refundPayment(req: Request, res: Response, next: NextFunction) {
    try {
      const id = parseInt(req.params.id);
      const result = await this.paymentService.refundPayment(id, req.body);
      res.json(result);
    } catch (error) {
      next(error);
    }
  }
}

// Payment Service
class PaymentService {
  constructor(
    private paymentRepository: PaymentRepository,
    private paymentGateway: PaymentGateway
  ) {}

  async processPayment(request: PaymentRequest): Promise<PaymentResult> {
    // Валидация
    this.validatePaymentRequest(request);

    // Создание платежа
    const payment = {
      amount: request.amount,
      currency: request.currency,
      paymentMethod: request.paymentMethod,
      orderId: request.orderId,
      customerId: request.customerId,
      status: 'PENDING',
      createdAt: new Date(),
      processedAt: null,
      refundedAt: null
    };

    const savedPayment = await this.paymentRepository.save(payment);

    try {
      // Обработка через платежный шлюз
      const response = await this.paymentGateway.processPayment(request);

      if (response.success) {
        savedPayment.status = 'COMPLETED';
        savedPayment.transactionId = response.transactionId;
        savedPayment.processedAt = new Date();
      } else {
        savedPayment.status = 'FAILED';
        savedPayment.failureReason = response.errorMessage;
      }

      await this.paymentRepository.save(savedPayment);

      return new PaymentResult(
        savedPayment.id,
        savedPayment.status,
        response.transactionId,
        response.errorMessage
      );

    } catch (error) {
      savedPayment.status = 'FAILED';
      savedPayment.failureReason = error.message;
      await this.paymentRepository.save(savedPayment);

      throw new Error(`Payment processing failed: ${error.message}`);
    }
  }

  private validatePaymentRequest(request: PaymentRequest): void {
    if (request.amount <= 0) {
      throw new Error('Payment amount must be positive');
    }

    if (!request.paymentMethod) {
      throw new Error('Payment method is required');
    }
  }

  async getPayment(id: number): Promise<any> {
    const payment = await this.paymentRepository.findById(id);
    if (!payment) {
      throw new Error('Payment not found');
    }
    return payment;
  }

  async refundPayment(id: number, request: RefundRequest): Promise<RefundResult> {
    const payment = await this.getPayment(id);

    if (payment.status !== 'COMPLETED') {
      throw new Error('Only completed payments can be refunded');
    }

    try {
      const refundResponse = await this.paymentGateway.refundPayment({
        transactionId: payment.transactionId,
        amount: request.amount,
        reason: request.reason
      });

      payment.status = 'REFUNDED';
      payment.refundedAt = new Date();
      await this.paymentRepository.save(payment);

      return new RefundResult(
        payment.id,
        'REFUNDED',
        refundResponse.refundId
      );

    } catch (error) {
      throw new Error(`Refund failed: ${error.message}`);
    }
  }
}

// Payment Repository
interface PaymentRepository {
  save(payment: any): Promise<any>;
  findById(id: number): Promise<any>;
}

class PrismaPaymentRepository implements PaymentRepository {
  async save(payment: any): Promise<any> {
    return await prisma.payment.create({
      data: {
        amount: payment.amount,
        currency: payment.currency,
        paymentMethod: payment.paymentMethod,
        orderId: payment.orderId,
        customerId: payment.customerId,
        status: payment.status,
        transactionId: payment.transactionId,
        failureReason: payment.failureReason,
        createdAt: payment.createdAt,
        processedAt: payment.processedAt,
        refundedAt: payment.refundedAt
      }
    });
  }

  async findById(id: number): Promise<any> {
    return await prisma.payment.findUnique({
      where: { id }
    });
  }
}

// Payment Gateway (mock implementation)
class PaymentGateway {
  async processPayment(request: PaymentRequest): Promise<any> {
    // Mock implementation - в реальности здесь будет интеграция с платежным шлюзом
    return new Promise((resolve) => {
      setTimeout(() => {
        if (Math.random() > 0.1) { // 90% success rate
          resolve({
            success: true,
            transactionId: `txn_${Date.now()}`,
            errorMessage: null
          });
        } else {
          resolve({
            success: false,
            transactionId: null,
            errorMessage: 'Payment gateway error'
          });
        }
      }, 1000);
    });
  }

  async refundPayment(request: any): Promise<any> {
    // Mock implementation
    return {
      refundId: `refund_${Date.now()}`
    };
  }
}

// Validation schemas
const paymentRequestSchema = z.object({
  amount: z.number().positive('Amount must be positive'),
  currency: z.string().min(3, 'Currency is required'),
  paymentMethod: z.string().min(1, 'Payment method is required'),
  orderId: z.number().positive('Order ID is required'),
  customerId: z.number().positive('Customer ID is required')
});

const refundRequestSchema = z.object({
  amount: z.number().positive('Amount must be positive'),
  reason: z.string().min(1, 'Reason is required')
});

// Dependency injection
const paymentRepository = new PrismaPaymentRepository();
const paymentGateway = new PaymentGateway();
const paymentService = new PaymentService(paymentRepository, paymentGateway);
const paymentController = new PaymentController(paymentService);

// Routes
app.post('/api/payments/process', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const validatedData = paymentRequestSchema.parse(req.body);
    await paymentController.processPayment({ ...req, body: validatedData }, res, next);
  } catch (error) {
    if (error instanceof z.ZodError) {
      res.status(400).json({ error: error.errors });
    } else {
      next(error);
    }
  }
});

app.get('/api/payments/:id', (req: Request, res: Response, next: NextFunction) => {
  paymentController.getPayment(req, res, next);
});

app.post('/api/payments/:id/refund', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const validatedData = refundRequestSchema.parse(req.body);
    await paymentController.refundPayment({ ...req, body: validatedData }, res, next);
  } catch (error) {
    if (error instanceof z.ZodError) {
      res.status(400).json({ error: error.errors });
    } else {
      next(error);
    }
  }
});

// Error handling middleware
app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(error);
  res.status(500).json({ error: 'Internal server error' });
});

app.listen(3002, () => {
  console.log('Payment Service running on port 3002');
});

Rust (Actix Web + Diesel) - User Service:

// User Service - только управление пользователями
use actix_web::{web, App, HttpResponse, HttpServer, Result};
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Serialize, Deserialize)]
struct CreateUserRequest {
    name: String,
    email: String,
    password: String,
}

#[derive(Serialize, Deserialize)]
struct UpdateUserRequest {
    name: String,
}

#[derive(Serialize, Deserialize, Queryable)]
struct User {
    id: Uuid,
    name: String,
    email: String,
    password_hash: String,
    created_at: chrono::DateTime<chrono::Utc>,
    updated_at: Option<chrono::DateTime<chrono::Utc>>,
}

// User Service
struct UserService {
    pool: diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<PgConnection>>,
}

impl UserService {
    fn new(database_url: &str) -> Self {
        let manager = diesel::r2d2::ConnectionManager::<PgConnection>::new(database_url);
        let pool = diesel::r2d2::Pool::builder()
            .build(manager)
            .expect("Failed to create pool");
        
        Self { pool }
    }

    async fn create_user(&self, request: CreateUserRequest) -> Result<User, Box<dyn std::error::Error>> {
        use crate::schema::users::dsl::*;
        
        // Проверка существования пользователя
        let existing_user = users
            .filter(email.eq(&request.email))
            .first::<User>(&mut self.pool.get()?)
            .optional()?;
        
        if existing_user.is_some() {
            return Err("User with email already exists".into());
        }

        // Хеширование пароля
        let password_hash = bcrypt::hash(&request.password, bcrypt::DEFAULT_COST)?;
        
        let new_user = NewUser {
            id: Uuid::new_v4(),
            name: request.name,
            email: request.email,
            password_hash,
            created_at: chrono::Utc::now(),
            updated_at: None,
        };

        let user = diesel::insert_into(users)
            .values(&new_user)
            .get_result::<User>(&mut self.pool.get()?)?;

        Ok(user)
    }

    async fn get_user(&self, user_id: Uuid) -> Result<User, Box<dyn std::error::Error>> {
        use crate::schema::users::dsl::*;
        
        let user = users
            .filter(id.eq(user_id))
            .first::<User>(&mut self.pool.get()?)?;
        
        Ok(user)
    }

    async fn update_user(&self, user_id: Uuid, request: UpdateUserRequest) -> Result<User, Box<dyn std::error::Error>> {
        use crate::schema::users::dsl::*;
        
        let user = diesel::update(users.filter(id.eq(user_id)))
            .set((
                name.eq(request.name),
                updated_at.eq(Some(chrono::Utc::now())),
            ))
            .get_result::<User>(&mut self.pool.get()?)?;
        
        Ok(user)
    }

    async fn delete_user(&self, user_id: Uuid) -> Result<(), Box<dyn std::error::Error>> {
        use crate::schema::users::dsl::*;
        
        diesel::delete(users.filter(id.eq(user_id)))
            .execute(&mut self.pool.get()?)?;
        
        Ok(())
    }
}

#[derive(Insertable)]
#[diesel(table_name = users)]
struct NewUser {
    id: Uuid,
    name: String,
    email: String,
    password_hash: String,
    created_at: chrono::DateTime<chrono::Utc>,
    updated_at: Option<chrono::DateTime<chrono::Utc>>,
}

// Handlers
async fn create_user(
    user_service: web::Data<UserService>,
    request: web::Json<CreateUserRequest>,
) -> Result<HttpResponse, actix_web::Error> {
    match user_service.create_user(request.into_inner()).await {
        Ok(user) => Ok(HttpResponse::Ok().json(user)),
        Err(e) => Ok(HttpResponse::BadRequest().json(serde_json::json!({
            "error": e.to_string()
        }))),
    }
}

async fn get_user(
    user_service: web::Data<UserService>,
    path: web::Path<Uuid>,
) -> Result<HttpResponse, actix_web::Error> {
    let user_id = path.into_inner();
    
    match user_service.get_user(user_id).await {
        Ok(user) => Ok(HttpResponse::Ok().json(user)),
        Err(_) => Ok(HttpResponse::NotFound().json(serde_json::json!({
            "error": "User not found"
        }))),
    }
}

async fn update_user(
    user_service: web::Data<UserService>,
    path: web::Path<Uuid>,
    request: web::Json<UpdateUserRequest>,
) -> Result<HttpResponse, actix_web::Error> {
    let user_id = path.into_inner();
    
    match user_service.update_user(user_id, request.into_inner()).await {
        Ok(user) => Ok(HttpResponse::Ok().json(user)),
        Err(e) => Ok(HttpResponse::BadRequest().json(serde_json::json!({
            "error": e.to_string()
        }))),
    }
}

async fn delete_user(
    user_service: web::Data<UserService>,
    path: web::Path<Uuid>,
) -> Result<HttpResponse, actix_web::Error> {
    let user_id = path.into_inner();
    
    match user_service.delete_user(user_id).await {
        Ok(_) => Ok(HttpResponse::NoContent().finish()),
        Err(e) => Ok(HttpResponse::BadRequest().json(serde_json::json!({
            "error": e.to_string()
        }))),
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let database_url = std::env::var("DATABASE_URL")
        .expect("DATABASE_URL must be set");
    
    let user_service = UserService::new(&database_url);
    
    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(user_service.clone()))
            .route("/api/users", web::post().to(create_user))
            .route("/api/users/{id}", web::get().to(get_user))
            .route("/api/users/{id}", web::put().to(update_user))
            .route("/api/users/{id}", web::delete().to(delete_user))
    })
    .bind("127.0.0.1:3001")?
    .run()
    .await
}

Python (FastAPI + SQLAlchemy) - User Service:

# User Service - только управление пользователей
from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy import create_engine, Column, String, DateTime, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from pydantic import BaseModel, EmailStr
from passlib.context import CryptContext
from datetime import datetime
from typing import Optional
import uuid

app = FastAPI(title="User Service", version="1.0.0")

# Database setup
DATABASE_URL = "postgresql://user:password@localhost/userdb"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# Database Models
class User(Base):
    __tablename__ = "users"
    
    id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
    name = Column(String, nullable=False)
    email = Column(String, unique=True, nullable=False)
    password_hash = Column(String, nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, nullable=True)

# Pydantic Models
class CreateUserRequest(BaseModel):
    name: str
    email: EmailStr
    password: str

class UpdateUserRequest(BaseModel):
    name: str

class UserResponse(BaseModel):
    id: str
    name: str
    email: str
    created_at: datetime
    updated_at: Optional[datetime]
    
    class Config:
        from_attributes = True

# Dependency
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

# User Service
class UserService:
    def __init__(self, db: Session):
        self.db = db
    
    def create_user(self, request: CreateUserRequest) -> User:
        # Проверка существования пользователя
        existing_user = self.db.query(User).filter(User.email == request.email).first()
        if existing_user:
            raise HTTPException(status_code=400, detail="User with email already exists")
        
        # Хеширование пароля
        password_hash = pwd_context.hash(request.password)
        
        # Создание пользователя
        user = User(
            name=request.name,
            email=request.email,
            password_hash=password_hash
        )
        
        self.db.add(user)
        self.db.commit()
        self.db.refresh(user)
        
        return user
    
    def get_user(self, user_id: str) -> User:
        user = self.db.query(User).filter(User.id == user_id).first()
        if not user:
            raise HTTPException(status_code=404, detail="User not found")
        return user
    
    def update_user(self, user_id: str, request: UpdateUserRequest) -> User:
        user = self.get_user(user_id)
        user.name = request.name
        user.updated_at = datetime.utcnow()
        
        self.db.commit()
        self.db.refresh(user)
        
        return user
    
    def delete_user(self, user_id: str) -> None:
        user = self.get_user(user_id)
        self.db.delete(user)
        self.db.commit()

# API Endpoints
@app.post("/api/users", response_model=UserResponse)
async def create_user(
    request: CreateUserRequest,
    db: Session = Depends(get_db)
):
    user_service = UserService(db)
    user = user_service.create_user(request)
    return user

@app.get("/api/users/{user_id}", response_model=UserResponse)
async def get_user(
    user_id: str,
    db: Session = Depends(get_db)
):
    user_service = UserService(db)
    user = user_service.get_user(user_id)
    return user

@app.put("/api/users/{user_id}", response_model=UserResponse)
async def update_user(
    user_id: str,
    request: UpdateUserRequest,
    db: Session = Depends(get_db)
):
    user_service = UserService(db)
    user = user_service.update_user(user_id, request)
    return user

@app.delete("/api/users/{user_id}")
async def delete_user(
    user_id: str,
    db: Session = Depends(get_db)
):
    user_service = UserService(db)
    user_service.delete_user(user_id)
    return {"message": "User deleted successfully"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=3001)

2. Decentralized Governance

  • Каждая команда выбирает свои технологии
  • Нет единого технологического стека
  • Самостоятельное принятие решений

3. Decentralized Data Management

  • Каждый сервис владеет своими данными
  • Нет единой базы данных
  • Разные типы БД для разных сервисов

4. Infrastructure Automation

  • Автоматизированное развертывание
  • CI/CD пайплайны
  • Мониторинг и логирование

5. Design for Failure

  • Сервисы могут отказывать
  • Необходима отказоустойчивость
  • Circuit breaker паттерны

Преимущества микросервисов

Технические преимущества

  • Независимое развертывание — можно обновлять сервисы отдельно
  • Технологическое разнообразие — разные технологии для разных задач
  • Масштабируемость — масштабирование только нужных сервисов
  • Отказоустойчивость — отказ одного сервиса не влияет на другие

Организационные преимущества

  • Независимые команды — каждая команда работает со своим сервисом
  • Быстрая разработка — параллельная работа команд
  • Специализация — команды могут специализироваться на доменах

Недостатки микросервисов

Сложность

  • Распределенная система — сложнее отладка и мониторинг
  • Сетевая задержка — коммуникация через сеть
  • Сложность данных — распределенные транзакции

Операционные проблемы

  • Мониторинг — нужно отслеживать множество сервисов
  • Развертывание — сложность управления множеством сервисов
  • Тестирование — интеграционное тестирование усложняется

Когда использовать микросервисы

Подходящие случаи:

  • Большие команды (8+ разработчиков)
  • Сложные домены
  • Высокие требования к масштабируемости
  • Необходимость независимого развертывания
  • Разные технологические требования

Неподходящие случаи:

  • Маленькие команды
  • Простые приложения
  • Низкие требования к масштабируемости
  • Недостаток DevOps экспертизы

Domain-Driven Design (DDD)

Что такое DDD

Domain-Driven Design — это подход к разработке программного обеспечения, который:

  • Фокусируется на бизнес-домене
  • Использует единый язык (Ubiquitous Language)
  • Разделяет систему на ограниченные контексты (Bounded Contexts)

Стратегическое проектирование

1. Bounded Context (Ограниченный контекст)

Определение: Четко определенная граница, внутри которой модель домена применима.

Пример:

E-commerce система:
- Order Context → заказы, платежи
- Inventory Context → товары, склад
- Customer Context → клиенты, профили
- Shipping Context → доставка, логистика

2. Context Map (Контекстная карта)

Определение: Диаграмма, показывающая отношения между ограниченными контекстами.

Типы отношений:

  • Shared Kernel — общая модель
  • Customer-Supplier — клиент-поставщик
  • Conformist — соответствие
  • Anticorruption Layer — антикоррупционный слой
  • Open Host Service — открытый хост-сервис
  • Published Language — опубликованный язык

3. Ubiquitous Language (Единый язык)

Определение: Общий язык, используемый командой для описания домена.

Принципы:

  • Используется в коде
  • Используется в документации
  • Используется в общении
  • Эволюционирует с пониманием домена

Тактическое проектирование

1. Entity (Сущность)

Определение: Объект с уникальной идентичностью.

public class Order {
    private OrderId id; // уникальный идентификатор
    private CustomerId customerId;
    private List<OrderItem> items;
    private OrderStatus status;
    
    // методы бизнес-логики
    public void addItem(ProductId productId, int quantity) {
        // бизнес-правила
    }
}

2. Value Object (Объект-значение)

Определение: Объект без идентичности, определяемый только своими атрибутами.

public class Money {
    private final BigDecimal amount;
    private final Currency currency;
    
    public Money(BigDecimal amount, Currency currency) {
        this.amount = amount;
        this.currency = currency;
    }
    
    // методы для работы с деньгами
    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("Different currencies");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }
}

3. Aggregate (Агрегат)

Определение: Граница консистентности данных.

Правила агрегатов:

  • Один агрегат = одна транзакция
  • Ссылки только на корень агрегата
  • Обновления только через корень
public class Order { // корень агрегата
    private OrderId id;
    private CustomerId customerId;
    private List<OrderItem> items; // сущности внутри агрегата
    private OrderStatus status;
    
    // методы для изменения состояния
    public void addItem(ProductId productId, int quantity) {
        // валидация бизнес-правил
        items.add(new OrderItem(productId, quantity));
    }
}

4. Repository (Репозиторий)

Определение: Интерфейс для доступа к агрегатам.

public interface OrderRepository {
    Order findById(OrderId id);
    void save(Order order);
    List<Order> findByCustomerId(CustomerId customerId);
}

5. Domain Service (Доменный сервис)

Определение: Операция, которая не принадлежит ни одной сущности.

public class OrderPricingService {
    public Money calculateTotalPrice(Order order, PricingRules rules) {
        // сложная логика расчета цены
        // не принадлежит ни Order, ни PricingRules
    }
}

6. Domain Event (Доменное событие)

Определение: Событие, произошедшее в домене.

public class OrderCreatedEvent {
    private final OrderId orderId;
    private final CustomerId customerId;
    private final LocalDateTime occurredAt;
    
    // конструктор и геттеры
}

Event Storming (Мозговой штурм событий)

Event Storming — методология для изучения домена через события.

Процесс:

  1. Доменные события — что происходит в системе
  2. Команды — что вызывает события
  3. Агрегаты — что обрабатывает команды
  4. Политики — бизнес-правила
  5. Read Models — что нужно для отображения

Пример:

Команда: CreateOrder
    ↓
Агрегат: Order
    ↓
Событие: OrderCreated
    ↓
Политика: SendConfirmationEmail
    ↓
Команда: SendEmail

Коммуникация между сервисами

Синхронная коммуникация

HTTP/REST

Преимущества:

  • Простота
  • Широкая поддержка
  • Стандартизация

Недостатки:

  • Связанность
  • Блокирующие вызовы
  • Проблемы с производительностью

Технический пример - HTTP/REST коммуникация:

// Order Service - клиент для Payment Service
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    @Autowired
    private PaymentServiceClient paymentServiceClient;
    @Autowired
    private OrderService orderService;
    
    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
        try {
            // Синхронный вызов Payment Service
            PaymentRequest paymentRequest = new PaymentRequest(
                request.getAmount(),
                request.getCurrency(),
                request.getPaymentMethod(),
                request.getPaymentDetails()
            );
            
            PaymentResult paymentResult = paymentServiceClient.processPayment(paymentRequest);
            
            if (paymentResult.isSuccess()) {
                // Создаем заказ только после успешной оплаты
                Order order = orderService.createOrder(request, paymentResult.getTransactionId());
                return ResponseEntity.ok(order);
            } else {
                return ResponseEntity.badRequest()
                    .body(new ErrorResponse("Payment failed: " + paymentResult.getErrorMessage()));
            }
            
        } catch (PaymentServiceException e) {
            // Обработка ошибок коммуникации
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
                .body(new ErrorResponse("Payment service unavailable"));
        }
    }
}

// HTTP клиент для Payment Service
@Component
public class PaymentServiceClient {
    @Autowired
    private RestTemplate restTemplate;
    
    private static final String PAYMENT_SERVICE_URL = "http://payment-service:8080";
    
    public PaymentResult processPayment(PaymentRequest request) {
        try {
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            
            HttpEntity<PaymentRequest> entity = new HttpEntity<>(request, headers);
            
            ResponseEntity<PaymentResult> response = restTemplate.postForEntity(
                PAYMENT_SERVICE_URL + "/api/payments/process",
                entity,
                PaymentResult.class
            );
            
            if (response.getStatusCode() == HttpStatus.OK) {
                return response.getBody();
            } else {
                throw new PaymentServiceException("Payment service returned error: " + response.getStatusCode());
            }
            
        } catch (RestClientException e) {
            throw new PaymentServiceException("Failed to communicate with payment service", e);
        }
    }
    
    public PaymentResult refundPayment(String transactionId, BigDecimal amount) {
        try {
            RefundRequest refundRequest = new RefundRequest(transactionId, amount);
            
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            
            HttpEntity<RefundRequest> entity = new HttpEntity<>(refundRequest, headers);
            
            ResponseEntity<RefundResult> response = restTemplate.postForEntity(
                PAYMENT_SERVICE_URL + "/api/payments/refund",
                entity,
                RefundResult.class
            );
            
            if (response.getStatusCode() == HttpStatus.OK) {
                RefundResult refundResult = response.getBody();
                return new PaymentResult(
                    null,
                    refundResult.isSuccess() ? PaymentStatus.REFUNDED : PaymentStatus.FAILED,
                    transactionId,
                    refundResult.isSuccess() ? null : "Refund failed"
                );
            } else {
                throw new PaymentServiceException("Refund service returned error: " + response.getStatusCode());
            }
            
        } catch (RestClientException e) {
            throw new PaymentServiceException("Failed to communicate with refund service", e);
        }
    }
}

// Конфигурация RestTemplate с таймаутами и retry
@Configuration
public class RestTemplateConfig {
    
    @Bean
    public RestTemplate restTemplate() {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setConnectTimeout(5000); // 5 секунд на подключение
        factory.setReadTimeout(10000);    // 10 секунд на чтение
        
        RestTemplate restTemplate = new RestTemplate(factory);
        
        // Добавляем retry логику
        restTemplate.setInterceptors(Arrays.asList(new RetryInterceptor()));
        
        return restTemplate;
    }
}

// Interceptor для retry логики
public class RetryInterceptor implements ClientHttpRequestInterceptor {
    private static final int MAX_RETRIES = 3;
    
    @Override
    public ClientHttpResponse intercept(
            HttpRequest request, 
            byte[] body, 
            ClientHttpRequestExecution execution) throws IOException {
        
        int retryCount = 0;
        IOException lastException = null;
        
        while (retryCount < MAX_RETRIES) {
            try {
                return execution.execute(request, body);
            } catch (IOException e) {
                lastException = e;
                retryCount++;
                
                if (retryCount < MAX_RETRIES) {
                    try {
                        Thread.sleep(1000 * retryCount); // Exponential backoff
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new IOException("Retry interrupted", ie);
                    }
                }
            }
        }
        
        throw lastException;
    }
}

GraphQL

Преимущества:

  • Гибкость запросов
  • Типизация
  • Единая точка входа

Недостатки:

  • Сложность кэширования
  • Потенциальные проблемы с производительностью

Асинхронная коммуникация

Очереди сообщений

Преимущества:

  • Слабая связанность
  • Отказоустойчивость
  • Масштабируемость

Недостатки:

  • Сложность отладки
  • Потенциальные проблемы с порядком

Технический пример - асинхронная коммуникация с RabbitMQ:

// Order Service - публикует события
@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public Order createOrder(CreateOrderRequest request) {
        // Создаем заказ
        Order order = new Order();
        order.setCustomerId(request.getCustomerId());
        order.setItems(request.getItems());
        order.setStatus(OrderStatus.CREATED);
        order.setCreatedAt(LocalDateTime.now());
        
        order = orderRepository.save(order);
        
        // Публикуем событие о создании заказа
        OrderCreatedEvent event = new OrderCreatedEvent(
            order.getId(),
            order.getCustomerId(),
            order.getItems(),
            order.getTotalAmount(),
            order.getCreatedAt()
        );
        
        rabbitTemplate.convertAndSend("order.exchange", "order.created", event);
        
        return order;
    }
    
    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException("Order not found"));
        
        if (order.getStatus() == OrderStatus.CREATED) {
            order.setStatus(OrderStatus.CANCELLED);
            orderRepository.save(order);
            
            // Публикуем событие об отмене заказа
            OrderCancelledEvent event = new OrderCancelledEvent(
                order.getId(),
                order.getCustomerId(),
                order.getItems(),
                order.getTotalAmount(),
                LocalDateTime.now()
            );
            
            rabbitTemplate.convertAndSend("order.exchange", "order.cancelled", event);
        }
    }
}

// Notification Service - подписывается на события
@Component
public class OrderEventHandler {
    @Autowired
    private NotificationService notificationService;
    
    @RabbitListener(queues = "order.created.queue")
    public void handleOrderCreated(OrderCreatedEvent event) {
        try {
            // Отправляем подтверждение заказа
            notificationService.sendOrderConfirmation(
                event.getCustomerId(),
                event.getOrderId(),
                event.getTotalAmount()
            );
            
            // Отправляем уведомление о статусе заказа
            notificationService.sendOrderStatusUpdate(
                event.getCustomerId(),
                event.getOrderId(),
                "Your order has been created and is being processed"
            );
            
        } catch (Exception e) {
            // Логируем ошибку, но не прерываем обработку
            log.error("Failed to send order confirmation for order: " + event.getOrderId(), e);
        }
    }
    
    @RabbitListener(queues = "order.cancelled.queue")
    public void handleOrderCancelled(OrderCancelledEvent event) {
        try {
            // Отправляем уведомление об отмене
            notificationService.sendOrderCancellation(
                event.getCustomerId(),
                event.getOrderId(),
                event.getTotalAmount()
            );
            
        } catch (Exception e) {
            log.error("Failed to send order cancellation for order: " + event.getOrderId(), e);
        }
    }
}

// Inventory Service - подписывается на события
@Component
public class InventoryEventHandler {
    @Autowired
    private InventoryService inventoryService;
    
    @RabbitListener(queues = "order.created.queue")
    public void handleOrderCreated(OrderCreatedEvent event) {
        try {
            // Резервируем товары
            for (OrderItem item : event.getItems()) {
                inventoryService.reserveItem(item.getProductId(), item.getQuantity());
            }
            
            // Публикуем событие о резервировании
            InventoryReservedEvent inventoryEvent = new InventoryReservedEvent(
                event.getOrderId(),
                event.getItems(),
                LocalDateTime.now()
            );
            
            rabbitTemplate.convertAndSend("inventory.exchange", "inventory.reserved", inventoryEvent);
            
        } catch (InsufficientStockException e) {
            // Если товаров недостаточно, отменяем заказ
            OrderCancelledEvent cancelEvent = new OrderCancelledEvent(
                event.getOrderId(),
                event.getCustomerId(),
                event.getItems(),
                event.getTotalAmount(),
                LocalDateTime.now()
            );
            
            rabbitTemplate.convertAndSend("order.exchange", "order.cancelled", cancelEvent);
            
        } catch (Exception e) {
            log.error("Failed to reserve inventory for order: " + event.getOrderId(), e);
        }
    }
    
    @RabbitListener(queues = "order.cancelled.queue")
    public void handleOrderCancelled(OrderCancelledEvent event) {
        try {
            // Освобождаем зарезервированные товары
            for (OrderItem item : event.getItems()) {
                inventoryService.releaseReservation(item.getProductId(), item.getQuantity());
            }
            
        } catch (Exception e) {
            log.error("Failed to release inventory for order: " + event.getOrderId(), e);
        }
    }
}

// Конфигурация RabbitMQ
@Configuration
@EnableRabbit
public class RabbitMQConfig {
    
    // Exchange для заказов
    @Bean
    public TopicExchange orderExchange() {
        return new TopicExchange("order.exchange");
    }
    
    // Очереди для событий заказов
    @Bean
    public Queue orderCreatedQueue() {
        return new Queue("order.created.queue", true);
    }
    
    @Bean
    public Queue orderCancelledQueue() {
        return new Queue("order.cancelled.queue", true);
    }
    
    // Привязки очередей к exchange
    @Bean
    public Binding orderCreatedBinding() {
        return BindingBuilder
            .bind(orderCreatedQueue())
            .to(orderExchange())
            .with("order.created");
    }
    
    @Bean
    public Binding orderCancelledBinding() {
        return BindingBuilder
            .bind(orderCancelledQueue())
            .to(orderExchange())
            .with("order.cancelled");
    }
    
    // Exchange для инвентаря
    @Bean
    public TopicExchange inventoryExchange() {
        return new TopicExchange("inventory.exchange");
    }
    
    @Bean
    public Queue inventoryReservedQueue() {
        return new Queue("inventory.reserved.queue", true);
    }
    
    @Bean
    public Binding inventoryReservedBinding() {
        return BindingBuilder
            .bind(inventoryReservedQueue())
            .to(inventoryExchange())
            .with("inventory.reserved");
    }
    
    // Конфигурация RabbitTemplate
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        return template;
    }
    
    // Конфигурация Jackson для сериализации
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}

// События (DTO)
public class OrderCreatedEvent {
    private Long orderId;
    private Long customerId;
    private List<OrderItem> items;
    private BigDecimal totalAmount;
    private LocalDateTime createdAt;
    
    // конструкторы, геттеры, сеттеры
}

public class OrderCancelledEvent {
    private Long orderId;
    private Long customerId;
    private List<OrderItem> items;
    private BigDecimal totalAmount;
    private LocalDateTime cancelledAt;
    
    // конструкторы, геттеры, сеттеры
}

public class InventoryReservedEvent {
    private Long orderId;
    private List<OrderItem> items;
    private LocalDateTime reservedAt;
    
    // конструкторы, геттеры, сеттеры
}

Потоковая обработка событий

Преимущества:

  • Высокая пропускная способность
  • Исторические данные
  • Real-time обработка

Технологии:

  • Apache Kafka
  • Apache Pulsar
  • AWS Kinesis

Паттерны коммуникации

1. Request-Response

Client → Service A → Service B
Client ← Service A ← Service B

2. Publish-Subscribe

Publisher → Event Bus → Subscriber 1
                      → Subscriber 2
                      → Subscriber 3

3. Request-Reply

Client → Message Queue → Service
Client ← Message Queue ← Service

4. Event Sourcing

Command → Aggregate → Event Store
Query  → Read Model ← Event Store

Обнаружение сервисов

Проблема: Как сервисы находят друг друга в динамической среде?

Решения:

1. Client-Side Discovery
@Service
public class PaymentServiceClient {
    @Autowired
    private ServiceDiscovery serviceDiscovery;
    
    public PaymentResult processPayment(PaymentRequest request) {
        // клиент сам находит сервис
        ServiceInstance instance = serviceDiscovery.getInstance("payment-service");
        String url = "http://" + instance.getHost() + ":" + instance.getPort();
        
        return restTemplate.postForObject(url + "/payments", request, PaymentResult.class);
    }
}
2. Server-Side Discovery
@Service
public class PaymentServiceClient {
    public PaymentResult processPayment(PaymentRequest request) {
        // сервис находится через load balancer
        String url = "http://payment-service/payments";
        return restTemplate.postForObject(url, request, PaymentResult.class);
    }
}

Инструменты:

  • Consul — service discovery и configuration
  • Eureka — Netflix service discovery
  • Kubernetes — встроенный service discovery

Управление данными в микросервисах

База данных на сервис

Принцип: Каждый микросервис имеет собственную базу данных.

Преимущества:

  • Независимость данных
  • Разные типы БД для разных задач
  • Независимое масштабирование

Недостатки:

  • Сложность транзакций
  • Проблемы с консистентностью
  • Дублирование данных

Паттерны управления данными

1. Saga Pattern

Определение: Паттерн для управления распределенными транзакциями.

Choreography Saga
// Order Service
public class OrderService {
    public void createOrder(CreateOrderRequest request) {
        Order order = new Order(request);
        orderRepository.save(order);
        
        // публикуем событие
        eventPublisher.publish(new OrderCreatedEvent(order.getId(), request.getItems()));
    }
}

// Inventory Service
@EventHandler
public class InventoryEventHandler {
    public void handleOrderCreated(OrderCreatedEvent event) {
        try {
            inventoryService.reserveItems(event.getItems());
            eventPublisher.publish(new ItemsReservedEvent(event.getOrderId()));
        } catch (InsufficientStockException e) {
            eventPublisher.publish(new OrderCancelledEvent(event.getOrderId()));
        }
    }
}

// Payment Service
@EventHandler
public class PaymentEventHandler {
    public void handleItemsReserved(ItemsReservedEvent event) {
        try {
            paymentService.processPayment(event.getOrderId());
            eventPublisher.publish(new PaymentProcessedEvent(event.getOrderId()));
        } catch (PaymentFailedException e) {
            eventPublisher.publish(new OrderCancelledEvent(event.getOrderId()));
        }
    }
}
Orchestration Saga
@Service
public class OrderOrchestrator {
    public void processOrder(CreateOrderRequest request) {
        Order order = orderService.createOrder(request);
        
        try {
            // шаг 1: резервирование товаров
            inventoryService.reserveItems(order.getId(), request.getItems());
            
            // шаг 2: обработка платежа
            paymentService.processPayment(order.getId(), request.getPayment());
            
            // шаг 3: подтверждение заказа
            orderService.confirmOrder(order.getId());
            
        } catch (Exception e) {
            // компенсирующие транзакции
            compensateOrder(order.getId());
        }
    }
    
    private void compensateOrder(OrderId orderId) {
        try {
            inventoryService.releaseItems(orderId);
            paymentService.refundPayment(orderId);
            orderService.cancelOrder(orderId);
        } catch (Exception e) {
            // логирование ошибок компенсации
        }
    }
}

2. CQRS (Command Query Responsibility Segregation)

Определение: Разделение команд и запросов.

// Command Side
@Service
public class OrderCommandService {
    public void createOrder(CreateOrderCommand command) {
        Order order = new Order(command);
        orderRepository.save(order);
        
        // обновляем read model
        orderReadModelService.updateReadModel(order);
    }
}

// Query Side
@Service
public class OrderQueryService {
    public OrderView getOrderView(OrderId orderId) {
        return orderReadModelRepository.findById(orderId);
    }
    
    public List<OrderView> getOrdersByCustomer(CustomerId customerId) {
        return orderReadModelRepository.findByCustomerId(customerId);
    }
}

3. Event Sourcing

Определение: Хранение событий вместо состояния.

@Entity
public class OrderEvent {
    private Long id;
    private String aggregateId;
    private String eventType;
    private String eventData;
    private LocalDateTime occurredAt;
}

@Service
public class OrderEventStore {
    public void saveEvent(OrderEvent event) {
        eventRepository.save(event);
    }
    
    public List<OrderEvent> getEvents(String aggregateId) {
        return eventRepository.findByAggregateIdOrderByOccurredAt(aggregateId);
    }
}

@Service
public class OrderProjectionService {
    public OrderView buildOrderView(String orderId) {
        List<OrderEvent> events = eventStore.getEvents(orderId);
        OrderView view = new OrderView();
        
        for (OrderEvent event : events) {
            applyEvent(view, event);
        }
        
        return view;
    }
}

Консистентность данных

Типы консистентности:

1. Strong Consistency
  • Все узлы видят одинаковые данные
  • Сложно в распределенных системах
  • Высокая задержка
2. Eventual Consistency
  • Данные в итоге станут консистентными
  • Подходит для большинства случаев
  • Низкая задержка
3. Weak Consistency
  • Нет гарантий консистентности
  • Используется редко

Паттерны для консистентности:

1. Two-Phase Commit (2PC)

Проблема: Блокирующий протокол Решение: Использовать Saga вместо 2PC

2. Three-Phase Commit (3PC)

Улучшение: Меньше блокировок Проблема: Все еще сложный

3. Consensus Algorithms
  • Raft — выбор лидера
  • Paxos — консенсус в распределенных системах

Микрофронтенды

Что такое микрофронтенды

Микрофронтенды — это архитектурный подход, где фронтенд приложения разбивается на независимые части.

Принципы микрофронтендов

1. Независимость

  • Независимая разработка
  • Независимое развертывание
  • Независимые команды

2. Технологическое разнообразие

  • Разные фреймворки
  • Разные библиотеки
  • Разные подходы

3. Единый пользовательский интерфейс

  • Общий дизайн
  • Общие компоненты
  • Общий пользовательский опыт

Архитектурные паттерны

1. Module Federation (Webpack 5)

// shell/ModuleFederationPlugin.js
new ModuleFederationPlugin({
  name: 'shell',
  remotes: {
    'product': 'product@http://localhost:3001/remoteEntry.js',
    'cart': 'cart@http://localhost:3002/remoteEntry.js',
  },
});

// product/webpack.config.js
new ModuleFederationPlugin({
  name: 'product',
  filename: 'remoteEntry.js',
  exposes: {
    './ProductList': './src/ProductList',
  },
});

2. Single-SPA

// main.js
import { registerApplication, start } from 'single-spa';

registerApplication({
  name: 'product',
  app: () => System.import('product'),
  activeWhen: ['/products'],
});

registerApplication({
  name: 'cart',
  app: () => System.import('cart'),
  activeWhen: ['/cart'],
});

start();

3. iframe-based

<!-- shell/index.html -->
<div id="product-microfrontend">
  <iframe src="http://localhost:3001/products" 
          width="100%" 
          height="600px">
  </iframe>
</div>

<div id="cart-microfrontend">
  <iframe src="http://localhost:3002/cart" 
          width="100%" 
          height="400px">
  </iframe>
</div>

Управление состоянием

1. Shared State

// shared-state/store.js
class SharedStore {
  constructor() {
    this.state = {};
    this.subscribers = [];
  }
  
  setState(newState) {
    this.state = { ...this.state, ...newState };
    this.notifySubscribers();
  }
  
  subscribe(callback) {
    this.subscribers.push(callback);
  }
  
  notifySubscribers() {
    this.subscribers.forEach(callback => callback(this.state));
  }
}

export const sharedStore = new SharedStore();

2. Event Bus

// shared/eventBus.js
class EventBus {
  constructor() {
    this.events = {};
  }
  
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }
  
  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(callback => callback(data));
    }
  }
}

export const eventBus = new EventBus();

Коммуникация между микрофронтендами

1. Props/Events

// shell/App.js
import ProductMicrofrontend from 'product/ProductList';
import CartMicrofrontend from 'cart/CartList';

function App() {
  const [cartItems, setCartItems] = useState([]);
  
  const handleAddToCart = (product) => {
    setCartItems([...cartItems, product]);
  };
  
  return (
    <div>
      <ProductMicrofrontend onAddToCart={handleAddToCart} />
      <CartMicrofrontend items={cartItems} />
    </div>
  );
}

2. Custom Events

// product/ProductList.js
function ProductList() {
  const handleAddToCart = (product) => {
    // отправляем событие
    window.dispatchEvent(new CustomEvent('addToCart', {
      detail: product
    }));
  };
  
  return (
    <div>
      {products.map(product => (
        <button onClick={() => handleAddToCart(product)}>
          Add to Cart
        </button>
      ))}
    </div>
  );
}

// cart/CartList.js
function CartList() {
  const [items, setItems] = useState([]);
  
  useEffect(() => {
    const handleAddToCart = (event) => {
      setItems([...items, event.detail]);
    };
    
    window.addEventListener('addToCart', handleAddToCart);
    
    return () => {
      window.removeEventListener('addToCart', handleAddToCart);
    };
  }, [items]);
  
  return (
    <div>
      {items.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}

Преимущества микрофронтендов

Технические:

  • Независимая разработка
  • Технологическое разнообразие
  • Независимое развертывание
  • Масштабируемость команд

Организационные:

  • Независимые команды
  • Быстрая разработка
  • Специализация команд

Недостатки микрофронтендов

Сложность:

  • Сложность интеграции
  • Дублирование кода
  • Проблемы с производительностью
  • Сложность тестирования

Операционные:

  • Сложность развертывания
  • Проблемы с версионированием
  • Сложность мониторинга

Serverless архитектура и FaaS

Что такое Serverless

Serverless — это модель облачных вычислений, где разработчики не управляют серверами, а платформа автоматически выделяет ресурсы по требованию.

Ключевые характеристики Serverless

1. Event-Driven Execution

  • Функции запускаются по событиям
  • Автоматическое масштабирование
  • Оплата только за время выполнения

2. Stateless Functions

  • Функции не хранят состояние
  • Внешнее хранение данных
  • Горизонтальное масштабирование

FaaS (Функция как сервис)

FaaS — это подмодель Serverless, где код выполняется в виде отдельных функций.

AWS Lambda

# AWS Lambda Function
import json
import boto3

def lambda_handler(event, context):
    """
    Lambda function для обработки заказов
    """
    # Парсинг события
    order_data = json.loads(event['body'])
    
    # Валидация данных
    if not validate_order(order_data):
        return {
            'statusCode': 400,
            'body': json.dumps({'error': 'Invalid order data'})
        }
    
    # Обработка заказа
    order_id = process_order(order_data)
    
    # Отправка уведомления
    send_notification(order_id)
    
    return {
        'statusCode': 200,
        'body': json.dumps({
            'orderId': order_id,
            'status': 'processed'
        })
    }

def validate_order(order_data):
    """Валидация данных заказа"""
    required_fields = ['customerId', 'items', 'total']
    return all(field in order_data for field in required_fields)

def process_order(order_data):
    """Обработка заказа"""
    # Логика обработки заказа
    return f"ORD-{order_data.get('customerId')}-{int(time.time())}"

def send_notification(order_id):
    """Отправка уведомления"""
    sns = boto3.client('sns')
    sns.publish(
        TopicArn='arn:aws:sns:us-east-1:123456789012:order-notifications',
        Message=f'Order {order_id} has been processed',
        Subject='Order Processed'
    )

Azure Functions

// Azure Function
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

public static class OrderProcessor
{
    [FunctionName("ProcessOrder")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "orders")] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("Processing order request");

        try
        {
            // Чтение данных запроса
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            var orderData = JsonSerializer.Deserialize<OrderData>(requestBody);

            // Валидация
            if (!ValidateOrder(orderData))
            {
                return new BadRequestObjectResult("Invalid order data");
            }

            // Обработка заказа
            var orderId = await ProcessOrderAsync(orderData);

            // Отправка уведомления
            await SendNotificationAsync(orderId);

            return new OkObjectResult(new { orderId, status = "processed" });
        }
        catch (Exception ex)
        {
            log.LogError(ex, "Error processing order");
            return new StatusCodeResult(500);
        }
    }

    private static bool ValidateOrder(OrderData orderData)
    {
        return !string.IsNullOrEmpty(orderData.CustomerId) &&
               orderData.Items?.Count > 0 &&
               orderData.Total > 0;
    }

    private static async Task<string> ProcessOrderAsync(OrderData orderData)
    {
        // Логика обработки заказа
        await Task.Delay(100); // Имитация обработки
        return $"ORD-{orderData.CustomerId}-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
    }

    private static async Task SendNotificationAsync(string orderId)
    {
        // Отправка уведомления
        await Task.Delay(50); // Имитация отправки
    }
}

public class OrderData
{
    public string CustomerId { get; set; }
    public List<OrderItem> Items { get; set; }
    public decimal Total { get; set; }
}

Google Cloud Functions

// Google Cloud Function
const functions = require('@google-cloud/functions-framework');
const { PubSub } = require('@google-cloud/pubsub');

const pubsub = new PubSub();

functions.http('processOrder', async (req, res) => {
    // Установка CORS
    res.set('Access-Control-Allow-Origin', '*');
    res.set('Access-Control-Allow-Methods', 'POST');
    res.set('Access-Control-Allow-Headers', 'Content-Type');

    if (req.method === 'OPTIONS') {
        res.status(204).send('');
        return;
    }

    try {
        // Валидация данных
        if (!validateOrder(req.body)) {
            res.status(400).json({ error: 'Invalid order data' });
            return;
        }

        // Обработка заказа
        const orderId = await processOrder(req.body);

        // Отправка уведомления
        await sendNotification(orderId);

        res.status(200).json({
            orderId: orderId,
            status: 'processed'
        });
    } catch (error) {
        console.error('Error processing order:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
});

function validateOrder(orderData) {
    return orderData.customerId &&
           orderData.items &&
           orderData.items.length > 0 &&
           orderData.total > 0;
}

async function processOrder(orderData) {
    // Логика обработки заказа
    const orderId = `ORD-${orderData.customerId}-${Date.now()}`;
    
    // Сохранение в базу данных
    // await saveOrderToDatabase(orderData, orderId);
    
    return orderId;
}

async function sendNotification(orderId) {
    const topic = pubsub.topic('order-notifications');
    const message = {
        data: Buffer.from(JSON.stringify({
            orderId: orderId,
            message: 'Order has been processed',
            timestamp: new Date().toISOString()
        }))
    };

    await topic.publish(message);
}

Архитектурные паттерны Serverless

1. Event-Driven Architecture

# Serverless Event-Driven Architecture
apiVersion: v1
kind: ConfigMap
metadata:
  name: event-driven-config
data:
  # Event sources
  event-sources: |
    - name: order-created
      type: api-gateway
      handler: order-created-handler
    - name: payment-processed
      type: message-queue
      handler: payment-processed-handler
    - name: inventory-updated
      type: database-trigger
      handler: inventory-updated-handler

  # Event handlers
  event-handlers: |
    - name: order-created-handler
      function: process-order
      events: [order-created]
    - name: payment-processed-handler
      function: update-order-status
      events: [payment-processed]
    - name: inventory-updated-handler
      function: update-product-availability
      events: [inventory-updated]

2. API Gateway + Lambda Pattern

# API Gateway + Lambda Pattern
import json
from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.utilities.typing import LambdaContext

logger = Logger()
tracer = Tracer()

@tracer.capture_lambda_handler
def lambda_handler(event: dict, context: LambdaContext) -> dict:
    """
    API Gateway + Lambda для микросервиса пользователей
    """
    try:
        # Извлечение HTTP метода и пути
        http_method = event['httpMethod']
        path = event['path']
        
        # Маршрутизация запросов
        if http_method == 'GET' and path == '/users':
            return get_users(event)
        elif http_method == 'GET' and path.startswith('/users/'):
            user_id = path.split('/')[-1]
            return get_user(user_id)
        elif http_method == 'POST' and path == '/users':
            return create_user(event)
        elif http_method == 'PUT' and path.startswith('/users/'):
            user_id = path.split('/')[-1]
            return update_user(user_id, event)
        elif http_method == 'DELETE' and path.startswith('/users/'):
            user_id = path.split('/')[-1]
            return delete_user(user_id)
        else:
            return {
                'statusCode': 404,
                'body': json.dumps({'error': 'Not found'})
            }
            
    except Exception as e:
        logger.error(f"Error processing request: {str(e)}")
        return {
            'statusCode': 500,
            'body': json.dumps({'error': 'Internal server error'})
        }

@tracer.capture_method
def get_users(event: dict) -> dict:
    """Получение списка пользователей"""
    # Логика получения пользователей
    users = [
        {'id': '1', 'name': 'John Doe', 'email': 'john@example.com'},
        {'id': '2', 'name': 'Jane Smith', 'email': 'jane@example.com'}
    ]
    
    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps(users)
    }

@tracer.capture_method
def get_user(user_id: str) -> dict:
    """Получение пользователя по ID"""
    # Логика получения пользователя
    user = {'id': user_id, 'name': 'John Doe', 'email': 'john@example.com'}
    
    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps(user)
    }

@tracer.capture_method
def create_user(event: dict) -> dict:
    """Создание нового пользователя"""
    try:
        body = json.loads(event['body'])
        
        # Валидация данных
        if not body.get('name') or not body.get('email'):
            return {
                'statusCode': 400,
                'body': json.dumps({'error': 'Name and email are required'})
            }
        
        # Создание пользователя
        user = {
            'id': str(int(time.time())),
            'name': body['name'],
            'email': body['email']
        }
        
        return {
            'statusCode': 201,
            'headers': {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*'
            },
            'body': json.dumps(user)
        }
    except json.JSONDecodeError:
        return {
            'statusCode': 400,
            'body': json.dumps({'error': 'Invalid JSON'})
        }

3. Event Sourcing с Serverless

# Event Sourcing с Serverless
import json
from datetime import datetime
from typing import List, Dict, Any

class EventStore:
    """Event Store для Serverless функций"""
    
    def __init__(self, dynamodb_client):
        self.dynamodb = dynamodb_client
        self.table_name = 'events'
    
    def save_event(self, aggregate_id: str, event_type: str, event_data: Dict[Any, Any]) -> str:
        """Сохранение события"""
        event_id = f"{aggregate_id}_{int(datetime.utcnow().timestamp() * 1000)}"
        
        event = {
            'eventId': event_id,
            'aggregateId': aggregate_id,
            'eventType': event_type,
            'eventData': event_data,
            'timestamp': datetime.utcnow().isoformat(),
            'version': self.get_next_version(aggregate_id)
        }
        
        self.dynamodb.put_item(
            TableName=self.table_name,
            Item=event
        )
        
        return event_id
    
    def get_events(self, aggregate_id: str) -> List[Dict[Any, Any]]:
        """Получение событий для агрегата"""
        response = self.dynamodb.query(
            TableName=self.table_name,
            KeyConditionExpression='aggregateId = :aggregate_id',
            ExpressionAttributeValues={':aggregate_id': aggregate_id}
        )
        
        return response.get('Items', [])
    
    def get_next_version(self, aggregate_id: str) -> int:
        """Получение следующей версии агрегата"""
        events = self.get_events(aggregate_id)
        return len(events) + 1

# Lambda функция для обработки событий
def process_order_event(event, context):
    """Обработка события заказа"""
    event_store = EventStore(boto3.client('dynamodb'))
    
    # Парсинг события
    order_data = json.loads(event['body'])
    
    # Создание события
    event_id = event_store.save_event(
        aggregate_id=order_data['orderId'],
        event_type='OrderCreated',
        event_data={
            'customerId': order_data['customerId'],
            'items': order_data['items'],
            'total': order_data['total']
        }
    )
    
    # Обработка события
    if order_data.get('paymentProcessed'):
        event_store.save_event(
            aggregate_id=order_data['orderId'],
            event_type='PaymentProcessed',
            event_data={'paymentId': order_data['paymentId']}
        )
    
    return {
        'statusCode': 200,
        'body': json.dumps({
            'eventId': event_id,
            'status': 'processed'
        })
    }

Преимущества и недостатки Serverless

Преимущества

  1. Автоматическое масштабирование

    • От 0 до тысяч экземпляров
    • Оплата только за использование
    • Нет необходимости в управлении серверами
  2. Быстрая разработка

    • Фокус на бизнес-логике
    • Минимальная инфраструктура
    • Быстрое развертывание
  3. Стоимость

    • Оплата только за время выполнения
    • Нет простоя серверов
    • Эффективное использование ресурсов

Недостатки

  1. Cold Start

    • Задержка при первом запуске
    • Влияние на производительность
    • Необходимость оптимизации
  2. Ограничения выполнения

    • Таймауты (обычно 15 минут)
    • Ограничения памяти
    • Ограничения размера пакета
  3. Vendor Lock-in

    • Привязка к конкретному провайдеру
    • Сложность миграции
    • Зависимость от API провайдера

Оптимизация Serverless функций

1. Уменьшение Cold Start

# Оптимизация Cold Start
import json
import boto3

# Инициализация клиентов вне handler'а
dynamodb = boto3.client('dynamodb')
sns = boto3.client('sns')

# Кэширование соединений
def get_database_connection():
    # Переиспользование соединений
    pass

def lambda_handler(event, context):
    """Оптимизированная Lambda функция"""
    # Минимальная инициализация в handler'е
    try:
        # Быстрая обработка
        result = process_request(event)
        return {
            'statusCode': 200,
            'body': json.dumps(result)
        }
    except Exception as e:
        return {
            'statusCode': 500,
            'body': json.dumps({'error': str(e)})
        }

def process_request(event):
    """Основная логика обработки"""
    # Использование предварительно инициализированных клиентов
    return {'message': 'Success'}

2. Мониторинг и логирование

# Мониторинг Serverless функций
import json
import time
from aws_lambda_powertools import Logger, Tracer, Metrics
from aws_lambda_powertools.utilities.typing import LambdaContext

logger = Logger()
tracer = Tracer()
metrics = Metrics()

@tracer.capture_lambda_handler
@metrics.log_metrics
def lambda_handler(event: dict, context: LambdaContext) -> dict:
    """Lambda функция с мониторингом"""
    
    # Логирование начала выполнения
    logger.info("Function started", extra={
        "requestId": context.aws_request_id,
        "functionName": context.function_name
    })
    
    start_time = time.time()
    
    try:
        # Обработка запроса
        result = process_request(event)
        
        # Метрики успешного выполнения
        metrics.add_metric(name="SuccessCount", unit="Count", value=1)
        metrics.add_metric(name="Duration", unit="Milliseconds", 
                          value=(time.time() - start_time) * 1000)
        
        logger.info("Function completed successfully")
        
        return {
            'statusCode': 200,
            'body': json.dumps(result)
        }
        
    except Exception as e:
        # Метрики ошибок
        metrics.add_metric(name="ErrorCount", unit="Count", value=1)
        
        logger.error(f"Function failed: {str(e)}", extra={
            "error": str(e),
            "requestId": context.aws_request_id
        })
        
        return {
            'statusCode': 500,
            'body': json.dumps({'error': 'Internal server error'})
        }

def process_request(event):
    """Обработка запроса с трейсингом"""
    with tracer.capture_method("process_request"):
        # Логика обработки
        return {'message': 'Success'}

Serverless против Микросервисов

Аспект Serverless Микросервисы
Управление инфраструктурой Полностью автоматизировано Требует управления
Масштабирование Автоматическое Ручное или полуавтоматическое
Стоимость Оплата за использование Постоянные затраты
Производительность Cold start проблемы Стабильная производительность
Сложность Низкая Высокая
Контроль Ограниченный Полный
Vendor Lock-in Высокий Низкий

Когда использовать Serverless

Подходящие случаи:

  • Event-driven приложения
  • API с переменной нагрузкой
  • Обработка файлов и данных
  • Интеграции между системами
  • Быстрые прототипы

Неподходящие случаи:

  • Длительные вычисления
  • Приложения с постоянной нагрузкой
  • Требования к низкой задержке
  • Сложные stateful приложения
  • Критичные к производительности системы

Паттерны микросервисов

1. API Gateway

Определение: Единая точка входа для всех клиентских запросов.

Функции:

  • Маршрутизация запросов
  • Аутентификация и авторизация
  • Rate limiting
  • Кэширование
  • Мониторинг
# nginx.conf
upstream user_service {
    server user-service:8080;
}

upstream order_service {
    server order-service:8080;
}

server {
    listen 80;
    
    location /api/users/ {
        proxy_pass http://user_service/;
    }
    
    location /api/orders/ {
        proxy_pass http://order_service/;
    }
}

2. Circuit Breaker

Определение: Паттерн для предотвращения каскадных отказов.

@Component
public class PaymentServiceClient {
    private final CircuitBreaker circuitBreaker;
    
    public PaymentServiceClient() {
        this.circuitBreaker = CircuitBreaker.ofDefaults("payment-service");
    }
    
    public PaymentResult processPayment(PaymentRequest request) {
        return circuitBreaker.executeSupplier(() -> {
            return restTemplate.postForObject(
                "http://payment-service/payments", 
                request, 
                PaymentResult.class
            );
        });
    }
}

3. Bulkhead

Определение: Изоляция ресурсов для предотвращения каскадных отказов.

@Configuration
public class BulkheadConfig {
    @Bean
    public ThreadPoolExecutor paymentExecutor() {
        return new ThreadPoolExecutor(
            10, 10, 0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(100)
        );
    }
    
    @Bean
    public ThreadPoolExecutor notificationExecutor() {
        return new ThreadPoolExecutor(
            5, 5, 0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(50)
        );
    }
}

4. Retry

Определение: Повторные попытки при временных сбоях.

@Retryable(value = {ConnectException.class}, maxAttempts = 3)
public PaymentResult processPayment(PaymentRequest request) {
    return restTemplate.postForObject(
        "http://payment-service/payments", 
        request, 
        PaymentResult.class
    );
}

@Recover
public PaymentResult recover(ConnectException ex, PaymentRequest request) {
    // логика восстановления
    return new PaymentResult(false, "Service unavailable");
}

5. Timeout

Определение: Ограничение времени ожидания ответа.

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        HttpComponentsClientHttpRequestFactory factory = 
            new HttpComponentsClientHttpRequestFactory();
        factory.setConnectTimeout(5000);
        factory.setReadTimeout(10000);
        
        return new RestTemplate(factory);
    }
}

6. Service Mesh

Определение: Инфраструктурный слой для управления коммуникацией между сервисами.

Пример Istio:

# istio-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: api-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - api.example.com
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: api-routes
spec:
  hosts:
  - api.example.com
  gateways:
  - api-gateway
  http:
  - match:
    - uri:
        prefix: /api/users
    route:
    - destination:
        host: user-service
        port:
          number: 8080
  - match:
    - uri:
        prefix: /api/orders
    route:
    - destination:
        host: order-service
        port:
          number: 8080

Антипаттерны и проблемы

1. Distributed Monolith

Проблема: Микросервисы тесно связаны друг с другом.

Симптомы:

  • Изменения в одном сервисе требуют изменений в других
  • Общие базы данных
  • Синхронные вызовы между всеми сервисами

Решение:

  • Правильное разделение доменов
  • Асинхронная коммуникация
  • Независимые базы данных

2. Database per Service Anti-pattern

Проблема: Неправильное разделение данных.

Симптомы:

  • Сложные JOIN'ы между сервисами
  • Дублирование данных
  • Проблемы с консистентностью

Решение:

  • Правильное разделение данных по доменам
  • Использование событий для синхронизации
  • CQRS для разделения чтения и записи

3. Chatty Services

Проблема: Слишком много мелких запросов между сервисами.

Симптомы:

  • Высокая сетевая задержка
  • Проблемы с производительностью
  • Сложность отладки

Решение:

  • Batch операции
  • Кэширование
  • Агрегация данных

4. Shared Database

Проблема: Несколько сервисов используют одну базу данных.

Симптомы:

  • Связанность сервисов
  • Проблемы с развертыванием
  • Сложность масштабирования

Решение:

  • Разделение баз данных
  • Миграция данных
  • Использование событий

5. Service Explosion

Проблема: Слишком много мелких сервисов.

Симптомы:

  • Сложность управления
  • Высокий overhead
  • Проблемы с мониторингом

Решение:

  • Объединение связанных сервисов
  • Правильное разделение доменов
  • Использование модулей вместо сервисов

6. Versioning Hell

Проблема: Проблемы с версионированием API.

Симптомы:

  • Breaking changes
  • Сложность миграции
  • Проблемы с совместимостью

Решение:

  • Semantic versioning
  • Backward compatibility
  • Graceful deprecation

Практические задания

Задание 1: Проектирование микросервисов

Спроектируйте микросервисную архитектуру для системы интернет-магазина:

  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

Онлайн ресурсы

Инструменты

  • 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. Когда стоит использовать микросервисы, а когда нет?