Episode ini membahas arsitektur aplikasi Quarkus: modular architecture dengan feature-based organization, domain-driven design dan hexagonal architecture, event-driven architecture dengan reactive messaging, serta CQRS, event sourcing, dan publish/subscribe patterns.

Semakin besar aplikasi, semakin besar pula biaya salah struktur. Kode yang tidak terorganisir menjadi sulit diubah, sulit dites, dan sulit dipahami tim baru. Arsitektur yang baik bukan kemewahan — ia investasi untuk kelangsungan proyek.
Episode 18 membahas arsitektur dan design patterns untuk aplikasi Quarkus: modular architecture dengan feature-based organization, domain-driven design dan hexagonal architecture, event-driven architecture dengan reactive messaging, serta CQRS, event sourcing, dan publish/subscribe patterns.
Daripada mengelompokkan file berdasarkan tipe (semua controller di satu folder, semua service di folder lain), kelompokkan berdasarkan fitur. Ini membuat setiap fitur mandiri dan mudah diubah.
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.javaSetiap fitur berisi entity, repository, service, dan resource-nya dalam satu package. Folder common menampung hal yang benar-benar lintas fitur. Jalankan ./mvnw quarkus:dev untuk melihat Dev UI yang mencantumkan seluruh bean per package.
Perubahan pada satu fitur tidak mengganggu fitur lain. Tim bisa bekerja paralel di fitur berbeda dengan konflik minimal. Quarkus memproses setiap package secara seragam, jadi pemisahan ini tidak menambah overhead.
Domain-Driven Design (DDD) menempatkan model domain sebagai pusat. Konsep penting: bounded context, aggregate, entity, dan value object. Repository dan service dipisahkan dari transport layer.
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 (ports and adapters) memisahkan inti aplikasi dari dunia luar. Port adalah interface yang didefinisikan domain; adapter adalah implementasi teknis (REST, database, messaging). Quarkus mempermudah pola ini dengan interface CDI:
public interface CustomerPort {
Customer simpan(Customer customer);
}
@ApplicationScoped
public class CustomerRepositoryAdapter implements CustomerPort {
@Override
public Customer simpan(Customer customer) {
return customer.persistAndReturnSelf();
}
}Domain memanggil CustomerPort, tidak peduli apakah implementasinya Hibernate, database lain, atau mock. Mengganti implementasi tinggal mengganti adapter — kode domain tidak berubah.
Event-driven architecture memisahkan producer dari consumer. Producer hanya mempublikasikan event; consumer memutuskan apa yang dilakukan. Di Quarkus, ini diwujudkan dengan Reactive Messaging (episode 15).
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);
}
}Setelah order dibuat, service memanggil terbitkan(...). Consumer (misal layanan notifikasi) mendengarkan order-created dan mengirim email — tanpa coupling langsung.
Konfigurasi connector membuat event berjalan di Kafka:
mp.messaging.outgoing.order-created.connector=smallrye-kafka
mp.messaging.outgoing.order-created.topic=order-createdEvent-driven membuat aplikasi lebih resilient: jika consumer down, event tetap antri di broker dan diproses saat consumer kembali.
CQRS (Command Query Responsibility Segregation) memisahkan model tulis dari model baca. Tulis memakai command dan database transaksional; baca memakai query teroptimasi:
@ApplicationScoped
public class OrderCommandService {
@Transactional
public void buatOrder(BuatOrderCommand cmd) { ... }
}
@ApplicationScoped
public class OrderQueryService {
public List<OrderView> cariOrder(String keyword) { ... }
}CQRS memberi fleksibilitas: model baca bisa memakai view, cache, atau database khusus baca tanpa mempengaruhi integritas tulis.
Event sourcing menyimpan seluruh perubahan state sebagai event, bukan hanya state terakhir. State sekarang dihitung dengan replaying event. Cocok untuk domain yang perlu audit trail lengkap, seperti keuangan.
Publish/subscribe memungkinkan satu event dikonsumsi banyak subscriber. Di Reactive Messaging, gunakan group dan topik berbeda:
mp.messaging.incoming.order-events.connector=smallrye-kafka
mp.messaging.incoming.order-events.topic=orders
mp.messaging.incoming.order-events.group.id=notifikasi-groupSetiap service memakai group id sendiri, sehingga satu event orders bisa memicu notifikasi, audit, dan analytics secara independen.
Episode 18 membekali kalian cetak biru arsitektur: feature-based organization untuk modularitas, DDD dan hexagonal untuk memisahkan domain dari teknologi, event-driven architecture dengan reactive messaging untuk decoupling, serta CQRS, event sourcing, dan publish/subscribe untuk skenario lanjutan.
Inti yang harus dibawa pulang:
Di episode 19 selanjutnya kita akan membahas tooling modern dan build automation — Quarkus CLI, Maven plugin, dan Gradle plugin, Dev Services untuk database dan observability, live coding dan Dev UI, serta reproducible artifacts dan native image pipeline.