or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/io.quarkus/quarkus-resteasy-reactive@3.15.x

docs

examples

advanced-patterns.mdcommon-scenarios.mdedge-cases.md
index.md
tile.json

tessl/maven-io-quarkus--quarkus-resteasy-reactive

tessl install tessl/maven-io-quarkus--quarkus-resteasy-reactive@3.15.0

A Jakarta REST implementation utilizing build time processing and Vert.x for high-performance REST endpoints with reactive capabilities in cloud-native environments.

advanced-patterns.mddocs/examples/

Advanced Patterns

Complex implementation patterns for sophisticated use cases.

Circuit Breaker Pattern

Implement resilience with circuit breaker:

@Path("/api/external")
public class ExternalServiceResource {

    @Inject
    @RestClient
    ExternalApiClient externalClient;

    private final CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("external-api");

    @GET
    @Path("/data")
    public Uni<RestResponse<Data>> getData() {
        return Uni.createFrom().item(() ->
            circuitBreaker.executeSupplier(() ->
                externalClient.getData().await().indefinitely()
            )
        )
        .onItem().transform(data -> RestResponse.ok(data))
        .onFailure(CallNotPermittedException.class)
        .recoverWithItem(RestResponse.status(503, "Service temporarily unavailable"));
    }
}

Event Sourcing

Implement event-driven architecture:

@Path("/api/orders")
public class OrderResource {

    @Inject
    OrderEventStore eventStore;

    @Inject
    OrderProjection projection;

    @POST
    public Uni<RestResponse<Order>> createOrder(CreateOrderCommand command) {
        OrderCreatedEvent event = new OrderCreatedEvent(
            UUID.randomUUID().toString(),
            command.customerId,
            command.items,
            Instant.now()
        );

        return eventStore.append(event)
            .chain(() -> projection.apply(event))
            .onItem().transform(order -> RestResponse.status(201, order));
    }

    @GET
    @Path("/{id}")
    public Uni<RestResponse<Order>> getOrder(@RestPath String id) {
        return eventStore.getEvents(id)
            .collect().asList()
            .map(events -> projection.rebuild(events))
            .onItem().transform(order ->
                order != null
                    ? RestResponse.ok(order)
                    : RestResponse.notFound()
            );
    }
}

CQRS Pattern

Separate read and write models:

// Command side
@Path("/api/commands/products")
public class ProductCommandResource {

    @Inject
    ProductCommandService commandService;

    @POST
    public Uni<CommandResult> createProduct(CreateProductCommand command) {
        return commandService.handle(command);
    }

    @PUT
    @Path("/{id}")
    public Uni<CommandResult> updateProduct(
        @RestPath String id,
        UpdateProductCommand command
    ) {
        return commandService.handle(command.withId(id));
    }
}

// Query side
@Path("/api/queries/products")
public class ProductQueryResource {

    @Inject
    ProductQueryService queryService;

    @GET
    public Multi<ProductView> list(@RestQuery Optional<String> category) {
        return queryService.findAll(category);
    }

    @GET
    @Path("/{id}")
    public Uni<ProductView> get(@RestPath String id) {
        return queryService.findById(id);
    }
}

Saga Pattern

Distributed transaction coordination:

@ApplicationScoped
public class OrderSaga {

    @Inject
    OrderService orderService;

    @Inject
    PaymentService paymentService;

    @Inject
    InventoryService inventoryService;

    @Inject
    ShippingService shippingService;

    public Uni<OrderResult> executeOrderSaga(CreateOrderRequest request) {
        return createOrder(request)
            .chain(order -> reserveInventory(order)
                .chain(reserved -> processPayment(order)
                    .chain(paid -> scheduleShipping(order)
                        .onItem().transform(shipped -> 
                            new OrderResult(order, OrderStatus.COMPLETED)
                        )
                        .onFailure().call(ex -> 
                            compensatePayment(order)
                                .chain(() -> compensateInventory(order))
                        )
                    )
                    .onFailure().call(ex -> compensateInventory(order))
                )
                .onFailure().call(ex -> compensateOrder(order))
            );
    }

    private Uni<Order> createOrder(CreateOrderRequest request) {
        return orderService.create(request);
    }

    private Uni<Void> reserveInventory(Order order) {
        return inventoryService.reserve(order.getItems());
    }

    private Uni<Void> processPayment(Order order) {
        return paymentService.charge(order.getTotal());
    }

    private Uni<Void> scheduleShipping(Order order) {
        return shippingService.schedule(order);
    }

    private Uni<Void> compensateOrder(Order order) {
        return orderService.cancel(order.getId());
    }

    private Uni<Void> compensateInventory(Order order) {
        return inventoryService.release(order.getItems());
    }

    private Uni<Void> compensatePayment(Order order) {
        return paymentService.refund(order.getTotal());
    }
}

GraphQL-like Field Selection

Dynamic field selection:

@Path("/api/users")
public class UserResource {

    @Inject
    UserService userService;

    @GET
    @Path("/{id}")
    public Uni<JsonObject> get(
        @RestPath Long id,
        @RestQuery Optional<String> fields
    ) {
        return userService.findById(id)
            .onItem().transform(user -> {
                Set<String> selectedFields = fields
                    .map(f -> Set.of(f.split(",")))
                    .orElse(Set.of("id", "name", "email"));

                JsonObjectBuilder builder = Json.createObjectBuilder();
                
                if (selectedFields.contains("id")) {
                    builder.add("id", user.id);
                }
                if (selectedFields.contains("name")) {
                    builder.add("name", user.name);
                }
                if (selectedFields.contains("email")) {
                    builder.add("email", user.email);
                }
                if (selectedFields.contains("profile")) {
                    builder.add("profile", toJson(user.profile));
                }
                
                return builder.build();
            });
    }
}

Streaming Aggregation

Real-time data aggregation:

@Path("/api/analytics")
public class AnalyticsResource {

    @Inject
    EventService eventService;

    @GET
    @Path("/stream")
    @Produces(MediaType.SERVER_SENT_EVENTS)
    @RestStreamElementType(MediaType.APPLICATION_JSON)
    public Multi<AggregatedMetrics> streamMetrics() {
        return eventService.streamEvents()
            .group().intoLists().of(100, Duration.ofSeconds(5))
            .onItem().transform(events -> {
                AggregatedMetrics metrics = new AggregatedMetrics();
                metrics.count = events.size();
                metrics.avgValue = events.stream()
                    .mapToDouble(Event::getValue)
                    .average()
                    .orElse(0.0);
                metrics.maxValue = events.stream()
                    .mapToDouble(Event::getValue)
                    .max()
                    .orElse(0.0);
                return metrics;
            });
    }
}

Conditional Request Processing

ETags and conditional requests:

@Path("/api/documents")
public class DocumentResource {

    @Inject
    DocumentService documentService;

    @GET
    @Path("/{id}")
    public Response get(
        @RestPath Long id,
        @Context Request request
    ) {
        Document doc = documentService.findById(id);

        if (doc == null) {
            return Response.status(404).build();
        }

        // Generate ETag from version
        EntityTag etag = new EntityTag(doc.getVersion());
        
        // Check If-None-Match
        Response.ResponseBuilder builder = request.evaluatePreconditions(etag);
        if (builder != null) {
            return builder.build(); // 304 Not Modified
        }

        return Response.ok(doc)
            .tag(etag)
            .lastModified(doc.getUpdatedAt())
            .build();
    }

    @PUT
    @Path("/{id}")
    public Response update(
        @RestPath Long id,
        @RestHeader("If-Match") String ifMatch,
        Document document
    ) {
        Document existing = documentService.findById(id);

        if (existing == null) {
            return Response.status(404).build();
        }

        // Check If-Match for optimistic locking
        if (!existing.getVersion().equals(ifMatch)) {
            return Response.status(412) // Precondition Failed
                .entity("Document was modified by another user")
                .build();
        }

        Document updated = documentService.update(id, document);
        
        return Response.ok(updated)
            .tag(new EntityTag(updated.getVersion()))
            .build();
    }
}

Multi-Tenancy

Tenant isolation:

@ApplicationScoped
public class TenantFilter {

    @ServerRequestFilter(priority = Priorities.AUTHENTICATION + 1)
    public void extractTenant(
        ContainerRequestContext ctx,
        @RestHeader("X-Tenant-ID") Optional<String> tenantId
    ) {
        String tenant = tenantId.orElse("default");
        TenantContext.set(tenant);
    }

    @ServerResponseFilter
    public void clearTenant() {
        TenantContext.clear();
    }
}

@Path("/api/data")
public class DataResource {

    @Inject
    DataService dataService;

    @GET
    public Uni<List<Data>> list() {
        String tenant = TenantContext.get();
        return dataService.findByTenant(tenant);
    }
}

Versioned API with Content Negotiation

API versioning via Accept header:

@Path("/api/products")
public class ProductResource {

    @Inject
    ProductService productService;

    @GET
    @Path("/{id}")
    public Response get(
        @RestPath Long id,
        @RestHeader("Accept") String accept
    ) {
        Product product = productService.findById(id);

        if (product == null) {
            return Response.status(404).build();
        }

        if (accept.contains("application/vnd.api.v2+json")) {
            return Response.ok(toV2DTO(product))
                .type("application/vnd.api.v2+json")
                .build();
        }

        return Response.ok(toV1DTO(product))
            .type("application/vnd.api.v1+json")
            .build();
    }
}

Reactive Caching

Implement reactive caching layer:

@ApplicationScoped
public class CachedProductService {

    @Inject
    ProductService productService;

    private final Cache<Long, Product> cache = Caffeine.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(Duration.ofMinutes(10))
        .build();

    public Uni<Product> findById(Long id) {
        Product cached = cache.getIfPresent(id);
        
        if (cached != null) {
            return Uni.createFrom().item(cached);
        }

        return productService.findById(id)
            .onItem().invoke(product -> cache.put(id, product));
    }

    public Uni<Product> update(Long id, Product product) {
        return productService.update(id, product)
            .onItem().invoke(updated -> {
                cache.invalidate(id);
                cache.put(id, updated);
            });
    }
}

Next Steps

  • Review Edge Cases
  • Check Common Scenarios
  • Explore Reference Documentation