mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-14 02:10:07 +08:00
723 lines
21 KiB
Markdown
723 lines
21 KiB
Markdown
---
|
|
name: quarkus-patterns
|
|
description: 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.
|
|
origin: ECC
|
|
---
|
|
|
|
# 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
|
|
@RequiredArgsConstructor
|
|
public 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
|
|
@ApplicationScoped
|
|
public 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
|
|
@RequiredArgsConstructor
|
|
public 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
|
|
@RequiredArgsConstructor
|
|
public class BusinessRulesPublisher {
|
|
private final ProducerTemplate producerTemplate;
|
|
|
|
public void publishSync(BusinessRulesPayload payload) {
|
|
producerTemplate.sendBody(
|
|
"direct:business-rules-publisher",
|
|
payload
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Camel Route Configuration:**
|
|
|
|
```java
|
|
@ApplicationScoped
|
|
public 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
|
|
@ApplicationScoped
|
|
public 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
|
|
@ApplicationScoped
|
|
public 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
|
|
@ApplicationScoped
|
|
public 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)
|
|
@RequiredArgsConstructor
|
|
public 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
|
|
@ApplicationScoped
|
|
public 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
|
|
@RequiredArgsConstructor
|
|
public 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
|
|
@Provider
|
|
public 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
|
|
@Slf4j
|
|
public 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
|
|
@RequiredArgsConstructor
|
|
public 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
|
|
@RequiredArgsConstructor
|
|
public 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 configuration
|
|
camel:
|
|
rabbitmq:
|
|
queue:
|
|
business-rules: business-rules-queue
|
|
invoice-processing: invoice-processing-queue
|
|
```
|
|
|
|
## Health Checks
|
|
|
|
```java
|
|
@Readiness
|
|
@ApplicationScoped
|
|
@RequiredArgsConstructor
|
|
public 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
|
|
@ApplicationScoped
|
|
public 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
|