7
Antipatrones detectados
3
Críticos (runtime risk)
12
Mejoras aplicadas
5
Reglas Quarkus-específicas
◆ Framework detectado: QUARKUS —
pom.xml contiene io.quarkus:quarkus-maven-plugin. Aplicando convenciones [QUARKUS]: JAX-RS resources, CDI scopes, Panache active-record, RESTEasy Reactive, JBoss Logging.
⚠
Antipatrones detectados
Crítico
@Singleton en lugar de @ApplicationScoped [QUARKUS]WorkflowController usa @Singleton, que no es proxyable — rompe la interceptación CDI (transacciones, seguridad, observabilidad). Usar @ApplicationScoped siempre que se necesite interception.
Crítico
Llamada
.get() sin check sobre OptionalworkflowService.find(s).get() lanza NoSuchElementException en silencio si no hay resultado. Reemplazar por .orElseThrow() con excepción de dominio.
Crítico
Catch genérico
Exception con e.printStackTrace() silenciosoOculta errores reales (conexión BD, timeout), devuelve lista vacía sin contexto. Usar logging estructurado y relanzar como excepción de dominio o dejar propagar.
Aviso
Inyección con campo
private @Inject — problemático en Quarkus [QUARKUS]Los campos
private con @Inject pueden causar problemas con proxies CDI. Usar campo package-private o inyección por constructor.
Aviso
Raw types (
List sin parámetro genérico)public List getAll() y public List findAll() usan raw types. Declarar siempre el tipo genérico: List<WorkflowDto>.
Aviso
Lista larga de parámetros en
create()create(String n, String d, String oid, int p) — 4 parámetros primitivos. Usar un DTO/record de comando: CreateWorkflowRequest(name, description, ownerId, priority).
Mejora
Nombre de clase
WorkflowController → WorkflowResource [QUARKUS]En Quarkus los endpoints JAX-RS se nombran con sufijo
*Resource, no *Controller (ese es el patrón Spring).✓
Código refactorizado
resource/WorkflowResource.java
Renombrado + refactorizado
╳ Antes — WorkflowController.java
// WorkflowController.java
@Singleton // ← BUG: no proxyable
public class WorkflowController {
@Inject
private WorkflowService workflowService;
// ← private: proxy issues
public List getAll(int page) {
// raw type ^
return workflowService.findAll(page);
}
public Object getBySlug(String s) {
WorkflowEntity w =
workflowService.find(s).get(); // NPE!
return w;
}
public void create(
String n, String d,
String oid, int p) {
workflowService.create(n, d, oid, p);
}
}
✓ Después — WorkflowResource.java
// [QUARKUS] JAX-RS resource
@ApplicationScoped
@Path("/workflows")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class WorkflowResource {
@Inject // package-private: OK Quarkus
WorkflowService workflowService;
@GET
public List<WorkflowDto> getAll(
@QueryParam("page") @DefaultValue("0")
int page) {
return workflowService.findAll(page);
}
@GET @Path("/{slug}")
public WorkflowDto getBySlug(
@PathParam("slug") String slug) {
return workflowService.findBySlug(slug);
// orElseThrow en service ↑
}
@POST @ResponseStatus(201)
public WorkflowDto create(
@Valid CreateWorkflowRequest req) {
return workflowService.create(req);
}
}
service/WorkflowService.java
Logging + Optional correcto + tipos
╳ Antes
// sin scope CDI explícito
public class WorkflowService {
public List findAll(int page) {
try {
return WorkflowEntity
.findAll()
.page(Page.of(page, 50))
.list();
} catch (Exception e) {
e.printStackTrace(); // ← mal
return new ArrayList();
}
}
public Optional find(String slug){
return WorkflowEntity
.find("slug", slug)
.firstResultOptional();
}
}
✓ Después
@ApplicationScoped
public class WorkflowService {
private static final Logger log =
Logger.getLogger(WorkflowService.class);
public List<WorkflowDto> findAll(int page) {
return WorkflowEntity
.findAll()
.page(Page.of(page, PAGE_SIZE))
.list()
.stream()
.map(WorkflowDto::from)
.toList(); // Java 16+
}
public WorkflowDto findBySlug(String slug){
return WorkflowEntity
.<WorkflowEntity>find("slug", slug)
.firstResultOptional()
.map(WorkflowDto::from)
.orElseThrow(() -> {
log.warnf("workflow_not_found slug=%s", slug);
return new WorkflowNotFoundException(slug);
});
}
}
dto/ y domain/ — Nuevos tipos Java 17+
Records + Excepción de dominio
╳ No existía (parámetros sueltos)
// create() recibía 4 parámetros sueltos:
String n, String d,
String oid, int p
// Sin DTO de respuesta tipado
public Object getBySlug(...)
// Sin excepción de dominio
// Lanzaba NoSuchElementException genérica
✓ Tipos nuevos (Java 17 records)
// dto/CreateWorkflowRequest.java
public record CreateWorkflowRequest(
@NotBlank String name,
String description,
@NotBlank String ownerId,
@Min(1) @Max(5) int priority
) {}
// dto/WorkflowDto.java
public record WorkflowDto(
Long id, String slug,
String name, WorkflowStatus status,
LocalDateTime createdAt
) {
static WorkflowDto from(WorkflowEntity e) {
return new WorkflowDto(
e.id, e.slug, e.name, e.status, e.createdAt);
}
}
// domain/WorkflowNotFoundException.java
public class WorkflowNotFoundException
extends RuntimeException {
public WorkflowNotFoundException(String slug){
super("Workflow not found: " + slug);
}
}
◆
Reglas aplicadas
◈
Estructura de proyecto corregida [QUARKUS]
src/main/java/io/nexaflow/workflow/
config/
WorkflowConfig.java @ConfigMapping(prefix="workflow")
resource/ ← JAX-RS (no "controller")
WorkflowResource.java
GlobalExceptionMapper.java @ServerExceptionMapper
service/
WorkflowService.java @ApplicationScoped
domain/
WorkflowEntity.java extends PanacheEntity
WorkflowNotFoundException.java
WorkflowStatus.java enum
dto/
WorkflowDto.java record
CreateWorkflowRequest.java record + Bean Validation
util/
src/main/resources/
application.properties Quarkus convention (no .yml)
import.sql
src/test/java/io/nexaflow/workflow/
resource/
WorkflowResourceTest.java @QuarkusTest + @InjectMock
service/
WorkflowServiceTest.java @ExtendWith(MockitoExtension) — no CDI
▶
Tests — patrón Quarkus correcto
test/ — WorkflowResourceTest.java y WorkflowServiceTest.java
Integración CDI — @QuarkusTest
// WorkflowResourceTest.java
@QuarkusTest
class WorkflowResourceTest {
@InjectMock
WorkflowService workflowService;
@Test
void should_return_404_when_not_found() {
when(workflowService
.findBySlug("unknown"))
.thenThrow(new
WorkflowNotFoundException("unknown"));
given().when()
.get("/workflows/unknown")
.then().statusCode(404);
}
}
Unitario puro — sin @QuarkusTest
// WorkflowServiceTest.java
@ExtendWith(MockitoExtension.class)
class WorkflowServiceTest {
@Mock
WorkflowRepository repo;
@InjectMocks
WorkflowService service;
@Test
void findBySlug_throws_when_missing() {
when(repo.findBySlug("x"))
.thenReturn(Optional.empty());
assertThatThrownBy(() ->
service.findBySlug("x")
).isInstanceOf(
WorkflowNotFoundException.class);
}
}