- 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
968 lines
29 KiB
Markdown
968 lines
29 KiB
Markdown
# Основы микросервисной архитектуры
|
|
|
|
## Навигация
|
|
- [🏠 Главная страница курса](../../../README.md)
|
|
- [📚 Модуль 2](../README.md)
|
|
|
|
---
|
|
|
|
## Определение микросервисов
|
|
|
|
**Микросервис** — это небольшая, независимая, слабо связанная служба, которая:
|
|
- Имеет единственную ответственность
|
|
- Развертывается независимо
|
|
- Имеет собственные данные
|
|
- Коммуницирует через четко определенные API
|
|
|
|
## Принципы микросервисной архитектуры
|
|
|
|
### 1. Single Responsibility Principle
|
|
Каждый микросервис должен решать одну бизнес-задачу.
|
|
|
|
**Пример:**
|
|
```
|
|
User Service → управление пользователями
|
|
Payment Service → обработка платежей
|
|
Order Service → управление заказами
|
|
Notification Service → отправка уведомлений
|
|
```
|
|
|
|
**Технические примеры:**
|
|
|
|
**Java (Spring Boot) - User Service:**
|
|
```java
|
|
// User Service - только управление пользователями
|
|
@SpringBootApplication
|
|
public class UserServiceApplication {
|
|
public static void main(String[] args) {
|
|
SpringApplication.run(UserServiceApplication.class, args);
|
|
}
|
|
}
|
|
|
|
@RestController
|
|
@RequestMapping("/api/users")
|
|
public class UserController {
|
|
@Autowired
|
|
private UserService userService;
|
|
|
|
@PostMapping
|
|
public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) {
|
|
User user = userService.createUser(request);
|
|
return ResponseEntity.ok(user);
|
|
}
|
|
|
|
@GetMapping("/{id}")
|
|
public ResponseEntity<User> getUser(@PathVariable Long id) {
|
|
User user = userService.getUser(id);
|
|
return ResponseEntity.ok(user);
|
|
}
|
|
|
|
@PutMapping("/{id}")
|
|
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody UpdateUserRequest request) {
|
|
User user = userService.updateUser(id, request);
|
|
return ResponseEntity.ok(user);
|
|
}
|
|
|
|
@DeleteMapping("/{id}")
|
|
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
|
|
userService.deleteUser(id);
|
|
return ResponseEntity.ok().build();
|
|
}
|
|
}
|
|
|
|
@Service
|
|
@Transactional
|
|
public class UserService {
|
|
@Autowired
|
|
private UserRepository userRepository;
|
|
|
|
public User createUser(CreateUserRequest request) {
|
|
// Валидация
|
|
if (userRepository.existsByEmail(request.getEmail())) {
|
|
throw new UserAlreadyExistsException("User with email already exists");
|
|
}
|
|
|
|
// Создание пользователя
|
|
User user = new User();
|
|
user.setName(request.getName());
|
|
user.setEmail(request.getEmail());
|
|
user.setPasswordHash(passwordEncoder.encode(request.getPassword()));
|
|
user.setCreatedAt(LocalDateTime.now());
|
|
|
|
return userRepository.save(user);
|
|
}
|
|
|
|
public User getUser(Long id) {
|
|
return userRepository.findById(id)
|
|
.orElseThrow(() -> new UserNotFoundException("User not found"));
|
|
}
|
|
|
|
public User updateUser(Long id, UpdateUserRequest request) {
|
|
User user = getUser(id);
|
|
user.setName(request.getName());
|
|
user.setUpdatedAt(LocalDateTime.now());
|
|
return userRepository.save(user);
|
|
}
|
|
|
|
public void deleteUser(Long id) {
|
|
User user = getUser(id);
|
|
userRepository.delete(user);
|
|
}
|
|
}
|
|
|
|
@Entity
|
|
@Table(name = "users")
|
|
public class User {
|
|
@Id
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
private Long id;
|
|
|
|
@Column(unique = true, nullable = false)
|
|
private String email;
|
|
|
|
@Column(nullable = false)
|
|
private String name;
|
|
|
|
@Column(name = "password_hash", nullable = false)
|
|
private String passwordHash;
|
|
|
|
@Column(name = "created_at", nullable = false)
|
|
private LocalDateTime createdAt;
|
|
|
|
@Column(name = "updated_at")
|
|
private LocalDateTime updatedAt;
|
|
|
|
// геттеры и сеттеры
|
|
}
|
|
```
|
|
|
|
**Payment Service - только обработка платежей:**
|
|
```java
|
|
// Payment Service - только обработка платежей
|
|
@SpringBootApplication
|
|
public class PaymentServiceApplication {
|
|
public static void main(String[] args) {
|
|
SpringApplication.run(PaymentServiceApplication.class, args);
|
|
}
|
|
}
|
|
|
|
@RestController
|
|
@RequestMapping("/api/payments")
|
|
public class PaymentController {
|
|
@Autowired
|
|
private PaymentService paymentService;
|
|
|
|
@PostMapping("/process")
|
|
public ResponseEntity<PaymentResult> processPayment(@RequestBody PaymentRequest request) {
|
|
PaymentResult result = paymentService.processPayment(request);
|
|
return ResponseEntity.ok(result);
|
|
}
|
|
|
|
@GetMapping("/{id}")
|
|
public ResponseEntity<Payment> getPayment(@PathVariable Long id) {
|
|
Payment payment = paymentService.getPayment(id);
|
|
return ResponseEntity.ok(payment);
|
|
}
|
|
|
|
@PostMapping("/{id}/refund")
|
|
public ResponseEntity<RefundResult> refundPayment(@PathVariable Long id, @RequestBody RefundRequest request) {
|
|
RefundResult result = paymentService.refundPayment(id, request);
|
|
return ResponseEntity.ok(result);
|
|
}
|
|
}
|
|
|
|
@Service
|
|
@Transactional
|
|
public class PaymentService {
|
|
@Autowired
|
|
private PaymentRepository paymentRepository;
|
|
@Autowired
|
|
private PaymentGatewayClient paymentGatewayClient;
|
|
|
|
public PaymentResult processPayment(PaymentRequest request) {
|
|
// Валидация платежа
|
|
validatePaymentRequest(request);
|
|
|
|
// Создание записи о платеже
|
|
Payment payment = new Payment();
|
|
payment.setAmount(request.getAmount());
|
|
payment.setCurrency(request.getCurrency());
|
|
payment.setPaymentMethod(request.getPaymentMethod());
|
|
payment.setStatus(PaymentStatus.PENDING);
|
|
payment.setCreatedAt(LocalDateTime.now());
|
|
|
|
payment = paymentRepository.save(payment);
|
|
|
|
try {
|
|
// Обработка через платежный шлюз
|
|
PaymentGatewayResponse response = paymentGatewayClient.processPayment(request);
|
|
|
|
if (response.isSuccess()) {
|
|
payment.setStatus(PaymentStatus.COMPLETED);
|
|
payment.setTransactionId(response.getTransactionId());
|
|
payment.setProcessedAt(LocalDateTime.now());
|
|
} else {
|
|
payment.setStatus(PaymentStatus.FAILED);
|
|
payment.setFailureReason(response.getErrorMessage());
|
|
}
|
|
|
|
payment = paymentRepository.save(payment);
|
|
|
|
return new PaymentResult(
|
|
payment.getId(),
|
|
payment.getStatus(),
|
|
response.getTransactionId(),
|
|
response.getErrorMessage()
|
|
);
|
|
|
|
} catch (Exception e) {
|
|
payment.setStatus(PaymentStatus.FAILED);
|
|
payment.setFailureReason(e.getMessage());
|
|
paymentRepository.save(payment);
|
|
|
|
throw new PaymentProcessingException("Payment processing failed", e);
|
|
}
|
|
}
|
|
|
|
private void validatePaymentRequest(PaymentRequest request) {
|
|
if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
|
throw new InvalidPaymentAmountException("Payment amount must be positive");
|
|
}
|
|
|
|
if (request.getPaymentMethod() == null) {
|
|
throw new InvalidPaymentMethodException("Payment method is required");
|
|
}
|
|
}
|
|
|
|
public Payment getPayment(Long id) {
|
|
return paymentRepository.findById(id)
|
|
.orElseThrow(() -> new PaymentNotFoundException("Payment not found"));
|
|
}
|
|
|
|
public RefundResult refundPayment(Long id, RefundRequest request) {
|
|
Payment payment = getPayment(id);
|
|
|
|
if (payment.getStatus() != PaymentStatus.COMPLETED) {
|
|
throw new InvalidRefundException("Only completed payments can be refunded");
|
|
}
|
|
|
|
// Логика возврата средств
|
|
RefundGatewayResponse response = paymentGatewayClient.refundPayment(
|
|
payment.getTransactionId(),
|
|
request.getAmount()
|
|
);
|
|
|
|
if (response.isSuccess()) {
|
|
payment.setStatus(PaymentStatus.REFUNDED);
|
|
payment.setRefundedAt(LocalDateTime.now());
|
|
paymentRepository.save(payment);
|
|
}
|
|
|
|
return new RefundResult(response.isSuccess(), response.getRefundId());
|
|
}
|
|
}
|
|
|
|
@Entity
|
|
@Table(name = "payments")
|
|
public class Payment {
|
|
@Id
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
private Long id;
|
|
|
|
@Column(nullable = false)
|
|
private BigDecimal amount;
|
|
|
|
@Column(nullable = false)
|
|
private String currency;
|
|
|
|
@Enumerated(EnumType.STRING)
|
|
@Column(nullable = false)
|
|
private PaymentMethod paymentMethod;
|
|
|
|
@Enumerated(EnumType.STRING)
|
|
@Column(nullable = false)
|
|
private PaymentStatus status;
|
|
|
|
@Column(name = "transaction_id")
|
|
private String transactionId;
|
|
|
|
@Column(name = "failure_reason")
|
|
private String failureReason;
|
|
|
|
@Column(name = "created_at", nullable = false)
|
|
private LocalDateTime createdAt;
|
|
|
|
@Column(name = "processed_at")
|
|
private LocalDateTime processedAt;
|
|
|
|
@Column(name = "refunded_at")
|
|
private LocalDateTime refundedAt;
|
|
|
|
// геттеры и сеттеры
|
|
}
|
|
```
|
|
|
|
**Node.js (Express + TypeScript + Prisma) - User Service:**
|
|
```typescript
|
|
// User Service - только управление пользователями
|
|
import express, { Request, Response, NextFunction } from 'express';
|
|
import { PrismaClient } from '@prisma/client';
|
|
import { z } from 'zod';
|
|
import bcrypt from 'bcrypt';
|
|
|
|
const app = express();
|
|
const prisma = new PrismaClient();
|
|
|
|
// Interfaces
|
|
interface CreateUserRequest {
|
|
name: string;
|
|
email: string;
|
|
password: string;
|
|
}
|
|
|
|
interface UpdateUserRequest {
|
|
name: string;
|
|
}
|
|
|
|
interface User {
|
|
id: number;
|
|
name: string;
|
|
email: string;
|
|
passwordHash: string;
|
|
createdAt: Date;
|
|
updatedAt: Date | null;
|
|
}
|
|
|
|
// User Controller
|
|
class UserController {
|
|
constructor(private userService: UserService) {}
|
|
|
|
async createUser(req: Request, res: Response, next: NextFunction) {
|
|
try {
|
|
const user = await this.userService.createUser(req.body);
|
|
res.json(user);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async getUser(req: Request, res: Response, next: NextFunction) {
|
|
try {
|
|
const id = parseInt(req.params.id);
|
|
const user = await this.userService.getUser(id);
|
|
res.json(user);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async updateUser(req: Request, res: Response, next: NextFunction) {
|
|
try {
|
|
const id = parseInt(req.params.id);
|
|
const user = await this.userService.updateUser(id, req.body);
|
|
res.json(user);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async deleteUser(req: Request, res: Response, next: NextFunction) {
|
|
try {
|
|
const id = parseInt(req.params.id);
|
|
await this.userService.deleteUser(id);
|
|
res.status(204).send();
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// User Service
|
|
class UserService {
|
|
constructor(private userRepository: UserRepository) {}
|
|
|
|
async createUser(request: CreateUserRequest): Promise<User> {
|
|
// Валидация
|
|
const existingUser = await this.userRepository.findByEmail(request.email);
|
|
if (existingUser) {
|
|
throw new Error('User with email already exists');
|
|
}
|
|
|
|
// Создание пользователя
|
|
const passwordHash = await bcrypt.hash(request.password, 10);
|
|
const user = {
|
|
name: request.name,
|
|
email: request.email,
|
|
passwordHash,
|
|
createdAt: new Date(),
|
|
updatedAt: null
|
|
};
|
|
|
|
return await this.userRepository.save(user);
|
|
}
|
|
|
|
async getUser(id: number): Promise<User> {
|
|
const user = await this.userRepository.findById(id);
|
|
if (!user) {
|
|
throw new Error('User not found');
|
|
}
|
|
return user;
|
|
}
|
|
|
|
async updateUser(id: number, request: UpdateUserRequest): Promise<User> {
|
|
const user = await this.getUser(id);
|
|
user.name = request.name;
|
|
user.updatedAt = new Date();
|
|
return await this.userRepository.save(user);
|
|
}
|
|
|
|
async deleteUser(id: number): Promise<void> {
|
|
const user = await this.getUser(id);
|
|
await this.userRepository.delete(user.id);
|
|
}
|
|
}
|
|
|
|
// User Repository
|
|
interface UserRepository {
|
|
save(user: Omit<User, 'id'>): Promise<User>;
|
|
findById(id: number): Promise<User | null>;
|
|
findByEmail(email: string): Promise<User | null>;
|
|
delete(id: number): Promise<void>;
|
|
}
|
|
|
|
class PrismaUserRepository implements UserRepository {
|
|
async save(user: Omit<User, 'id'>): Promise<User> {
|
|
const savedUser = await prisma.user.create({
|
|
data: {
|
|
name: user.name,
|
|
email: user.email,
|
|
passwordHash: user.passwordHash,
|
|
createdAt: user.createdAt,
|
|
updatedAt: user.updatedAt
|
|
}
|
|
});
|
|
|
|
return {
|
|
id: savedUser.id,
|
|
name: savedUser.name,
|
|
email: savedUser.email,
|
|
passwordHash: savedUser.passwordHash,
|
|
createdAt: savedUser.createdAt,
|
|
updatedAt: savedUser.updatedAt
|
|
};
|
|
}
|
|
|
|
async findById(id: number): Promise<User | null> {
|
|
const user = await prisma.user.findUnique({
|
|
where: { id }
|
|
});
|
|
|
|
if (!user) return null;
|
|
|
|
return {
|
|
id: user.id,
|
|
name: user.name,
|
|
email: user.email,
|
|
passwordHash: user.passwordHash,
|
|
createdAt: user.createdAt,
|
|
updatedAt: user.updatedAt
|
|
};
|
|
}
|
|
|
|
async findByEmail(email: string): Promise<User | null> {
|
|
const user = await prisma.user.findUnique({
|
|
where: { email }
|
|
});
|
|
|
|
if (!user) return null;
|
|
|
|
return {
|
|
id: user.id,
|
|
name: user.name,
|
|
email: user.email,
|
|
passwordHash: user.passwordHash,
|
|
createdAt: user.createdAt,
|
|
updatedAt: user.updatedAt
|
|
};
|
|
}
|
|
|
|
async delete(id: number): Promise<void> {
|
|
await prisma.user.delete({
|
|
where: { id }
|
|
});
|
|
}
|
|
}
|
|
|
|
// Validation schemas
|
|
const createUserSchema = z.object({
|
|
name: z.string().min(1, 'Name is required'),
|
|
email: z.string().email('Valid email is required'),
|
|
password: z.string().min(6, 'Password must be at least 6 characters')
|
|
});
|
|
|
|
const updateUserSchema = z.object({
|
|
name: z.string().min(1, 'Name is required')
|
|
});
|
|
|
|
// Dependency injection
|
|
const userRepository = new PrismaUserRepository();
|
|
const userService = new UserService(userRepository);
|
|
const userController = new UserController(userService);
|
|
|
|
// Routes
|
|
app.post('/api/users', async (req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const validatedData = createUserSchema.parse(req.body);
|
|
await userController.createUser({ ...req, body: validatedData }, res, next);
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
res.status(400).json({ error: error.errors });
|
|
} else {
|
|
next(error);
|
|
}
|
|
}
|
|
});
|
|
|
|
app.get('/api/users/:id', (req: Request, res: Response, next: NextFunction) => {
|
|
userController.getUser(req, res, next);
|
|
});
|
|
|
|
app.put('/api/users/:id', async (req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const validatedData = updateUserSchema.parse(req.body);
|
|
await userController.updateUser({ ...req, body: validatedData }, res, next);
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
res.status(400).json({ error: error.errors });
|
|
} else {
|
|
next(error);
|
|
}
|
|
}
|
|
});
|
|
|
|
app.delete('/api/users/:id', (req: Request, res: Response, next: NextFunction) => {
|
|
userController.deleteUser(req, res, next);
|
|
});
|
|
|
|
// Error handling middleware
|
|
app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
|
|
console.error(error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
});
|
|
|
|
app.listen(3001, () => {
|
|
console.log('User Service running on port 3001');
|
|
});
|
|
```
|
|
|
|
**Rust (Actix Web + Diesel) - User Service:**
|
|
```rust
|
|
// User Service - только управление пользователями
|
|
use actix_web::{web, App, HttpResponse, HttpServer, Result};
|
|
use diesel::prelude::*;
|
|
use serde::{Deserialize, Serialize};
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct CreateUserRequest {
|
|
name: String,
|
|
email: String,
|
|
password: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct UpdateUserRequest {
|
|
name: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Queryable)]
|
|
struct User {
|
|
id: Uuid,
|
|
name: String,
|
|
email: String,
|
|
password_hash: String,
|
|
created_at: chrono::DateTime<chrono::Utc>,
|
|
updated_at: Option<chrono::DateTime<chrono::Utc>>,
|
|
}
|
|
|
|
// User Service
|
|
struct UserService {
|
|
pool: diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<PgConnection>>,
|
|
}
|
|
|
|
impl UserService {
|
|
fn new(database_url: &str) -> Self {
|
|
let manager = diesel::r2d2::ConnectionManager::<PgConnection>::new(database_url);
|
|
let pool = diesel::r2d2::Pool::builder()
|
|
.build(manager)
|
|
.expect("Failed to create pool");
|
|
|
|
Self { pool }
|
|
}
|
|
|
|
async fn create_user(&self, request: CreateUserRequest) -> Result<User, Box<dyn std::error::Error>> {
|
|
use crate::schema::users::dsl::*;
|
|
|
|
// Проверка существования пользователя
|
|
let existing_user = users
|
|
.filter(email.eq(&request.email))
|
|
.first::<User>(&mut self.pool.get()?)
|
|
.optional()?;
|
|
|
|
if existing_user.is_some() {
|
|
return Err("User with email already exists".into());
|
|
}
|
|
|
|
// Хеширование пароля
|
|
let password_hash = bcrypt::hash(&request.password, bcrypt::DEFAULT_COST)?;
|
|
|
|
let new_user = NewUser {
|
|
id: Uuid::new_v4(),
|
|
name: request.name,
|
|
email: request.email,
|
|
password_hash,
|
|
created_at: chrono::Utc::now(),
|
|
updated_at: None,
|
|
};
|
|
|
|
let user = diesel::insert_into(users)
|
|
.values(&new_user)
|
|
.get_result::<User>(&mut self.pool.get()?)?;
|
|
|
|
Ok(user)
|
|
}
|
|
|
|
async fn get_user(&self, user_id: Uuid) -> Result<User, Box<dyn std::error::Error>> {
|
|
use crate::schema::users::dsl::*;
|
|
|
|
let user = users
|
|
.filter(id.eq(user_id))
|
|
.first::<User>(&mut self.pool.get()?)?;
|
|
|
|
Ok(user)
|
|
}
|
|
|
|
async fn update_user(&self, user_id: Uuid, request: UpdateUserRequest) -> Result<User, Box<dyn std::error::Error>> {
|
|
use crate::schema::users::dsl::*;
|
|
|
|
let user = diesel::update(users.filter(id.eq(user_id)))
|
|
.set((
|
|
name.eq(request.name),
|
|
updated_at.eq(Some(chrono::Utc::now())),
|
|
))
|
|
.get_result::<User>(&mut self.pool.get()?)?;
|
|
|
|
Ok(user)
|
|
}
|
|
|
|
async fn delete_user(&self, user_id: Uuid) -> Result<(), Box<dyn std::error::Error>> {
|
|
use crate::schema::users::dsl::*;
|
|
|
|
diesel::delete(users.filter(id.eq(user_id)))
|
|
.execute(&mut self.pool.get()?)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Insertable)]
|
|
#[diesel(table_name = users)]
|
|
struct NewUser {
|
|
id: Uuid,
|
|
name: String,
|
|
email: String,
|
|
password_hash: String,
|
|
created_at: chrono::DateTime<chrono::Utc>,
|
|
updated_at: Option<chrono::DateTime<chrono::Utc>>,
|
|
}
|
|
|
|
// Handlers
|
|
async fn create_user(
|
|
user_service: web::Data<UserService>,
|
|
request: web::Json<CreateUserRequest>,
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
|
match user_service.create_user(request.into_inner()).await {
|
|
Ok(user) => Ok(HttpResponse::Ok().json(user)),
|
|
Err(e) => Ok(HttpResponse::BadRequest().json(serde_json::json!({
|
|
"error": e.to_string()
|
|
}))),
|
|
}
|
|
}
|
|
|
|
async fn get_user(
|
|
user_service: web::Data<UserService>,
|
|
path: web::Path<Uuid>,
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
|
let user_id = path.into_inner();
|
|
|
|
match user_service.get_user(user_id).await {
|
|
Ok(user) => Ok(HttpResponse::Ok().json(user)),
|
|
Err(_) => Ok(HttpResponse::NotFound().json(serde_json::json!({
|
|
"error": "User not found"
|
|
}))),
|
|
}
|
|
}
|
|
|
|
async fn update_user(
|
|
user_service: web::Data<UserService>,
|
|
path: web::Path<Uuid>,
|
|
request: web::Json<UpdateUserRequest>,
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
|
let user_id = path.into_inner();
|
|
|
|
match user_service.update_user(user_id, request.into_inner()).await {
|
|
Ok(user) => Ok(HttpResponse::Ok().json(user)),
|
|
Err(e) => Ok(HttpResponse::BadRequest().json(serde_json::json!({
|
|
"error": e.to_string()
|
|
}))),
|
|
}
|
|
}
|
|
|
|
async fn delete_user(
|
|
user_service: web::Data<UserService>,
|
|
path: web::Path<Uuid>,
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
|
let user_id = path.into_inner();
|
|
|
|
match user_service.delete_user(user_id).await {
|
|
Ok(_) => Ok(HttpResponse::NoContent().finish()),
|
|
Err(e) => Ok(HttpResponse::BadRequest().json(serde_json::json!({
|
|
"error": e.to_string()
|
|
}))),
|
|
}
|
|
}
|
|
|
|
#[actix_web::main]
|
|
async fn main() -> std::io::Result<()> {
|
|
let database_url = std::env::var("DATABASE_URL")
|
|
.expect("DATABASE_URL must be set");
|
|
|
|
let user_service = UserService::new(&database_url);
|
|
|
|
HttpServer::new(move || {
|
|
App::new()
|
|
.app_data(web::Data::new(user_service.clone()))
|
|
.route("/api/users", web::post().to(create_user))
|
|
.route("/api/users/{id}", web::get().to(get_user))
|
|
.route("/api/users/{id}", web::put().to(update_user))
|
|
.route("/api/users/{id}", web::delete().to(delete_user))
|
|
})
|
|
.bind("127.0.0.1:3001")?
|
|
.run()
|
|
.await
|
|
}
|
|
```
|
|
|
|
**Python (FastAPI + SQLAlchemy) - User Service:**
|
|
```python
|
|
# User Service - только управление пользователей
|
|
from fastapi import FastAPI, HTTPException, Depends
|
|
from sqlalchemy import create_engine, Column, String, DateTime, Boolean
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from sqlalchemy.orm import sessionmaker, Session
|
|
from pydantic import BaseModel, EmailStr
|
|
from passlib.context import CryptContext
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
import uuid
|
|
|
|
app = FastAPI(title="User Service", version="1.0.0")
|
|
|
|
# Database setup
|
|
DATABASE_URL = "postgresql://user:password@localhost/userdb"
|
|
engine = create_engine(DATABASE_URL)
|
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
Base = declarative_base()
|
|
|
|
# Password hashing
|
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
|
|
# Database Models
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
name = Column(String, nullable=False)
|
|
email = Column(String, unique=True, nullable=False)
|
|
password_hash = Column(String, nullable=False)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, nullable=True)
|
|
|
|
# Pydantic Models
|
|
class CreateUserRequest(BaseModel):
|
|
name: str
|
|
email: EmailStr
|
|
password: str
|
|
|
|
class UpdateUserRequest(BaseModel):
|
|
name: str
|
|
|
|
class UserResponse(BaseModel):
|
|
id: str
|
|
name: str
|
|
email: str
|
|
created_at: datetime
|
|
updated_at: Optional[datetime]
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
# Dependency
|
|
def get_db():
|
|
db = SessionLocal()
|
|
try:
|
|
yield db
|
|
finally:
|
|
db.close()
|
|
|
|
# User Service
|
|
class UserService:
|
|
def __init__(self, db: Session):
|
|
self.db = db
|
|
|
|
def create_user(self, request: CreateUserRequest) -> User:
|
|
# Проверка существования пользователя
|
|
existing_user = self.db.query(User).filter(User.email == request.email).first()
|
|
if existing_user:
|
|
raise HTTPException(status_code=400, detail="User with email already exists")
|
|
|
|
# Хеширование пароля
|
|
password_hash = pwd_context.hash(request.password)
|
|
|
|
# Создание пользователя
|
|
user = User(
|
|
name=request.name,
|
|
email=request.email,
|
|
password_hash=password_hash
|
|
)
|
|
|
|
self.db.add(user)
|
|
self.db.commit()
|
|
self.db.refresh(user)
|
|
|
|
return user
|
|
|
|
def get_user(self, user_id: str) -> User:
|
|
user = self.db.query(User).filter(User.id == user_id).first()
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
return user
|
|
|
|
def update_user(self, user_id: str, request: UpdateUserRequest) -> User:
|
|
user = self.get_user(user_id)
|
|
user.name = request.name
|
|
user.updated_at = datetime.utcnow()
|
|
|
|
self.db.commit()
|
|
self.db.refresh(user)
|
|
|
|
return user
|
|
|
|
def delete_user(self, user_id: str) -> None:
|
|
user = self.get_user(user_id)
|
|
self.db.delete(user)
|
|
self.db.commit()
|
|
|
|
# API Endpoints
|
|
@app.post("/api/users", response_model=UserResponse)
|
|
async def create_user(
|
|
request: CreateUserRequest,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
user_service = UserService(db)
|
|
user = user_service.create_user(request)
|
|
return user
|
|
|
|
@app.get("/api/users/{user_id}", response_model=UserResponse)
|
|
async def get_user(
|
|
user_id: str,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
user_service = UserService(db)
|
|
user = user_service.get_user(user_id)
|
|
return user
|
|
|
|
@app.put("/api/users/{user_id}", response_model=UserResponse)
|
|
async def update_user(
|
|
user_id: str,
|
|
request: UpdateUserRequest,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
user_service = UserService(db)
|
|
user = user_service.update_user(user_id, request)
|
|
return user
|
|
|
|
@app.delete("/api/users/{user_id}")
|
|
async def delete_user(
|
|
user_id: str,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
user_service = UserService(db)
|
|
user_service.delete_user(user_id)
|
|
return {"message": "User deleted successfully"}
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=3001)
|
|
```
|
|
|
|
### 2. Decentralized Governance
|
|
- Каждая команда выбирает свои технологии
|
|
- Нет единого технологического стека
|
|
- Самостоятельное принятие решений
|
|
|
|
### 3. Decentralized Data Management
|
|
- Каждый сервис владеет своими данными
|
|
- Нет единой базы данных
|
|
- Разные типы БД для разных сервисов
|
|
|
|
### 4. Infrastructure Automation
|
|
- Автоматизированное развертывание
|
|
- CI/CD пайплайны
|
|
- Мониторинг и логирование
|
|
|
|
### 5. Design for Failure
|
|
- Сервисы могут отказывать
|
|
- Необходима отказоустойчивость
|
|
- Circuit breaker паттерны
|
|
|
|
## Преимущества микросервисов
|
|
|
|
### Технические преимущества
|
|
- **Независимое развертывание** — можно обновлять сервисы отдельно
|
|
- **Технологическое разнообразие** — разные технологии для разных задач
|
|
- **Масштабируемость** — масштабирование только нужных сервисов
|
|
- **Отказоустойчивость** — отказ одного сервиса не влияет на другие
|
|
|
|
### Организационные преимущества
|
|
- **Независимые команды** — каждая команда работает со своим сервисом
|
|
- **Быстрая разработка** — параллельная работа команд
|
|
- **Специализация** — команды могут специализироваться на доменах
|
|
|
|
## Недостатки микросервисов
|
|
|
|
### Сложность
|
|
- **Распределенная система** — сложнее отладка и мониторинг
|
|
- **Сетевая задержка** — коммуникация через сеть
|
|
- **Сложность данных** — распределенные транзакции
|
|
|
|
### Операционные проблемы
|
|
- **Мониторинг** — нужно отслеживать множество сервисов
|
|
- **Развертывание** — сложность управления множеством сервисов
|
|
- **Тестирование** — интеграционное тестирование усложняется
|
|
|
|
## Когда использовать микросервисы
|
|
|
|
### Подходящие случаи:
|
|
- Большие команды (8+ разработчиков)
|
|
- Сложные домены
|
|
- Высокие требования к масштабируемости
|
|
- Необходимость независимого развертывания
|
|
- Разные технологические требования
|
|
|
|
### Неподходящие случаи:
|
|
- Маленькие команды
|
|
- Простые приложения
|
|
- Низкие требования к масштабируемости
|
|
- Недостаток DevOps экспертизы
|