Este artigo faz parte da série “Quarkus for Spring Developers”.

O mundo Java mudou. Se você vem do ecossistema Spring, está acostumado com um framework que faz quase tudo em tempo de execução. Mas o Quarkus chegou para virar esse jogo, trazendo o conceito de Build-time Efficiency. Neste artigo, vamos mergulhar no coração do Quarkus: o ArC e o seu sistema de Injeção de Dependência, construindo um serviço de pedidos do zero.

Mãos à obra: Criando o Projeto

Para começar, vamos gerar o nosso projeto via terminal. Note que já incluímos as extensões necessárias para Quarkus REST e o suporte ao CDI Lite 4.1 que já vem no Core.

Usando Quarkus CLI:

quarkus create app org.acme:injecao-dependencia \
    --extension="quarkus-rest" \
    --no-code

Usando Maven

mvn io.quarkus.platform:quarkus-maven-plugin:3.31.1:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=injecao-dependencia \
    -Dextensions="quarkus-rest" \
    -DnoCode

Spring DI vs. ArC (Quarkus CDI)

A grande diferença entre os dois não está na sintaxe, mas no momento em que a “mágica” acontece. Enquanto o Spring utiliza reflexão (Reflection) em runtime para escanear pacotes, o Quarkus utiliza o ArC.

O ArC analisa sua aplicação durante o build, valida as dependências e gera o bytecode necessário. Isso significa que, se você esquecer de anotar um componente ou tiver uma dependência ambígua, o erro aparecerá na sua tela antes mesmo da aplicação tentar subir.

Dica para Executáveis Nativos: Para que o GraalVM otimize ao máximo sua aplicação, evite usar private. Use o modificador de pacote (package-private) em campos e métodos injetados. Isso elimina a necessidade de Reflection e deixa o seu binário mais leve e rápido.

Mapeando Beans: A Tabela Completa de Escopos

No Quarkus, os escopos definem o ciclo de vida dos seus beans. Aqui está a comparação direta com o que você já conhece no Spring:

Quarkus (CDI)Spring EquivalentDescrição
@ApplicationScoped@Scope("singleton")Instância única via Proxy (Lazy). Recomendado para a maioria dos casos.
@Singleton@Scope("singleton")Instância única sem Proxy (Eager). Criada na injeção.
@RequestScoped@Scope("request")Uma instância associada à requisição HTTP atual.
@Dependent@Scope("prototype")Escopo padrão. Nova instância para cada ponto de injeção.
@SessionScoped@Scope("session")Vinculado à sessão HTTP (requer extensão quarkus-undertow).

Qual escolher: @ApplicationScoped ou @Singleton?

Embora ambos criem instâncias únicas, prefira sempre o @ApplicationScoped. O uso de um Client Proxy permite que o Quarkus gerencie melhor o ciclo de vida, facilite a substituição por mocks em testes e resolva dependências circulares de forma mais elegante. Use @Singleton apenas se precisar espremer o máximo de performance e não precisar de proxies.

Implementando o nosso Serviço de Pedidos

Vamos criar o nosso primeiro Bean. No Spring, você usaria @Service, aqui usaremos @ApplicationScoped. Repare que não usamos private nos campos e adicionamos o Logger para monitorar o processamento.

@ApplicationScoped
public class OrderService {
    
    private static final Logger LOG = Logger.getLogger(OrderService.class);

    public String process() {
        LOG.info("Processing order business logic...");
        return "Order processed successfully!";
    }
}

Injeção de Dependências: @Inject e Construtores

O Quarkus incentiva a injeção via construtor. Vamos criar um TaxService e injetá-lo no OrderService. Repare que, por haver apenas um construtor, a anotação @Inject torna-se opcional. Além disso, o Quarkus gera o construtor padrão necessário para o Proxy automaticamente.

@ApplicationScoped
public class TaxService {
    public double getRate() {
        return 0.1;
    }
}

@ApplicationScoped
public class OrderService {

    final TaxService taxService; // Campos 'final' são permitidos em injeção por construtor

    OrderService(TaxService taxService) {
        this.taxService = taxService;
    }

    public double calculateTotal(double amount) {
        return amount + (amount * taxService.getRate());
    }
}

Configuração Dinâmica com @ConfigProperty

Agora, vamos tornar o serviço dinâmico usando o @ConfigProperty. Diferente do Spring, se você usa um qualificador como este em um campo, o @Inject é opcional!

@ApplicationScoped
public class OrderService {

    private static final Logger LOG = Logger.getLogger(OrderService.class);

    final TaxService taxService;

    @ConfigProperty(name = "order.service.fee", defaultValue = "5.0")
    double serviceFee;

    OrderService(TaxService taxService) {
        this.taxService = taxService;
    }

    public double calculateTotal(double amount) {
        LOG.infof("Calculating total for amount %.2f with fee %.2f", amount, serviceFee);
        double tax = amount * taxService.getRate();
        return amount + tax + serviceFee;
    }
}

Dica Pro: Se precisar de identificadores baseados em String para seus beans, evite o @Named (que pode causar ambiguidades com o qualificador @Default) e utilize o @Identifier("nome-do-bean").

Gerindo Ambientes com Profiles: %dev, %test e %prod

Para finalizar, vamos configurar nossa aplicação para se comportar de forma diferente em cada ambiente sem complicação. Basta usar prefixos no seu arquivo application.properties:

# Valor padrão para produção (prod)
%prod.order.service.fee=15.0

# Valor reduzido para desenvolvimento (dev)
%dev.order.service.fee=5.0

# Valor zerado para testes unitários (test)
%test.order.service.fee=0.0

Conclusão

Migrar do Spring para o Quarkus é uma mudança de mentalidade para o mundo Cloud Native. Ao mover a resolução de dependências para o tempo de build com o ArC e adotar o CDI Lite 4.1, ganhamos aplicações que iniciam instantaneamente e consomem o mínimo de recursos.

Neste artigo, vimos como criar um projeto, mapear beans, injetar dependências e gerir configurações por perfil. Agora você já tem uma base sólida para construir serviços eficientes.

Se esse guia te ajudou, compartilhe com seu time para facilitar a migração tecnológica do projeto. No próximo artigo, vamos mergulhar em Programação Reativa e Testes Integrados com Mutiny!