-
Notifications
You must be signed in to change notification settings - Fork 0
Advanced Usage
Advanced patterns and features for using Graphite effectively.
GraphQL custom scalars map to Java types. Graphite provides built-in support for common scalars and allows you to add your own.
| GraphQL Scalar | Java Type | Coercing Class |
|---|---|---|
DateTime |
java.time.Instant |
DateTimeCoercing |
Date |
java.time.LocalDate |
DateCoercing |
Time |
java.time.LocalTime |
TimeCoercing |
BigDecimal |
java.math.BigDecimal |
BigDecimalCoercing |
BigInteger |
java.math.BigInteger |
BigIntegerCoercing |
Long |
java.lang.Long |
LongCoercing |
UUID |
java.util.UUID |
UuidCoercing |
JSON |
com.fasterxml.jackson.databind.JsonNode |
JsonCoercing |
Void |
java.lang.Void |
VoidCoercing |
In Gradle:
graphite {
scalars = mapOf(
"DateTime" to "java.time.Instant",
"Date" to "java.time.LocalDate",
"Money" to "java.math.BigDecimal"
)
}In Maven:
<scalars>
<DateTime>java.time.Instant</DateTime>
<Date>java.time.LocalDate</Date>
<Money>java.math.BigDecimal</Money>
</scalars>Implement ScalarCoercing for custom serialization/deserialization:
import io.github.graphite.scalar.ScalarCoercing;
public class MoneyCoercing implements ScalarCoercing<Money> {
public static final MoneyCoercing INSTANCE = new MoneyCoercing();
@Override
public Money deserialize(Object value) {
if (value instanceof String s) {
return Money.parse(s);
}
if (value instanceof Map<?, ?> map) {
BigDecimal amount = new BigDecimal(map.get("amount").toString());
String currency = map.get("currency").toString();
return Money.of(amount, currency);
}
throw new IllegalArgumentException("Cannot deserialize Money from: " + value);
}
@Override
public Object serialize(Money value) {
return Map.of(
"amount", value.getAmount().toString(),
"currency", value.getCurrency().getCurrencyCode()
);
}
}Register your coercing:
import io.github.graphite.scalar.ScalarRegistry;
ScalarRegistry registry = ScalarRegistry.builder()
.register("Money", MoneyCoercing.INSTANCE)
.build();
GraphiteClient client = GraphiteClient.builder()
.endpoint(uri)
.scalarRegistry(registry)
.build();Interceptors allow you to modify requests before they are sent.
import io.github.graphite.interceptor.RequestInterceptor;
public class AuthInterceptor implements RequestInterceptor {
private final TokenProvider tokenProvider;
public AuthInterceptor(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public HttpRequest intercept(HttpRequest request) {
String token = tokenProvider.getAccessToken();
return request.withHeader("Authorization", "Bearer " + token);
}
}Register the interceptor:
GraphiteClient client = GraphiteClient.builder()
.endpoint(uri)
.requestInterceptor(new AuthInterceptor(tokenProvider))
.build();public class LoggingInterceptor implements RequestInterceptor {
private static final Logger log = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public HttpRequest intercept(HttpRequest request) {
log.debug("GraphQL Request: {} {}", request.method(), request.uri());
log.trace("Request body: {}", request.body());
return request;
}
}Process responses after they are received:
import io.github.graphite.interceptor.ResponseInterceptor;
public class MetricsInterceptor implements ResponseInterceptor {
private final MeterRegistry meterRegistry;
@Override
public HttpResponse intercept(HttpResponse response, HttpRequest request) {
meterRegistry.counter("graphql.responses",
"status", String.valueOf(response.statusCode()))
.increment();
return response;
}
}var response = client.execute(query);
if (response.hasErrors()) {
for (GraphQLError error : response.errors()) {
log.error("GraphQL error: {} at {}",
error.message(),
error.locations());
// Check error extensions for error codes
if (error.extensions() != null) {
String code = (String) error.extensions().get("code");
if ("UNAUTHORIZED".equals(code)) {
throw new UnauthorizedException(error.message());
}
}
}
}
// Or use getDataOrThrow() to throw on errors
UserDTO user = response.getDataOrThrow();GraphiteException (base)
├── GraphiteClientException (client-side errors)
│ ├── GraphiteConnectionException (network failures)
│ ├── GraphiteTimeoutException (timeouts)
│ └── GraphiteRateLimitException (rate limited)
└── GraphiteServerException (server-side errors)
└── GraphiteGraphQLException (GraphQL errors in response)
try {
client.execute(query);
} catch (GraphiteConnectionException e) {
log.error("Network error: {}", e.getMessage());
// Implement fallback logic
} catch (GraphiteTimeoutException e) {
log.error("Timeout: {} (type: {})", e.getMessage(), e.getTimeoutType());
// CONNECT, READ, or REQUEST timeout
} catch (GraphiteRateLimitException e) {
log.warn("Rate limited, retry after: {}", e.getRetryAfter());
// Wait and retry
} catch (GraphiteGraphQLException e) {
log.error("GraphQL errors: {}", e.getErrors());
// Handle application-level errors
}Graphite exposes metrics via Micrometer:
// Request metrics
graphite.client.requests{operation=GetUser, status=success}
graphite.client.requests{operation=GetUser, status=error}
graphite.client.request.duration{operation=GetUser}
// Retry metrics
graphite.client.retry.attempts{operation=GetUser}
graphite.client.retry.exhausted{operation=GetUser}
// HTTP connection pool metrics
graphite.http.connections.active{client=default}
graphite.http.connections.pending{client=default}
graphite.http.connections.max{client=default}
graphite.http.connections.total{client=default}
graphite.http.connections.acquired{client=default}Graphite propagates trace context automatically when Micrometer Tracing is on the classpath:
# application.yml
spring:
application:
name: my-service
management:
tracing:
enabled: true
sampling:
probability: 1.0The following headers are propagated:
-
traceparent(W3C Trace Context) -
tracestate(W3C Trace Context) -
X-B3-TraceId,X-B3-SpanId(Zipkin B3)
Graphite adds context to MDC for structured logging:
// Available MDC keys during request execution:
// - graphite.operation: Operation name
// - graphite.requestId: Unique request ID
// - graphite.correlationId: Correlation ID (if provided)Configure in logback:
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{graphite.operation}] - %msg%n</pattern>@Test
void shouldHandleMultipleOperations() {
try (GraphiteMockServer server = GraphiteMockServer.create()) {
// Stub multiple operations
server.stubQuery("GetUser", Map.of("id", "1", "name", "Alice"));
server.stubQuery("GetPosts", List.of(
Map.of("id", "1", "title", "Post 1"),
Map.of("id", "2", "title", "Post 2")
));
// Execute operations
var user = client.execute(getUserQuery).data();
var posts = client.execute(getPostsQuery).data();
// Verify
server.verify("GetUser", 1);
server.verify("GetPosts", 1);
}
}@Test
void shouldHandleGraphQLErrors() {
try (GraphiteMockServer server = GraphiteMockServer.create()) {
server.stubError("GetUser",
GraphQLError.builder()
.message("User not found")
.path(List.of("user"))
.extensions(Map.of("code", "NOT_FOUND"))
.build());
var response = client.execute(getUserQuery);
assertThat(response.hasErrors()).isTrue();
assertThat(response.errors().get(0).message()).isEqualTo("User not found");
}
}
@Test
void shouldHandleHttpErrors() {
try (GraphiteMockServer server = GraphiteMockServer.create()) {
server.stubHttpError("GetUser", 500);
assertThatThrownBy(() -> client.execute(getUserQuery))
.isInstanceOf(GraphiteServerException.class);
}
}@Test
void shouldTimeout() {
try (GraphiteMockServer server = GraphiteMockServer.create()) {
server.stubWithDelay("GetUser", 5000, Map.of("id", "1"));
GraphiteClient client = GraphiteClient.builder()
.endpoint(URI.create(server.getUrl()))
.requestTimeout(Duration.ofMillis(100))
.build();
assertThatThrownBy(() -> client.execute(getUserQuery))
.isInstanceOf(GraphiteTimeoutException.class);
}
}Use connection pooling for high-throughput scenarios:
graphite:
connection-pool:
max-connections: 100
idle-timeout: 60sUse async execution for non-blocking operations:
List<CompletableFuture<UserDTO>> futures = userIds.stream()
.map(id -> client.executeAsync(getUserQuery(id))
.thenApply(GraphiteResponse::getDataOrThrow))
.toList();
List<UserDTO> users = futures.stream()
.map(CompletableFuture::join)
.toList();Enable rate limiting to prevent server overload:
graphite:
rate-limit:
enabled: true
requests-per-second: 50
burst-capacity: 75- Configuration-Reference - Complete configuration options
- Troubleshooting - Common issues and solutions
- FAQ - Frequently asked questions