8.05. Синхронная коммуникация
Синхронная коммуникация
Что такое синхронная коммуникация?
Синхронная коммуникация — это способ взаимодействия, при котором отправитель отправляет запрос и ждёт ответа от получателя. Этот подход широко используется в микросервисной архитектуре для операций, которые требуют немедленного результата (например, проверка данных, выполнение расчётов).
Синхронная коммуникация предполагает блокирующее взаимодействие: инициатор запроса ожидает завершения операции и получения ответа до продолжения выполнения. Клиент и сервер связаны во времени — задержка ответа напрямую влияет на производительность вызывающей стороны.
Примерами синхронности являются HTTP и RPC:
- HTTP/HTTPS - клиент отправляет HTTP-запрос (например, GET или POST), и сервер немедленно отвечает на этот запрос.
- RPC (Remote Procedure Call) - вызов удалённой процедуры, где клиент ожидает результата выполнения функции на сервере.
Для HTTP(S) используется, как правило, REST, а для RPC - gRPC.
HTTP

HTTP — протокол прикладного уровня, реализующий модель запрос-ответ поверх надёжного транспорта (обычно TCP). HTTPS добавляет шифрование через TLS/SSL.
Архитектурные особенности
- Состояние не сохраняется между запросами (stateless): каждый запрос должен содержать всю необходимую контекстную информацию.
- Версии протокола:
- HTTP/1.1: поддержка постоянных соединений (keep-alive), конвейеризация (pipeline) ограничена.
- HTTP/2: мультиплексирование потоков поверх одного TCP-соединения, сжатие заголовков (HPACK), серверные отправки (server push).
- HTTP/3: замена транспорта с TCP на QUIC (поверх UDP) для снижения задержки при установке соединения и устойчивости к потере пакетов.
- Методы запросов определяют семантику операции: GET (получение), POST (создание/действие), PUT/PATCH (обновление), DELETE (удаление).
- Коды состояния группируются по классам: 2xx (успех), 3xx (перенаправление), 4xx (ошибка клиента), 5xx (ошибка сервера).
Механизмы надёжности
- Повтор запроса при сетевых сбоях осуществляется на уровне приложения (идемпотентные методы GET, PUT, DELETE допускают безопасный повтор).
- Таймауты соединения и чтения настраиваются на клиенте и сервере.
- Для критичных операций применяется идемпотентность через идентификаторы запросов (например, идемпотентные ключи в платежных системах).
Ограничения синхронной модели
- Блокировка потока выполнения до получения ответа.
- Ограниченное время ожидания (таймауты прокси, балансировщиков, клиентских библиотек).
- Неэффективность для длительных операций (долгие вычисления, внешние интеграции) — требуется применение паттернов опроса (polling) или асинхронных обратных вызовов.
Типичные сценарии применения
- Веб-интерфейсы и мобильные клиенты.
- Интеграция между сервисами с низкой задержкой и простой семантикой запроса.
- RESTful API с чёткими контрактами и идемпотентными операциями.
RPC

RPC (Remote Procedure Call) — абстракция, позволяющая вызывать функцию на удалённом узле так, будто она локальная. Синхронная реализация подразумевает блокирующее ожидание результата.
Архитектурные особенности
- Стек вызова расширяется через сеть: клиент сериализует параметры → отправляет запрос → ожидает ответ → десериализует результат/исключение.
- Типы реализаций:
- gRPC (Google): бинарный протокол поверх HTTP/2, IDL на основе Protocol Buffers, поддержка потоковой передачи.
- Apache Thrift: мультиязыковая поддержка, собственный бинарный протокол (также поддерживает HTTP, JSON).
- JSON-RPC / XML-RPC: текстовые форматы поверх HTTP, простота отладки ценой производительности.
- CORBA (устаревший): сложная распределённая объектная модель с поддержкой транзакций и безопасности.
- Контракт интерфейса строго определяется в IDL (Interface Definition Language), что обеспечивает типобезопасность на этапе компиляции.
Механизмы надёжности
- Автоматические повторы (retry) с экспоненциальной задержкой и ограничением количества попыток.
- Таймауты на уровне вызова (deadline propagation) — критично для предотвращения каскадных отказов.
- Трассировка распределённых вызовов через контекстные заголовки (например, trace ID в gRPC metadata).
- Обнаружение сервисов (service discovery) и балансировка нагрузки на уровне клиента (client-side load balancing).
Проблемы синхронного RPC
- Сетевые разделения приводят к блокировке или таймаутам вызывающего потока.
- Каскадные отказы: медленный зависимый сервис блокирует все вызывающие его компоненты.
- Сложность обеспечения согласованности при распределённых транзакциях.
- Отсутствие встроенной очереди: пиковая нагрузка может привести к отказу сервиса.
Типичные сценарии применения
- Высокопроизводительные внутренние API между микросервисами с низкой задержкой.
- Системы, требующие строгой типизации и автоматической генерации клиентских библиотек.
- Синхронные операции с предсказуемым временем выполнения (менее 100–500 мс).
HTTP(S) работают по модели «запрос-ответ», поэтому всегда подразумевает клиент-серверное взаимодействие, ведь клиент (веб-браузер или приложение) формирует и отправляет запрос на сервер, а сервер, получает запрос, обрабатывает его и отправляет ответ клиенту.
HTTP мы уже ранее изучали, поэтому перейдём к другим протоколам.
gRPC, GraphQL
gRPC (Google Remote Procedure Call) — это современный фреймворк для создания высокопроизводительных RPC (Remote Procedure Call) сервисов. В отличие от REST, gRPC использует протокол HTTP/2 и формат Protocol Buffers (Protobuf).
Полная документация доступна здесь - https://grpc.io/
Этот подход лучше использовать для высокопроизводительных систем с большим объёмом данных, и если требуется строгая типизация и автоматическая генерация кода.
Protocol Buffers (Protobuf) — это специальный формат для описания контрактов (схем данных) и сериализации. Protobuf более компактный и быстрый, чем JSON.
HTTP/2 используется для передачи данных, что обеспечивает высокую производительность благодаря мультиплексированию, сжатию заголовков и двунаправленным потокам.
GraphQL — это язык запросов и среда выполнения для API, разработанный Facebook в 2012 году. Он позволяет клиентам запрашивать только те данные, которые им нужны, что делает его более гибким по сравнению с традиционными REST API. Клиент может указать точную структуру данных, которые он хочет получить.
Документация здесь - https://graphql.org/
Чит-лист - https://cheatsheets.zip/graphql
В отличие от REST, где каждый ресурс имеет свой URL, GraphQL использует один эндпоинт для всех запросов. GraphQL использует схему, которая определяет доступные данные и их типы, предоставляя документацию.
Пример реализации
gRPC с Protocol Buffers
Определение контракта (.proto файл)
syntax = "proto3";
package user;
option csharp_namespace = "UserService";
option java_package = "com.example.user";
option java_outer_classname = "UserProto";
// Сообщения
message UserRequest {
int32 user_id = 1;
}
message UserResponse {
int32 user_id = 1;
string name = 2;
string email = 3;
bool is_active = 4;
}
message UserListRequest {
int32 page = 1;
int32 page_size = 2;
}
message UserListResponse {
repeated UserResponse users = 1;
int32 total_count = 2;
}
// Сервис
service UserService {
rpc GetUser(UserRequest) returns (UserResponse);
rpc ListUsers(UserListRequest) returns (UserListResponse);
rpc CreateUser(UserResponse) returns (UserResponse);
rpc UpdateUser(UserResponse) returns (UserResponse);
rpc DeleteUser(UserRequest) returns (google.protobuf.Empty);
}
Реализация на C#
Сервер:
using Grpc.Core;
using UserService;
public class UserServiceImpl : UserService.UserServiceBase
{
private readonly Dictionary<int, UserResponse> _users = new();
public override Task<UserResponse> GetUser(
UserRequest request,
ServerCallContext context)
{
if (_users.TryGetValue(request.UserId, out var user))
return Task.FromResult(user);
throw new RpcException(
new Status(StatusCode.NotFound, "User not found"));
}
public override Task<UserListResponse> ListUsers(
UserListRequest request,
ServerCallContext context)
{
var skip = (request.Page - 1) * request.PageSize;
var users = _users.Values
.Skip(skip)
.Take(request.PageSize)
.ToList();
return Task.FromResult(new UserListResponse
{
Users = { users },
TotalCount = _users.Count
});
}
public override Task<UserResponse> CreateUser(
UserResponse request,
ServerCallContext context)
{
var newUser = new UserResponse
{
UserId = _users.Count + 1,
Name = request.Name,
Email = request.Email,
IsActive = request.IsActive
};
_users[newUser.UserId] = newUser;
return Task.FromResult(newUser);
}
}
// Запуск сервера
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
var app = builder.Build();
app.MapGrpcService<UserServiceImpl>();
app.Run();
Клиент:
using Grpc.Net.Client;
using UserService;
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new UserService.UserServiceClient(channel);
// Получение пользователя
var user = await client.GetUserAsync(new UserRequest { UserId = 1 });
Console.WriteLine($"User: {user.Name}, Email: {user.Email}");
// Создание пользователя
var newUser = await client.CreateUserAsync(new UserResponse
{
Name = "Тимур",
Email = "timur@example.com",
IsActive = true
});
Console.WriteLine($"Created user ID: {newUser.UserId}");
Реализация на Python
Сервер:
import grpc
from concurrent import futures
import user_pb2
import user_pb2_grpc
class UserServiceImpl(user_pb2_grpc.UserServiceServicer):
def __init__(self):
self.users = {}
def GetUser(self, request, context):
user = self.users.get(request.user_id)
if not user:
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details('User not found')
return user_pb2.UserResponse()
return user
def ListUsers(self, request, context):
skip = (request.page - 1) * request.page_size
users_list = list(self.users.values())[skip:skip + request.page_size]
return user_pb2.UserListResponse(
users=users_list,
total_count=len(self.users)
)
def CreateUser(self, request, context):
user_id = len(self.users) + 1
new_user = user_pb2.UserResponse(
user_id=user_id,
name=request.name,
email=request.email,
is_active=request.is_active
)
self.users[user_id] = new_user
return new_user
# Запуск сервера
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
user_pb2_grpc.add_UserServiceServicer_to_server(UserServiceImpl(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
if __name__ == '__main__':
serve()
Клиент:
import grpc
import user_pb2
import user_pb2_grpc
def run():
channel = grpc.insecure_channel('localhost:50051')
stub = user_pb2_grpc.UserServiceStub(channel)
# Получение пользователя
response = stub.GetUser(user_pb2.UserRequest(user_id=1))
print(f"User: {response.name}, Email: {response.email}")
# Создание пользователя
new_user = stub.CreateUser(user_pb2.UserResponse(
name="Тимур",
email="timur@example.com",
is_active=True
))
print(f"Created user ID: {new_user.user_id}")
if __name__ == '__main__':
run()
Реализация на Java
Сервер:
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import com.example.user.*;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
private final Map<Integer, UserResponse> users = new ConcurrentHashMap<>();
@Override
public void getUser(UserRequest request, StreamObserver<UserResponse> responseObserver) {
UserResponse user = users.get(request.getUserId());
if (user == null) {
responseObserver.onError(
io.grpc.Status.NOT_FOUND
.withDescription("User not found")
.asRuntimeException()
);
} else {
responseObserver.onNext(user);
responseObserver.onCompleted();
}
}
@Override
public void listUsers(UserListRequest request, StreamObserver<UserListResponse> responseObserver) {
int skip = (request.getPage() - 1) * request.getPageSize();
UserListResponse.Builder builder = UserListResponse.newBuilder();
users.values().stream()
.skip(skip)
.limit(request.getPageSize())
.forEach(builder::addUsers);
builder.setTotalCount(users.size());
responseObserver.onNext(builder.build());
responseObserver.onCompleted();
}
@Override
public void createUser(UserResponse request, StreamObserver<UserResponse> responseObserver) {
int userId = users.size() + 1;
UserResponse newUser = UserResponse.newBuilder()
.setUserId(userId)
.setName(request.getName())
.setEmail(request.getEmail())
.setIsActive(request.getIsActive())
.build();
users.put(userId, newUser);
responseObserver.onNext(newUser);
responseObserver.onCompleted();
}
public static void main(String[] args) throws IOException, InterruptedException {
Server server = ServerBuilder.forPort(50051)
.addService(new UserServiceImpl())
.build()
.start();
System.out.println("Server started on port 50051");
server.awaitTermination();
}
}
Клиент:
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import com.example.user.*;
public class UserClient {
public static void main(String[] args) {
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 50051)
.usePlaintext()
.build();
UserServiceGrpc.UserServiceBlockingStub stub =
UserServiceGrpc.newBlockingStub(channel);
// Получение пользователя
UserResponse user = stub.getUser(
UserRequest.newBuilder().setUserId(1).build()
);
System.out.println("User: " + user.getName() + ", Email: " + user.getEmail());
// Создание пользователя
UserResponse newUser = stub.createUser(
UserResponse.newBuilder()
.setName("Тимур")
.setEmail("timur@example.com")
.setIsActive(true)
.build()
);
System.out.println("Created user ID: " + newUser.getUserId());
channel.shutdown();
}
}
GraphQL
Схема типов (schema.graphql)
type User {
id: ID!
name: String!
email: String!
isActive: Boolean!
createdAt: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
publishedAt: String!
}
type Query {
user(id: ID!): User
users(page: Int = 1, pageSize: Int = 10): [User!]!
post(id: ID!): Post
posts(page: Int = 1, pageSize: Int = 10): [Post!]!
}
type Mutation {
createUser(name: String!, email: String!, isActive: Boolean = true): User!
updateUser(id: ID!, name: String, email: String, isActive: Boolean): User!
deleteUser(id: ID!): Boolean!
createPost(title: String!, content: String!, authorId: ID!): Post!
}
type Subscription {
userCreated: User!
postCreated: Post!
}
Реализация на C# (Hot Chocolate)
Модели:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int AuthorId { get; set; }
public DateTime PublishedAt { get; set; }
public User Author { get; set; }
}
Резолверы:
public class Query
{
private readonly List<User> _users = new();
private readonly List<Post> _posts = new();
public User GetUser(int id) => _users.FirstOrDefault(u => u.Id == id);
public List<User> GetUsers(int page = 1, int pageSize = 10)
{
int skip = (page - 1) * pageSize;
return _users.Skip(skip).Take(pageSize).ToList();
}
public Post GetPost(int id) => _posts.FirstOrDefault(p => p.Id == id);
public List<Post> GetPosts(int page = 1, int pageSize = 10)
{
int skip = (page - 1) * pageSize;
return _posts.Skip(skip).Take(pageSize).ToList();
}
}
public class Mutation
{
private readonly List<User> _users = new();
private readonly List<Post> _posts = new();
public User CreateUser(string name, string email, bool isActive = true)
{
var user = new User
{
Id = _users.Count + 1,
Name = name,
Email = email,
IsActive = isActive,
CreatedAt = DateTime.UtcNow
};
_users.Add(user);
return user;
}
public User UpdateUser(int id, string? name = null, string? email = null, bool? isActive = null)
{
var user = _users.FirstOrDefault(u => u.Id == id);
if (user == null) throw new Exception("User not found");
if (name != null) user.Name = name;
if (email != null) user.Email = email;
if (isActive != null) user.IsActive = isActive.Value;
return user;
}
public bool DeleteUser(int id)
{
var user = _users.FirstOrDefault(u => u.Id == id);
if (user == null) return false;
return _users.Remove(user);
}
public Post CreatePost(string title, string content, int authorId)
{
var author = _users.FirstOrDefault(u => u.Id == authorId);
if (author == null) throw new Exception("Author not found");
var post = new Post
{
Id = _posts.Count + 1,
Title = title,
Content = content,
AuthorId = authorId,
PublishedAt = DateTime.UtcNow,
Author = author
};
_posts.Add(post);
return post;
}
}
Настройка сервера:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.AddSubscriptionType<Subscription>()
.AddType<UserType>()
.AddType<PostType>();
var app = builder.Build();
app.MapGraphQL();
app.Run();
Типы GraphQL:
public class UserType : ObjectType<User>
{
protected override void Configure(IObjectTypeDescriptor<User> descriptor)
{
descriptor.Field(u => u.Id).Type<NonNullType<IDType>>();
descriptor.Field(u => u.Posts)
.ResolveWith<Query>(q => q.GetPosts(default!, default!))
.Type<ListType<NonNullType<PostType>>>();
}
}
public class PostType : ObjectType<Post>
{
protected override void Configure(IObjectTypeDescriptor<Post> descriptor)
{
descriptor.Field(p => p.Author)
.ResolveWith<Query>(q => q.GetUser(default!))
.Type<NonNullType<UserType>>();
}
}
Реализация на Python (Strawberry)
Схема и резолверы:
import strawberry
from typing import List, Optional
from datetime import datetime
@strawberry.type
class User:
id: int
name: str
email: str
is_active: bool
created_at: str
@strawberry.field
def posts(self) -> List["Post"]:
return [post for post in posts if post.author_id == self.id]
@strawberry.type
class Post:
id: int
title: str
content: str
author_id: int
published_at: str
@strawberry.field
def author(self) -> User:
return next(user for user in users if user.id == self.author_id)
# Хранилище данных
users: List[User] = []
posts: List[Post] = []
@strawberry.type
class Query:
@strawberry.field
def user(self, id: int) -> Optional[User]:
return next((u for u in users if u.id == id), None)
@strawberry.field
def users(self, page: int = 1, page_size: int = 10) -> List[User]:
start = (page - 1) * page_size
return users[start:start + page_size]
@strawberry.field
def post(self, id: int) -> Optional[Post]:
return next((p for p in posts if p.id == id), None)
@strawberry.field
def posts(self, page: int = 1, page_size: int = 10) -> List[Post]:
start = (page - 1) * page_size
return posts[start:start + page_size]
@strawberry.type
class Mutation:
@strawberry.mutation
def create_user(self, name: str, email: str, is_active: bool = True) -> User:
user = User(
id=len(users) + 1,
name=name,
email=email,
is_active=is_active,
created_at=datetime.utcnow().isoformat()
)
users.append(user)
return user
@strawberry.mutation
def update_user(
self,
id: int,
name: Optional[str] = None,
email: Optional[str] = None,
is_active: Optional[bool] = None
) -> User:
user = next((u for u in users if u.id == id), None)
if not user:
raise Exception("User not found")
if name: user.name = name
if email: user.email = email
if is_active is not None: user.is_active = is_active
return user
@strawberry.mutation
def delete_user(self, id: int) -> bool:
global users
initial_count = len(users)
users = [u for u in users if u.id != id]
return len(users) < initial_count
@strawberry.mutation
def create_post(self, title: str, content: str, author_id: int) -> Post:
author = next((u for u in users if u.id == author_id), None)
if not author:
raise Exception("Author not found")
post = Post(
id=len(posts) + 1,
title=title,
content=content,
author_id=author_id,
published_at=datetime.utcnow().isoformat()
)
posts.append(post)
return post
@strawberry.type
class Subscription:
@strawberry.subscription
async def user_created(self) -> User:
# Реализация через asyncio.Queue или Redis Pub/Sub
pass
schema = strawberry.Schema(query=Query, mutation=Mutation, subscription=Subscription)
Запуск сервера (FastAPI):
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
graphql_app = GraphQLRouter(schema)
app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Реализация на Java (GraphQL Java)
Схема:
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import java.io.InputStream;
public class GraphQLSchemaProvider {
public GraphQLSchema createSchema() {
SchemaParser parser = new SchemaParser();
TypeDefinitionRegistry typeRegistry = parser.parse(
this.getClass().getResourceAsStream("/schema.graphqls")
);
RuntimeWiring wiring = RuntimeWiring.newRuntimeWiring()
.type("Query", typeWiring -> typeWiring
.dataFetcher("user", new UserDataFetcher())
.dataFetcher("users", new UsersDataFetcher())
.dataFetcher("post", new PostDataFetcher())
.dataFetcher("posts", new PostsDataFetcher())
)
.type("Mutation", typeWiring -> typeWiring
.dataFetcher("createUser", new CreateUserMutation())
.dataFetcher("updateUser", new UpdateUserMutation())
.dataFetcher("deleteUser", new DeleteUserMutation())
.dataFetcher("createPost", new CreatePostMutation())
)
.type("User", typeWiring -> typeWiring
.dataFetcher("posts", new UserPostsDataFetcher())
)
.type("Post", typeWiring -> typeWiring
.dataFetcher("author", new PostAuthorDataFetcher())
)
.build();
SchemaGenerator generator = new SchemaGenerator();
return generator.makeExecutableSchema(typeRegistry, wiring);
}
}
DataFetcher для запросов:
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class UserDataFetcher implements DataFetcher<User> {
private final Map<Integer, User> users = new ConcurrentHashMap<>();
@Override
public User get(DataFetchingEnvironment environment) {
Integer id = environment.getArgument("id");
return users.get(id);
}
}
public class UsersDataFetcher implements DataFetcher<List<User>> {
private final Map<Integer, User> users = new ConcurrentHashMap<>();
@Override
public List<User> get(DataFetchingEnvironment environment) {
Integer page = environment.getArgument("page");
Integer pageSize = environment.getArgument("pageSize");
int skip = (page - 1) * pageSize;
return users.values().stream()
.skip(skip)
.limit(pageSize)
.collect(Collectors.toList());
}
}
Модели:
public class User {
private int id;
private String name;
private String email;
private boolean isActive;
private String createdAt;
// getters/setters
}
public class Post {
private int id;
private String title;
private String content;
private int authorId;
private String publishedAt;
// getters/setters
}
Запуск сервера (Spring Boot):
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GraphQLConfig {
@Bean
public GraphQL graphQL(GraphQLSchemaProvider schemaProvider) {
GraphQLSchema schema = schemaProvider.createSchema();
return GraphQL.newGraphQL(schema).build();
}
}
@RestController
@RequestMapping("/graphql")
public class GraphQLController {
@Autowired
private GraphQL graphQL;
@PostMapping(produces = "application/json")
public ResponseEntity<Object> graphql(@RequestBody String query) {
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(query)
.build();
ExecutionResult executionResult = graphQL.execute(executionInput);
return ResponseEntity.ok(executionResult.toSpecification());
}
}
Примеры запросов и мутаций
Запрос пользователя с постами:
query {
user(id: "1") {
id
name
email
isActive
posts {
id
title
publishedAt
}
}
}
Список пользователей с пагинацией:
query {
users(page: 1, pageSize: 5) {
id
name
email
posts {
id
title
}
}
}
Создание пользователя:
mutation {
createUser(name: "Тимур", email: "timur@example.com", isActive: true) {
id
name
email
createdAt
}
}
Создание поста:
mutation {
createPost(
title: "GraphQL vs REST",
content: "Сравнительный анализ...",
authorId: "1"
) {
id
title
author {
name
email
}
}
}
Фрагменты для повторного использования:
fragment UserFields on User {
id
name
email
isActive
}
query {
user(id: "1") {
...UserFields
posts {
id
title
}
}
}
Параметризованный запрос:
query GetUserById($userId: ID!) {
user(id: $userId) {
id
name
posts {
id
title
content
}
}
}
# Variables:
# {
# "userId": "1"
# }