Files
sa-lessons/modules/module2/sections/01-microservices-fundamentals.md
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

29 KiB

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

Навигация


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

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

  • Имеет единственную ответственность
  • Развертывается независимо
  • Имеет собственные данные
  • Коммуницирует через четко определенные 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');
});

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 экспертизы