← Назад к вопросам

Как расширяли функционал последнего проекта

2.2 Middle🔥 111 комментариев
#JVM и управление памятью

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Расширение функционала последнего проекта

В моем последнем проекте я разрабатывал микросервисное приложение для обработки заказов (Order Management System). Расширение функционала происходило итеративно, с учетом требований клиента и технических ограничений.

Начальное состояние проекта

Проект был построен на базовой архитектуре:

  • Spring Boot 3.1 с REST API
  • PostgreSQL для хранения заказов
  • Простая CRUD логика без кэширования
  • Синхронная обработка платежей
  • Без разделения ролей пользователей

Анализ требований расширения

Клиент попросил:

  1. Поддержка различных статусов заказов с отслеживанием истории изменений
  2. Интеграция с платежной системой Stripe для обработки платежей
  3. Асинхронная отправка уведомлений (email, SMS) при изменении статуса
  4. Кэширование данных для оптимизации производительности
  5. Разделение ролей (администраторы, менеджеры, клиенты)
  6. Аналитика — отчёты по продажам и популярным товарам

Этап 1: Управление статусами заказов

Добавил Order Status History — аудит всех изменений:

@Entity
@Table(name = "orders")
public class Order {
    @Id
    private UUID id;
    
    @Enumerated(EnumType.STRING)
    private OrderStatus status;
    
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "order")
    private List<OrderStatusHistory> statusHistory = new ArrayList<>();
    
    private BigDecimal totalAmount;
    private LocalDateTime createdAt;
}

@Entity
@Table(name = "order_status_history")
public class OrderStatusHistory {
    @Id
    private UUID id;
    
    @ManyToOne
    @JoinColumn(name = "order_id")
    private Order order;
    
    @Enumerated(EnumType.STRING)
    private OrderStatus previousStatus;
    
    @Enumerated(EnumType.STRING)
    private OrderStatus newStatus;
    
    private String reason;  // Причина изменения
    private LocalDateTime changedAt;
    private String changedBy;  // Кто изменил
}

public enum OrderStatus {
    PENDING,      // Ожидание подтверждения
    CONFIRMED,    // Подтвержден
    PROCESSING,   // На обработке
    SHIPPED,      // Отправлен
    DELIVERED,    // Доставлен
    CANCELLED,    // Отмен
    REFUNDED      // Возврат
}

Этап 2: Интеграция со Stripe

Добавил Payment Service для безопасной обработки платежей:

@Service
public class PaymentService {
    private final StripeService stripeService;
    private final OrderRepository orderRepository;
    private final EventPublisher eventPublisher;
    
    @Transactional
    public PaymentResult processPayment(UUID orderId, String paymentMethodId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));
        
        try {
            PaymentIntentRequest request = new PaymentIntentRequest()
                .setAmount(order.getTotalAmount().longValue() * 100)  // Stripe использует центы
                .setCurrency("usd")
                .setPaymentMethod(paymentMethodId)
                .setConfirm(true);
            
            PaymentIntent intent = stripeService.createPaymentIntent(request);
            
            if ("succeeded".equals(intent.getStatus())) {
                order.setStatus(OrderStatus.CONFIRMED);
                order.setPaymentId(intent.getId());
                orderRepository.save(order);
                
                eventPublisher.publish(new OrderPaymentSuccessEvent(orderId));
                return new PaymentResult(true, "Payment successful");
            } else if ("requires_action".equals(intent.getStatus())) {
                return new PaymentResult(false, "3D Secure required");
            }
        } catch (StripeException e) {
            eventPublisher.publish(new OrderPaymentFailedEvent(orderId, e.getMessage()));
            throw new PaymentProcessingException("Payment failed", e);
        }
        
        return new PaymentResult(false, "Payment declined");
    }
}

Этап 3: Асинхронная обработка через Kafka

Добавил Event-driven архитектуру для отправки уведомлений:

@Service
public class NotificationService {
    @KafkaListener(topics = "order-events", groupId = "notification-group")
    public void handleOrderEvent(OrderEvent event) {
        switch(event.getEventType()) {
            case PAYMENT_SUCCESS:
                sendConfirmationEmail(event.getOrderId());
                break;
            case ORDER_SHIPPED:
                sendShipmentNotification(event.getOrderId());
                break;
            case ORDER_DELIVERED:
                sendDeliveryConfirmation(event.getOrderId());
                break;
        }
    }
    
    @Async
    private void sendConfirmationEmail(UUID orderId) {
        Order order = orderRepository.findById(orderId).orElse(null);
        if (order != null) {
            emailService.send(
                order.getCustomerEmail(),
                "Order Confirmed",
                generateConfirmationTemplate(order)
            );
        }
    }
}

@Configuration
public class KafkaProducerConfig {
    @Bean
    public KafkaTemplate<String, OrderEvent> kafkaTemplate(ProducerFactory<String, OrderEvent> producerFactory) {
        return new KafkaTemplate<>(producerFactory);
    }
}

Этап 4: Кэширование с Redis

Добавил многоуровневое кэширование для оптимизации:

@Service
@CacheConfig(cacheNames = "orders")
public class OrderService {
    private final OrderRepository orderRepository;
    private final CacheManager cacheManager;
    
    @Cacheable(value = "orders", key = "#id")
    public Order getOrderById(UUID id) {
        return orderRepository.findById(id)
            .orElseThrow(() -> new OrderNotFoundException(id));
    }
    
    @CacheEvict(value = "orders", key = "#id")
    @Transactional
    public Order updateOrder(UUID id, UpdateOrderRequest request) {
        Order order = getOrderById(id);
        order.setStatus(request.getStatus());
        return orderRepository.save(order);
    }
    
    @Cacheable(value = "customer-orders", key = "#customerId")
    public List<Order> getCustomerOrders(UUID customerId) {
        return orderRepository.findByCustomerId(customerId);
    }
}

@Configuration
public class CacheConfig {
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(10))
            .serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(
                    new GenericJackson2JsonRedisSerializer()
                )
            );
        return RedisCacheManager.create(connectionFactory);
    }
}

Этап 5: Управление доступом (RBAC)

Добавил Role-Based Access Control с Spring Security:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/api/public/**").permitAll()
                .antMatchers("/api/orders/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            .and()
            .httpBasic();
        return http.build();
    }
}

@Service
public class OrderService {
    @PreAuthorize("#customerId == authentication.principal.id or hasRole('ADMIN')")
    public Order getOrder(UUID orderId, UUID customerId) {
        return orderRepository.findByIdAndCustomerId(orderId, customerId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));
    }
    
    @PreAuthorize("hasRole('ADMIN')")
    @Transactional
    public void cancelOrder(UUID orderId, String reason) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow();
        order.setStatus(OrderStatus.CANCELLED);
        orderRepository.save(order);
    }
}

Этап 6: Аналитика и отчеты

Добавил Analytics Service для бизнес-инсайтов:

@Service
public class AnalyticsService {
    @Query("""
        SELECT new com.orders.dto.SalesReport(
            CAST(o.createdAt AS DATE),
            COUNT(o),
            SUM(o.totalAmount)
        )
        FROM Order o
        WHERE o.createdAt >= :from AND o.createdAt <= :to
        GROUP BY CAST(o.createdAt AS DATE)
        ORDER BY CAST(o.createdAt AS DATE) DESC
    """)
    List<SalesReport> getDailySalesReport(
        @Param("from") LocalDateTime from,
        @Param("to") LocalDateTime to
    );
    
    public List<ProductAnalytics> getTopProducts(int limit) {
        return orderRepository.findTopProducts(limit);
    }
}

Интеграция компонентов

@RestController
@RequestMapping("/api/orders")
public class OrderController {
    private final OrderService orderService;
    private final PaymentService paymentService;
    private final KafkaTemplate<String, OrderEvent> kafkaTemplate;
    
    @PostMapping("/{id}/confirm")
    public ResponseEntity<OrderDto> confirmOrder(
        @PathVariable UUID id,
        @RequestBody PaymentRequest paymentRequest) {
        
        // Обработка платежа
        PaymentResult result = paymentService.processPayment(
            id,
            paymentRequest.getPaymentMethodId()
        );
        
        // Публикация события в Kafka
        kafkaTemplate.send("order-events",
            new OrderEvent(id, EventType.PAYMENT_SUCCESS));
        
        // Получение заказа (кэшированный)
        Order order = orderService.getOrderById(id);
        return ResponseEntity.ok(OrderDto.from(order));
    }
}

Результаты расширения

После всех изменений система:

  • Обрабатывает 10x больше заказов благодаря асинхронности
  • Уменьшила latency на 60% через кэширование
  • Улучшила безопасность с помощью RBAC и Stripe интеграции
  • Предоставляет аналитику для бизнес-решений
  • Остается масштабируемой с event-driven архитектурой

Каждый этап был протестирован, документирован, и развернут в production с мониторингом метрик.

Как расширяли функционал последнего проекта | PrepBro