affaan-m/ecc1 file

Quarkus Patterns

Patrones de arquitectura Quarkus 3.x LTS con Camel para mensajería, diseño de API RESTful, servicios CDI, acceso a datos con Panache y procesamiento asíncrono.

Specification
Skill ID
affaan-m/ecc/quarkus-patterns
Publisher
affaan-m
Repository
ecc
Installs
0
Files
1
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

affaan-m/ecc/quarkus-patternsInstalls these files
  • SKILL.md

What this skill tells the agent

Patrones de Desarrollo Quarkus

Patrones de arquitectura y API de Quarkus 3.x para servicios cloud-native y orientados a eventos con Apache Camel.

Cuándo Activar

  • Construir APIs REST con JAX-RS o RESTEasy Reactive
  • Estructurar capas resource → service → repository
  • Implementar patrones orientados a eventos con Apache Camel y RabbitMQ
  • Configurar Hibernate Panache, caché o streams reactivos
  • Agregar validación, mapeo de excepciones o paginación
  • Configurar perfiles para entornos dev/staging/producción (configuración YAML)
  • Logging personalizado con LogContext y Logback/Logstash encoder
  • Trabajar con CompletableFuture para operaciones asíncronas
  • Implementar procesamiento condicional de flujos
  • Trabajar con compilación nativa GraalVM

Capa de Servicio con Múltiples Dependencias

@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("Orden procesada {}", order.id);
        return receipt;
    }
}

Patrones Clave:

  • @RequiredArgsConstructor para inyección por constructor mediante Lombok
  • @Slf4j para logging con Logback
  • @Transactional en métodos de servicio que escriben a través de Panache o repositorios
  • Validar entrada antes de persistencia o publicación de mensajes
  • Seguimiento de eventos para escenarios de éxito/error
  • Publicación asíncrona de mensajes Camel

Patrón de Contexto de Logging Personalizado (Logback)

@ApplicationScoped
public class ProcessingService {

    public void processDocument(Document doc) {
        LogContext logContext = CustomLog.getCurrentContext();
        try (SafeAutoCloseable ignored = CustomLog.startScope(logContext)) {
            logContext.put("documentId", doc.getId().toString());
            logContext.put("documentType", doc.getType());
            logContext.put("userId", SecurityContext.getUserId());

            log.info("Iniciando procesamiento de documento");

            processInternal(doc);

            log.info("Procesamiento de documento completado");
        } catch (Exception e) {
            log.error("Error en el procesamiento de documento", e);
            throw e;
        }
    }
}

Configuración de Logback (logback.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>

Patrón de Servicio de Eventos

@Slf4j
@ApplicationScoped
@RequiredArgsConstructor
public class EventService {
    private final EventRepository eventRepository;
    private final ObjectMapper objectMapper;

    public void createSuccessEvent(Object payload, String eventType) {
        Objects.requireNonNull(payload, "El payload no puede ser 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("Evento de éxito creado: {}", eventType);
    }

    public void createErrorEvent(Object payload, String eventType, String errorMessage) {
        Objects.requireNonNull(payload, "El payload no puede ser null");
        if (errorMessage == null || errorMessage.isBlank()) {
            throw new IllegalArgumentException("El mensaje de error no puede estar en blanco");
        }
        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("Evento de error creado: {} - {}", eventType, errorMessage);
    }

    private String serializePayload(Object payload) {
        try {
            return objectMapper.writeValueAsString(payload);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Error al serializar el payload del evento", e);
        }
    }
}

Publicación de Mensajes Camel (RabbitMQ)

@Slf4j
@ApplicationScoped
@RequiredArgsConstructor
public class BusinessRulesPublisher {
    private final ProducerTemplate producerTemplate;

    public void publishSync(BusinessRulesPayload payload) {
        producerTemplate.sendBody(
            "direct:business-rules-publisher",
            payload
        );
    }
}

Configuración de Ruta Camel:

@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("Publicando mensaje en RabbitMQ: ${body}")
            .marshal().json(JsonLibrary.Jackson)
            .toF("spring-rabbitmq:%s?hostname=%s&portNumber=%d",
                businessRulesQueue, rabbitHost, rabbitPort);
    }
}

Rutas Camel Direct (En Memoria)

@ApplicationScoped
public class DocumentProcessingRoute extends RouteBuilder {

    @Override
    public void configure() {
        onException(ValidationException.class)
            .handled(true)
            .to("direct:validation-error-handler")
            .log("Error de validación: ${exception.message}");

        from("direct:process-document")
            .routeId("document-processing")
            .log("Procesando documento: ${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();
    }
}

Procesamiento de Archivos Camel

@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")

Use this skill in a project

Open any RiverX project, search the Skills panel for affaan-m/ecc/quarkus-patterns, and install it in one click.

Use this skill