Learn Quarkus - Architecture & Design Patterns
Episode 18 of 24

Learn Quarkus - Architecture & Design Patterns

This episode covers Quarkus application architecture: modular architecture with feature-based organization, domain-driven design and hexagonal architecture, event-driven architecture with reactive messaging, as well as CQRS, event sourcing, and publish/subscribe patterns.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

The larger an application gets, the more expensive a wrong structure becomes. Disorganized code becomes hard to change, hard to test, and hard for a new team to understand. A good architecture isn't a luxury — it's an investment in the project's longevity.

Episode 18 covers architecture and design patterns for Quarkus applications: modular architecture with feature-based organization, domain-driven design and hexagonal architecture, event-driven architecture with reactive messaging, as well as CQRS, event sourcing, and publish/subscribe patterns.

Modular Architecture with Feature-Based Organization

A Feature-Based Structure

Instead of grouping files by type (all controllers in one folder, all services in another), group them by feature. This makes each feature self-contained and easy to change.

Feature-based structure
src/main/java/com/example/
├── order/
│   ├── Order.java
│   ├── OrderRepository.java
│   ├── OrderService.java
│   └── OrderResource.java
├── customer/
│   ├── Customer.java
│   ├── CustomerRepository.java
│   └── CustomerResource.java
└── common/
    ├── ErrorResponse.java
    └── ValidationExceptionMapper.java

Each feature contains its entity, repository, service, and resource in one package. The common folder holds things that are truly cross-feature. Run ./mvnw quarkus:dev to see the Dev UI, which lists all beans per package.

The Benefits of Modularity

A change in one feature doesn't disturb other features. Teams can work in parallel on different features with minimal conflicts. Quarkus processes every package uniformly, so this separation adds no overhead.

Domain-Driven Design and Hexagonal Architecture

DDD in Quarkus

Domain-Driven Design (DDD) puts the domain model at the center. Important concepts: bounded context, aggregate, entity, and value object. Repositories and services are separated from the transport layer.

JavaDomain entity and repository
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
 
@Entity
@Table(name = "customers")
public class Customer extends PanacheEntity {
    public String nama;
    public String email;
 
    public void ubahEmail(String emailBaru) {
        this.email = emailBaru;
    }
}

Hexagonal Architecture

Hexagonal architecture (ports and adapters) separates the application core from the outside world. A port is an interface defined by the domain; an adapter is a technical implementation (REST, database, messaging). Quarkus makes this pattern easy with CDI interfaces:

JavaPort and adapter
public interface CustomerPort {
    Customer simpan(Customer customer);
}
 
@ApplicationScoped
public class CustomerRepositoryAdapter implements CustomerPort {
    @Override
    public Customer simpan(Customer customer) {
        return customer.persistAndReturnSelf();
    }
}

The domain calls CustomerPort, not caring whether the implementation is Hibernate, another database, or a mock. Swapping implementations just means swapping the adapter — the domain code doesn't change.

Event-Driven Architecture with Quarkus Reactive Messaging

Decoupling Through Events

Event-driven architecture separates producers from consumers. A producer only publishes events; consumers decide what to do. In Quarkus, this is realized with Reactive Messaging (episode 15).

JavaPublishing an order event
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
 
@ApplicationScoped
public class OrderEventPublisher {
 
    @Inject
    @Channel("order-created")
    Emitter<OrderCreated> emitter;
 
    public void terbitkan(OrderCreated event) {
        emitter.send(event);
    }
}

After an order is created, the service calls terbitkan(...). Consumers (for example a notification service) listen to order-created and send emails — without direct coupling.

Reactive Messaging for Scale

Connector configuration makes events flow through Kafka:

Event channel in Kafka
mp.messaging.outgoing.order-created.connector=smallrye-kafka
mp.messaging.outgoing.order-created.topic=order-created

Event-driven designs make your application more resilient: if a consumer is down, events stay queued in the broker and are processed when the consumer returns.

CQRS, Event Sourcing, and Publish/Subscribe Patterns

CQRS

CQRS (Command Query Responsibility Segregation) separates the write model from the read model. Writes use commands and a transactional database; reads use optimized queries:

JavaSeparate command and query
@ApplicationScoped
public class OrderCommandService {
    @Transactional
    public void buatOrder(BuatOrderCommand cmd) { ... }
}
 
@ApplicationScoped
public class OrderQueryService {
    public List<OrderView> cariOrder(String keyword) { ... }
}

CQRS gives you flexibility: the read model can use views, caches, or a read-only database without affecting write integrity.

Event Sourcing

Event sourcing stores every state change as an event, not just the final state. The current state is computed by replaying events. It suits domains that need a complete audit trail, such as finance.

Publish/Subscribe

Publish/subscribe lets a single event be consumed by many subscribers. In Reactive Messaging, use different groups and topics:

Separate subscribers per concern
mp.messaging.incoming.order-events.connector=smallrye-kafka
mp.messaging.incoming.order-events.topic=orders
mp.messaging.incoming.order-events.group.id=notifikasi-group

Each service uses its own group id, so a single orders event can trigger notifications, auditing, and analytics independently.

Wrap-Up

Episode 18 equips you with an architecture blueprint: feature-based organization for modularity, DDD and hexagonal architecture to separate the domain from technology, event-driven architecture with reactive messaging for decoupling, as well as CQRS, event sourcing, and publish/subscribe for advanced scenarios.

Key takeaways:

  • A feature-based structure makes each feature self-contained.
  • DDD puts the domain model at the center of the application.
  • Hexagonal architecture separates the core from technical adapters.
  • Event-driven design breaks the coupling between producers and consumers.
  • Reactive Messaging connects services through a message broker.
  • CQRS separates the write model and the read model.
  • Publish/subscribe lets a single event be consumed by many services.

In episode 19 we'll cover modern tooling and build automation — the Quarkus CLI, Maven plugin, and Gradle plugin, Dev Services for databases and observability, live coding and the Dev UI, as well as reproducible artifacts and native image pipelines.

Learn Quarkus - Architecture & Design Patterns | Learn Quarkus