Введение: эволюция API на Java
Мир Java-разработки не стоит на месте. Ещё 10 лет назад выбор был очевиден: REST и JSON. Сегодня же, проектируя микросервисную архитектуру или мобильное приложение, разработчик сталкивается с целым зоопарком протоколов. REST, GraphQL, gRPC — каждый из них обещает райские кущи, но в реальности за каждой технологией стоят компромиссы.
В этой статье мы не будем пересказывать документацию. Мы возьмём живую задачу: нам нужно создать API для образовательной платформы (как наш PythonLib, только на Java). У нас есть курсы, студенты и прогресс обучения. И мы попробуем реализовать один и тот же endpoint тремя разными способами.
Поехали разбираться, какой протокол сделает ваш код быстрее, а жизнь — проще.
REST API на Java: классика, которая не умирает
REST (Representational State Transfer) — это архитектурный стиль, а не протокол. В Java стандартом де-факто для REST стал Spring Boot с его аннотациями. Простота, понятность, огромное комьюнити — вот за что мы любим REST.
Пример: получаем список курсов
Допустим, нам нужно отдать клиенту список всех курсов с базовой информацией:
@RestController
@RequestMapping("/api/courses")
public class CourseController {
@GetMapping
public ResponseEntity<List<CourseDTO>> getAllCourses() {
List<Course> courses = courseService.findAll();
List<CourseDTO> dtos = courses.stream()
.map(c -> new CourseDTO(c.getId(), c.getTitle(), c.getAuthor()))
.toList();
return ResponseEntity.ok(dtos);
}
}
Клиент получает JSON:
[
{"id": 1, "title": "Java для начинающих", "author": "Иван Петров"},
{"id": 2, "title": "Spring Boot Masterclass", "author": "Анна Смирнова"}
]
Плюсы REST на Java:
- Минимальный порог входа — любой Java-разработчик знает Spring.
- Кэширование через HTTP-заголовки (ETag, Cache-Control).
- Широкая поддержка инструментов (Postman, Swagger, OpenAPI).
Минусы:
- Проблема over-fetching (лишние данные) и under-fetching (не хватает данных, нужно делать несколько запросов).
- Слабая типизация — всё решает контракт, который легко нарушить.
REST идеален для публичных API, когда клиенты — третьи лица, и вам нужна максимальная совместимость. Но если ваш клиент — мобильное приложение, которое хочет получать ровно то, что нужно, REST начинает раздражать.
GraphQL на Java: дайте мне только то, что я прошу
GraphQL — это язык запросов, созданный Facebook. В Java его чаще всего реализуют через библиотеку graphql-java или интеграцию с Spring — GraphQL Kickstart.
Клиент сам решает, какие поля ему нужны. Никакого over-fetching. Один запрос — и ты получаешь курсы вместе с прогрессом студента.
Пример: GraphQL schema и resolver
Определяем схему:
type Course {
id: ID!
title: String!
author: String!
progress(studentId: ID!): Int
}
type Query {
courses: [Course!]!
}
Пишем резолвер на Java:
@Component
public class CourseResolver implements GraphQLQueryResolver {
@Autowired
private CourseService courseService;
public List<Course> courses() {
return courseService.findAll();
}
}
@Component
public class CourseFieldResolver implements GraphQLResolver<Course> {
@Autowired
private ProgressService progressService;
public Integer progress(Course course, Long studentId) {
return progressService.getProgress(course.getId(), studentId);
}
}
Запрос от клиента:
query {
courses {
title
progress(studentId: 42)
}
}
Ответ — только title и прогресс. Никаких лишних полей.
Плюсы GraphQL на Java:
- Гибкость запросов — один endpoint решает всё.
- Сильная типизация схемы (SDL) — меньше ошибок на стыке фронта и бэка.
- Отличная документация через GraphiQL или Voyager.
Минусы:
- Сложность кэширования на уровне HTTP (POST-запросы, динамические ответы).
- Риск N+1 проблемы — если не использовать DataLoader, каждый запрос к БД будет множиться.
- Кривая обучения для команды, привыкшей к REST.
GraphQL — выбор для сложных клиентских приложений с динамическими интерфейсами. Например, дашборды или мобильные приложения с разными ролями пользователей.
gRPC на Java: скорость и строгость контрактов
gRPC — это RPC-фреймворк от Google, использующий Protocol Buffers (protobuf) для сериализации и HTTP/2 для транспорта. В Java он живёт через библиотеку io.grpc.
Если вам нужно передавать гигабайты данных между микросервисами с минимальной задержкой — gRPC ваш выбор. Здесь нет человекочитаемого JSON, зато есть бинарный протокол и строгий контракт.
Пример: gRPC сервис на Java
Сначала описываем proto-файл:
syntax = "proto3";
service CourseService {
rpc GetCourse (CourseRequest) returns (CourseResponse);
}
message CourseRequest {
int32 id = 1;
}
message CourseResponse {
int32 id = 1;
string title = 2;
string author = 3;
}
Генерируем Java-классы (через Maven/Gradle плагин) и реализуем сервис:
public class CourseServiceImpl extends CourseServiceGrpc.CourseServiceImplBase {
@Override
public void getCourse(CourseRequest request,
StreamObserver<CourseResponse> responseObserver) {
Course course = courseService.findById(request.getId());
CourseResponse response = CourseResponse.newBuilder()
.setId(course.getId())
.setTitle(course.getTitle())
.setAuthor(course.getAuthor())
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
Клиент вызывает метод как локальный:
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext()
.build();
CourseServiceGrpc.CourseServiceBlockingStub stub = CourseServiceGrpc.newBlockingStub(channel);
CourseResponse response = stub.getCourse(CourseRequest.newBuilder().setId(1).build());
System.out.println(response.getTitle());
Плюсы gRPC на Java:
- Высокая производительность (бинарный протокол, HTTP/2, потоковая передача).
- Автоматическая генерация кода из proto-файлов — контракт всегда в актуальном состоянии.
- Поддержка стриминга (server-streaming, client-streaming, bidirectional).
Минусы:
-
<