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

Почему REST синхронный?

2.0 Middle🔥 141 комментариев
#REST API и микросервисы

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

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

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

Почему REST синхронный

Краткий ответ

REST синхронный по своей природе, потому что основан на HTTP протоколе, где клиент отправляет запрос и ЖДЕТ ответ. Это не техническое ограничение, а архитектурное решение. REST требует request-response цикл, где клиент блокируется до получения ответа от сервера.

HTTP: Request-Response модель

Клиент                          Сервер
   |                              |
   |---- HTTP запрос ------------->|  (1. Клиент отправил)
   |  (блокируется, ждет)        |
   |                         (2. Сервер обрабатывает)
   |<---- HTTP ответ -------------|  (3. Сервер ответил)
   |  (разблокируется)           |
   |                              |

Это синхронное взаимодействие.

Природа REST

REST (Representational State Transfer) построен на HTTP методах:

// GET - получить ресурс
GET /api/users/1 HTTP/1.1

// Клиент ждет:
HTTP/1.1 200 OK
{ "id": 1, "name": "John" }

// POST - создать ресурс
POST /api/users HTTP/1.1
{ "name": "Jane" }

// Клиент ждет:
HTTP/1.1 201 Created
{ "id": 2, "name": "Jane" }

// PUT - обновить ресурс
PUT /api/users/1 HTTP/1.1
{ "name": "John Doe" }

// Клиент ждет:
HTTP/1.1 200 OK

Каждый запрос ожидает ответ.

Синхронный код в Java

// Synchronous REST call
public class UserService {
    private RestTemplate restTemplate;
    
    public User getUser(Long id) {
        // Блокирующий вызов!
        // Поток ждет ответа от сервера
        ResponseEntity<User> response = restTemplate.getForEntity(
            "https://api.example.com/users/" + id,
            User.class
        );
        return response.getBody();
    }
}

// Использование
User user = userService.getUser(1);
// Этот код блокируется, пока не придет ответ
System.out.println(user.getName());

Почему это синхронно

1. TCP соединение синхронное

Транспортный уровень (TCP):

Клиент                 Сервер
  |-------- SYN -------->|  (подключение)
  |<------ SYN-ACK ------|  (подтверждение)
  |-------- ACK -------->|  (подтверждение)
  |                      |
  |---- HTTP запрос ---->|  (отправляем)
  |---- (ждем) ------    |  (блокируемся)
  |<---- HTTP ответ -----|  (получаем)
  |---- ACK ------------>|  (подтверждаем)

2. HTTP запрос-ответ синхронный

HTTP спецификация определяет:

  • 1 запрос = 1 ответ
  • Ответ не может прийти раньше запроса
  • Клиент должен дождаться полного ответа

Проблемы синхронного REST

Проблема 1: Thread per Request

@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        // Для каждого запроса Tomcat создает новый поток
        // Поток заблокирован, пока идет обработка
        
        User user = userService.getUser(id);  // может быть медленным
        Profile profile = profileService.getProfile(id);  // заблокирован!
        
        return user;
    }
}

// С 10000 одновременных пользователей:
// Tomcat должен создать 10000 потоков
// Это огромные затраты памяти
// Stack per thread: 1MB * 10000 = 10GB RAM!

Проблема 2: Nested Calls

@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
    Order order = orderService.getOrder(id);  // блок 1
    
    List<Product> products = order.getProducts().stream()
        .map(p -> productService.getProduct(p.getId()))  // блок 2
        .collect(toList());
    
    User user = userService.getUser(order.getUserId());  // блок 3
    
    // Общее время: блок1 + блок2 + блок3 (последовательно)
    // Если каждый блок 100ms, то 300ms всего
    // Пока один блок выполняется, поток ничего не делает
}

Асинхронные альтернативы

1. Callback Hell

// JavaScript (асинхронный)
function getUser(id, callback) {
    fetch(`/api/users/${id}`)
        .then(response => response.json())
        .then(user => callback(user));
}

getUser(1, function(user) {
    getProfile(user.id, function(profile) {
        getOrders(user.id, function(orders) {
            console.log(user, profile, orders);
            // Callback hell!
        });
    });
});

2. Promise (более чистый)

fetch(`/api/users/${id}`)
    .then(response => response.json())
    .then(user => {
        return Promise.all([
            fetch(`/api/profiles/${user.id}`).then(r => r.json()),
            fetch(`/api/orders/${user.id}`).then(r => r.json())
        ]).then(([profile, orders]) => ({ user, profile, orders }));
    })
    .then(data => console.log(data));

3. Async/Await (современный подход)

async function getUserWithDetails(id) {
    const user = await fetch(`/api/users/${id}`).then(r => r.json());
    
    const [profile, orders] = await Promise.all([
        fetch(`/api/profiles/${user.id}`).then(r => r.json()),
        fetch(`/api/orders/${user.id}`).then(r => r.json())
    ]);
    
    return { user, profile, orders };
}

Java: Асинхронные решения

1. WebClient (Spring Reactive)

@Service
public class UserService {
    private WebClient webClient;
    
    public Mono<User> getUser(Long id) {
        // Асинхронный (non-blocking) запрос
        return webClient.get()
            .uri("/users/{id}", id)
            .retrieve()
            .bodyToMono(User.class);
    }
}

// Использование
userService.getUser(1)
    .subscribe(user -> System.out.println(user.getName()));
    // Не блокирует!

2. CompletableFuture

public CompletableFuture<User> getUser(Long id) {
    return CompletableFuture.supplyAsync(() -> {
        // Выполняется в отдельном потоке
        return restTemplate.getForObject(
            "https://api.example.com/users/" + id,
            User.class
        );
    });
}

// Использование
getUser(1)
    .thenApply(user -> user.getName())
    .thenAccept(System.out::println);

3. Virtual Threads (Java 21+)

// Spring Boot 3.2+ с Java 21
public class UserController {
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        // Синхронный код, но выполняется на virtual thread
        // Virtual threads очень легкие (миллионы можно запустить)
        return userService.getUser(id);
    }
}

// Можно запустить миллионы virtual threads:
// Каждый virtual thread занимает < 1KB памяти
// В отличие от 1MB для OS thread

Сравнение подходов

ПодходСинхронный?MemoryLatencyComplexity
RestTemplate (sync)ДаВысокаяВысокаяНизкая
AsyncRestTemplateНетНизкаяНизкаяСредняя
WebClient (reactive)НетНизкаяНизкаяВысокая
Virtual ThreadsДаНизкаяНизкаяНизкая
CompletableFutureНетНизкаяНизкаяСредняя

Когда REST должен быть синхронным

// 1. Большинство REST API синхронные
// Простота, понятность, predictability

@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody OrderDto dto) {
    Order order = orderService.create(dto);
    return ResponseEntity.status(201).body(order);
}

// 2. Клиент получает результат сразу
Order order = restTemplate.postForObject(url, dto, Order.class);

Когда REST должен быть асинхронным

// 1. Долгие операции
@PostMapping("/reports/generate")
public ResponseEntity<String> generateReport(@RequestBody ReportDto dto) {
    String jobId = reportService.startGenerationAsync(dto);
    return ResponseEntity.accepted()
        .location(URI.create("/reports/" + jobId))
        .build();
}

// 2. Клиент должен проверять статус
GET /reports/{jobId}  -> { status: "processing", progress: 50 }
GET /reports/{jobId}  -> { status: "completed", url: "/download/..." }

// 3. WebSocket для real-time уведомлений
@SendTo("/topic/reports/{jobId}")
public ReportProgress updateProgress(@Payload ReportProgress progress) {
    return progress;
}

Вывод

REST синхронный, потому что HTTP протокол, на котором он основан, требует request-response цикла. Это архитектурное решение, а не техническое ограничение.

Для асинхронных операций:

  1. Используй WebClient (Spring Reactive)
  2. Используй Virtual Threads (Java 21+)
  3. Используй асинхронный REST (202 Accepted + polling)
  4. Используй WebSocket для real-time
  5. Используй Message Queues (RabbitMQ, Kafka) для фоновых задач
Почему REST синхронный? | PrepBro