Quarkus Patterns

affaan-m/ECC/skills/quarkus-patterns

作者 affaan-mef648e01899ba3e8dc6371642deaaf64b4477775無授權條款275K 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫4 天前更新

Quarkus 3.x LTS architecture patterns with Camel for messaging, RESTful API design, CDI services, data access with Panache, and async processing. Use for Java Quarkus backend work with event-driven architectures. Use when building or reviewing a Quarkus service, especially with Camel messaging or Panache data access.

AI 產生的概覽

用於建構 Quarkus 3.x Java 服務的參考模式,涵蓋 Apache Camel 訊息、Panache 資料存取與 REST API。

功能
提供 Quarkus 3.x 雲端原生服務的架構與程式碼模式,涵蓋 REST 資源、服務層與儲存庫層、Panache 資料存取、用於 RabbitMQ 與檔案處理的 Apache Camel 路由、事件記錄、結構化日誌、快取、非同步操作、健康檢查、YAML 設定以及 Maven 相依性。它是僅含說明的參考資料,提供程式碼範例,而不是產生檔案的軟體。
適用情境
在建構或審查 Java Quarkus 後端服務時使用,尤其是採用 Camel 訊息、Panache 持久化或事件驅動架構的服務。也適合在 Quarkus 中規劃 REST API、交易、驗證與依環境區分的設定。
執行需求
沒有指令碼或隨附資源,代理只需這份技能文件。套用這些模式需要 Java 17+ 的 Quarkus 3.x 專案,並搭配 Maven、Apache Camel Quarkus 擴充套件、Panache、RabbitMQ,以及 Lombok、Logback 等相關程式庫。

Quarkus Development Patterns

Quarkus 3.x architecture and API patterns for cloud-native, event-driven services with Apache Camel.

When to Activate

  • Building REST APIs with JAX-RS or RESTEasy Reactive
  • Structuring resource → service → repository layers
  • Implementing event-driven patterns with Apache Camel and RabbitMQ
  • Configuring Hibernate Panache, caching, or reactive streams
  • Adding validation, exception mapping, or pagination
  • Setting up profiles for dev/staging/production environments (YAML config)
  • Custom logging with LogContext and Logback/Logstash encoder
  • Working with CompletableFuture for async operations
  • Implementing conditional flow processing
  • Working with GraalVM native compilation

Service Layer with Multiple Dependencies

java
@Slf4j@ApplicationScoped@RequiredArgsConstructorpublic class OrderProcessingService {
    private final OrderValidator orderValidator;    private final EventService eventService;    private final OrderRepository orderRepository;    private final FulfillmentPublisher fulfillmentPublisher;    private final AuditPublisher auditPublisher;
    @Transactional    public OrderReceipt process(CreateOrderCommand command) {        ValidationResult validation = orderValidator.validate(command);        if (!validation.valid()) {            eventService.createErrorEvent(command, "ORDER_REJECTED", validation.message());            throw new WebApplicationException(validation.message(), Response.Status.BAD_REQUEST);        }
        Order order = Order.from(command);        orderRepository.persist(order);
        OrderReceipt receipt = OrderReceipt.from(order);        fulfillmentPublisher.publishAsync(receipt);        auditPublisher.publish("ORDER_ACCEPTED", receipt);        eventService.createSuccessEvent(receipt, "ORDER_ACCEPTED");
        log.info("Processed order {}", order.id);        return receipt;    }}

Key Patterns:

  • @RequiredArgsConstructor for constructor injection via Lombok
  • @Slf4j for Logback logging
  • @Transactional on service methods that write through Panache or repositories
  • Validate input before persistence or message publication
  • Event tracking for success/error scenarios
  • Async Camel message publishing

Custom Logging Context Pattern (Logback)

java
@ApplicationScopedpublic class ProcessingService {
    public void processDocument(Document doc) {        LogContext logContext = CustomLog.getCurrentContext();        try (SafeAutoCloseable ignored = CustomLog.startScope(logContext)) {            // Add context to all log statements            logContext.put("documentId", doc.getId().toString());            logContext.put("documentType", doc.getType());            logContext.put("userId", SecurityContext.getUserId());
            log.info("Starting document processing");
            // All logs within this scope inherit the context            processInternal(doc);
            log.info("Document processing completed");        } catch (Exception e) {            log.error("Document processing failed", e);            throw e;        }    }}

Logback Configuration (logback.xml):

xml
<configuration>    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">        <encoder class="net.logstash.logback.encoder.LogstashEncoder">            <includeContext>true</includeContext>            <includeMdc>true</includeMdc>        </encoder>    </appender>
    <logger name="com.example" level="INFO"/>    <root level="WARN">        <appender-ref ref="CONSOLE"/>    </root></configuration>

Event Service Pattern

java
@Slf4j@ApplicationScoped@RequiredArgsConstructorpublic class EventService {    private final EventRepository eventRepository;    private final ObjectMapper objectMapper;
    public void createSuccessEvent(Object payload, String eventType) {        Objects.requireNonNull(payload, "Payload cannot be null");        Event event = new Event();        event.setType(eventType);        event.setStatus(EventStatus.SUCCESS);        event.setPayload(serializePayload(payload));        event.setTimestamp(Instant.now());
        eventRepository.persist(event);        log.info("Success event created: {}", eventType);    }
    public void createErrorEvent(Object payload, String eventType, String errorMessage) {        Objects.requireNonNull(payload, "Payload cannot be null");        if (errorMessage == null || errorMessage.isBlank()) {            throw new IllegalArgumentException("Error message cannot be blank");        }        Event event = new Event();        event.setType(eventType);        event.setStatus(EventStatus.ERROR);        event.setErrorMessage(errorMessage);        event.setPayload(serializePayload(payload));        event.setTimestamp(Instant.now());
        eventRepository.persist(event);        log.error("Error event created: {} - {}", eventType, errorMessage);    }
    private String serializePayload(Object payload) {        try {            return objectMapper.writeValueAsString(payload);        } catch (JsonProcessingException e) {            throw new IllegalStateException("Failed to serialize event payload", e);        }    }}

Camel Message Publishing (RabbitMQ)

java
@Slf4j@ApplicationScoped@RequiredArgsConstructorpublic class BusinessRulesPublisher {    private final ProducerTemplate producerTemplate;
    public void publishSync(BusinessRulesPayload payload) {        producerTemplate.sendBody(            "direct:business-rules-publisher",            payload        );    }}

Camel Route Configuration:

java
@ApplicationScopedpublic class BusinessRulesRoute extends RouteBuilder {
    @ConfigProperty(name = "camel.rabbitmq.queue.business-rules")    String businessRulesQueue;
    @ConfigProperty(name = "rabbitmq.host")    String rabbitHost;
    @ConfigProperty(name = "rabbitmq.port")    Integer rabbitPort;
    @Override    public void configure() {        from("direct:business-rules-publisher")            .routeId("business-rules-publisher")            .log("Publishing message to RabbitMQ: ${body}")            .marshal().json(JsonLibrary.Jackson)            .toF("spring-rabbitmq:%s?hostname=%s&portNumber=%d",                businessRulesQueue, rabbitHost, rabbitPort);    }}

Camel Direct Routes (In-Memory)

java
@ApplicationScopedpublic class DocumentProcessingRoute extends RouteBuilder {
    @Override    public void configure() {        // Error handling        onException(ValidationException.class)            .handled(true)            .to("direct:validation-error-handler")            .log("Validation error: ${exception.message}");
        // Main processing route        from("direct:process-document")            .routeId("document-processing")            .log("Processing document: ${header.documentId}")            .bean(DocumentValidator.class, "validate")            .bean(DocumentTransformer.class, "transform")            .choice()                .when(header("documentType").isEqualTo("INVOICE"))                    .to("direct:process-invoice")                .when(header("documentType").isEqualTo("CREDIT_NOTE"))                    .to("direct:process-credit-note")                .otherwise()                    .to("direct:process-generic")            .end();
        from("direct:validation-error-handler")            .bean(EventService.class, "createErrorEvent")            .log("Validation error handled");    }}

Camel File Processing

java
@ApplicationScopedpublic class FileMonitoringRoute extends RouteBuilder {
    @ConfigProperty(name = "file.input.directory")    String inputDirectory;
    @ConfigProperty(name = "file.processed.directory")    String processedDirectory;
    @ConfigProperty(name = "file.error.directory")    String errorDirectory;
    @Override    public void configure() {        from("file:" + inputDirectory + "?move=" + processedDirectory +             "&moveFailed=" + errorDirectory + "&delay=5000")            .routeId("file-monitor")            .log("Processing file: ${header.CamelFileName}")            .to("direct:process-file");
        from("direct:process-file")            .bean(OrderProcessingService.class, "processFile")            .log("File processing completed");    }}

Camel Bean Invocation

java
@ApplicationScopedpublic class InvoiceRoute extends RouteBuilder {
    @Override    public void configure() {        from("direct:invoice-validation")            .bean(InvoiceFlowValidator.class, "validateFlowWithConfig")            .log("Validation result: ${body}");
        from("direct:persist-and-publish")            .bean(DocumentJobService.class, "createDocumentAndJobEntities")            .bean(BusinessRulesPublisher.class, "publishAsync")            .bean(EventService.class, "createSuccessEvent(${body}, 'PUBLISHED')");    }}

REST API Structure

java
@Path("/api/documents")@Produces(MediaType.APPLICATION_JSON)@Consumes(MediaType.APPLICATION_JSON)@RequiredArgsConstructorpublic class DocumentResource {  private final DocumentService documentService;
  @GET  public Response list(      @QueryParam("page") @DefaultValue("0") int page,      @QueryParam("size") @DefaultValue("20") int size) {    List<Document> documents = documentService.list(page, size);    return Response.ok(documents).build();  }
  @POST  public Response create(@Valid CreateDocumentRequest request, @Context UriInfo uriInfo) {    Document document = documentService.create(request);    URI location = uriInfo.getAbsolutePathBuilder()        .path(String.valueOf(document.id))        .build();    return Response.created(location).entity(DocumentResponse.from(document)).build();  }
  @GET  @Path("/{id}")  public Response getById(@PathParam("id") Long id) {    return documentService.findById(id)        .map(DocumentResponse::from)        .map(Response::ok)        .orElse(Response.status(Response.Status.NOT_FOUND))        .build();  }}

Repository Pattern (Panache Repository)

java
@ApplicationScopedpublic class DocumentRepository implements PanacheRepository<Document> {
  public List<Document> findByStatus(DocumentStatus status, int page, int size) {    return find("status = ?1 order by createdAt desc", status)        .page(page, size)        .list();  }
  public Optional<Document> findByReferenceNumber(String referenceNumber) {    return find("referenceNumber", referenceNumber).firstResultOptional();  }
  public long countByStatusAndDate(DocumentStatus status, LocalDate date) {    return count("status = ?1 and createdAt >= ?2", status, date.atStartOfDay());  }}

Service Layer with Transactions

java
@ApplicationScoped@RequiredArgsConstructorpublic class DocumentService {  private final DocumentRepository repo;  private final EventService eventService;
  @Transactional  public Document create(CreateDocumentRequest request) {    Document document = new Document();    document.setReferenceNumber(request.referenceNumber());    document.setDescription(request.description());    document.setStatus(DocumentStatus.PENDING);    document.setCreatedAt(Instant.now());
    repo.persist(document);
    eventService.createSuccessEvent(document, "DOCUMENT_CREATED");
    return document;  }
  public Optional<Document> findById(Long id) {    return repo.findByIdOptional(id);  }
  public List<Document> list(int page, int size) {    return repo.findAll()        .page(page, size)        .list();  }}

DTOs and Validation

java
public record CreateDocumentRequest(    @NotBlank @Size(max = 200) String referenceNumber,    @NotBlank @Size(max = 2000) String description,    @NotNull @FutureOrPresent Instant validUntil,    @NotEmpty List<@NotBlank String> categories) {}
public record DocumentResponse(Long id, String referenceNumber, DocumentStatus status) {  public static DocumentResponse from(Document document) {    return new DocumentResponse(document.getId(), document.getReferenceNumber(),        document.getStatus());  }}

Exception Mapping

java
@Providerpublic class ValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {  @Override  public Response toResponse(ConstraintViolationException exception) {    String message = exception.getConstraintViolations().stream()        .map(cv -> cv.getPropertyPath() + ": " + cv.getMessage())        .collect(Collectors.joining(", "));
    return Response.status(Response.Status.BAD_REQUEST)        .entity(Map.of("error", "validation_error", "message", message))        .build();  }}
@Provider@Slf4jpublic class GenericExceptionMapper implements ExceptionMapper<Exception> {
  @Override  public Response toResponse(Exception exception) {    log.error("Unhandled exception", exception);    return Response.status(Response.Status.INTERNAL_SERVER_ERROR)        .entity(Map.of("error", "internal_error", "message", "An unexpected error occurred"))        .build();  }}

CompletableFuture Async Operations

java
@Slf4j@ApplicationScoped@RequiredArgsConstructorpublic class FileStorageService {    private final S3Client s3Client;    private final ExecutorService executorService;
    @ConfigProperty(name = "storage.bucket-name")    String bucketName;
    public CompletableFuture<StoredDocumentInfo> uploadOriginalFile(            InputStream inputStream,            long size,            LogContext logContext,            InvoiceFormat format) {
        return CompletableFuture.supplyAsync(() -> {            try (SafeAutoCloseable ignored = CustomLog.startScope(logContext)) {                String path = generateStoragePath(format);
                PutObjectRequest request = PutObjectRequest.builder()                    .bucket(bucketName)                    .key(path)                    .contentLength(size)                    .build();
                s3Client.putObject(request, RequestBody.fromInputStream(inputStream, size));
                log.info("File uploaded to S3: {}", path);
                return new StoredDocumentInfo(path, size, Instant.now());            } catch (Exception e) {                log.error("Failed to upload file to S3", e);                throw new StorageException("Upload failed", e);            }        }, executorService);    }}

Caching

java
@ApplicationScoped@RequiredArgsConstructorpublic class DocumentCacheService {  private final DocumentRepository repo;
  @CacheResult(cacheName = "document-cache")  public Optional<Document> getById(@CacheKey Long id) {    return repo.findByIdOptional(id);  }
  @CacheInvalidate(cacheName = "document-cache")  public void evict(@CacheKey Long id) {}
  @CacheInvalidateAll(cacheName = "document-cache")  public void evictAll() {}}

Configuration as YAML

yaml
# application.yml"%dev":  quarkus:    datasource:      jdbc:        url: jdbc:postgresql://localhost:5432/dev_db      username: dev_user      password: ${DB_PASSWORD}    hibernate-orm:      database:        generation: drop-and-create
  rabbitmq:    host: localhost    port: 5672    username: ${RABBITMQ_USER}    password: ${RABBITMQ_PASSWORD}
"%test":  quarkus:    datasource:      jdbc:        url: jdbc:h2:mem:test    hibernate-orm:      database:        generation: drop-and-create
"%prod":  quarkus:    datasource:      jdbc:        url: ${DATABASE_URL}      username: ${DB_USER}      password: ${DB_PASSWORD}    hibernate-orm:      database:        generation: validate
  rabbitmq:    host: ${RABBITMQ_HOST}    port: ${RABBITMQ_PORT}    username: ${RABBITMQ_USER}    password: ${RABBITMQ_PASSWORD}
# Camel configurationcamel:  rabbitmq:    queue:      business-rules: business-rules-queue      invoice-processing: invoice-processing-queue

Health Checks

java
@Readiness@ApplicationScoped@RequiredArgsConstructorpublic class DatabaseHealthCheck implements HealthCheck {  private final AgroalDataSource dataSource;
  @Override  public HealthCheckResponse call() {    try (Connection conn = dataSource.getConnection()) {      boolean valid = conn.isValid(2);      return HealthCheckResponse.named("Database connection")          .status(valid)          .build();    } catch (SQLException e) {      return HealthCheckResponse.down("Database connection");    }  }}
@Liveness@ApplicationScopedpublic class CamelHealthCheck implements HealthCheck {  @Inject  CamelContext camelContext;
  @Override  public HealthCheckResponse call() {    boolean isStarted = camelContext.getStatus().isStarted();    return HealthCheckResponse.named("Camel Context")        .status(isStarted)        .build();  }}

Dependencies (Maven)

xml
<properties>    <quarkus.platform.version>3.27.0</quarkus.platform.version>    <lombok.version>1.18.42</lombok.version>    <assertj-core.version>3.24.2</assertj-core.version>    <jacoco-maven-plugin.version>0.8.13</jacoco-maven-plugin.version>    <maven.compiler.release>17</maven.compiler.release></properties>
<dependencyManagement>    <dependencies>        <dependency>            <groupId>io.quarkus.platform</groupId>            <artifactId>quarkus-bom</artifactId>            <version>${quarkus.platform.version}</version>            <type>pom</type>            <scope>import</scope>        </dependency>        <dependency>            <groupId>io.quarkus.platform</groupId>            <artifactId>quarkus-camel-bom</artifactId>            <version>${quarkus.platform.version}</version>            <type>pom</type>            <scope>import</scope>        </dependency>    </dependencies></dependencyManagement>
<dependencies>    <!-- Quarkus Core -->    <dependency>        <groupId>io.quarkus</groupId>        <artifactId>quarkus-arc</artifactId>    </dependency>    <dependency>        <groupId>io.quarkus</groupId>        <artifactId>quarkus-config-yaml</artifactId>    </dependency>
    <!-- Camel Extensions -->    <dependency>        <groupId>org.apache.camel.quarkus</groupId>        <artifactId>camel-quarkus-spring-rabbitmq</artifactId>    </dependency>    <dependency>        <groupId>org.apache.camel.quarkus</groupId>        <artifactId>camel-quarkus-direct</artifactId>    </dependency>    <dependency>        <groupId>org.apache.camel.quarkus</groupId>        <artifactId>camel-quarkus-bean</artifactId>    </dependency>
    <!-- Lombok -->    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>        <version>${lombok.version}</version>        <scope>provided</scope>    </dependency>
    <!-- Logging -->    <dependency>        <groupId>io.quarkiverse.logging.logback</groupId>        <artifactId>quarkus-logging-logback</artifactId>    </dependency>    <dependency>        <groupId>net.logstash.logback</groupId>        <artifactId>logstash-logback-encoder</artifactId>    </dependency></dependencies>

Best Practices

Architecture

  • Use @RequiredArgsConstructor with Lombok for constructor injection
  • Keep service layer thin; delegate complex logic to specialized classes
  • Use Camel routes for message routing and integration patterns
  • Prefer Panache Repository pattern for data access

Event-Driven

  • Always track operations with EventService (success/error events)
  • Use Camel direct: endpoints for in-memory routing
  • Use spring-rabbitmq component for RabbitMQ integration
  • Implement async publishing with ProducerTemplate.asyncSendBody()

Logging

  • Use Logback with Logstash encoder for structured logging
  • Propagate LogContext through service calls with SafeAutoCloseable
  • Add contextual information to LogContext for request tracing
  • Use @Slf4j instead of manual logger instantiation

Async Operations

  • Use CompletableFuture for non-blocking I/O operations
  • Call .join() when you need to wait for completion
  • Handle exceptions from CompletableFuture properly
  • Pass LogContext to async operations for tracing

Configuration

  • Use YAML configuration (quarkus-config-yaml)
  • Profile-aware configuration for dev/test/prod environments
  • Externalize sensitive configuration to environment variables
  • Use @ConfigProperty for type-safe config injection

Validation

  • Validate at resource layer with @Valid
  • Use Bean Validation annotations on DTOs
  • Map exceptions to proper HTTP responses with @Provider

Transactions

  • Use @Transactional on service methods that modify data
  • Keep transactions short and focused
  • Avoid calling async operations within transactions

Testing

  • Use camel-quarkus-junit5 for route testing
  • Use AssertJ for assertions
  • Mock all external dependencies
  • Test conditional flow logic thoroughly

Quarkus-Specific

  • Stay on latest LTS version (3.x)
  • Use Quarkus dev mode for hot reload
  • Add health checks for production readiness
  • Test native compilation compatibility periodically

來源與署名

來源:affaan-m/ECC位於skills/quarkus-patterns提交ef648e0

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架