Während wir uns weiter mit neuen Java-Frameworks und Ihrem Interesse am Spring Boot- Buch befassen, schauen wir uns das neue Quarkus-Framework für Java an. Eine ausführliche Beschreibung finden Sie hier . Heute schlagen wir vor, die Übersetzung eines einfachen Artikels zu lesen, der zeigt, wie bequem es ist, mit Quarkus an einer sauberen Architektur festzuhalten .
Quarkus erlangt schnell den Status eines Frameworks, das nicht vermieden werden kann. Aus diesem Grund habe ich mich entschlossen, es noch einmal durchzugehen und zu prüfen, inwieweit es den Prinzipien der reinen Architektur entspricht.
Als Ausgangspunkt habe ich ein einfaches Maven-Projekt mit 5 Standardmodulen verwendet, um eine CRUD REST-Anwendung nach den Prinzipien einer sauberen Architektur zu erstellen:
domain
: Domänenobjekte und Gateway-Schnittstellen für diese Objekteapp-api
: Anwendungsschnittstellen entsprechend praktischen Fällenapp-impl
: Umsetzung dieser Fälle anhand des Themenbereichs. Kommt aufapp-api
und andomain
.infra-persistence
: Implementiert Gateways, mit denen die Domäne mit der Datenbank-API interagieren kann. Kommt drauf andomain
.infra-web
: Öffnet die betrachteten Fälle für die Interaktion mit der Außenwelt mithilfe von REST. Kommt drauf anapp-api
.
Darüber hinaus erstellen wir ein Modul
main-partition
, das als implementierbares Anwendungsartefakt dient.
Wenn Sie mit Quarkus arbeiten möchten, müssen Sie zunächst die Stücklistenspezifikation zur POM-Datei Ihres Projekts hinzufügen. Diese Stückliste verwaltet alle Versionen der von Ihnen verwendeten Abhängigkeiten. Sie müssen auch Standard-Plugins für Maven-Projekte in Ihrem Plugin-Verwaltungstool konfigurieren, z. B. das todsichere Plugin . Während Sie mit Quarkus arbeiten, konfigurieren Sie hier auch das gleichnamige Plugin. Zu guter Letzt müssen Sie hier das Plugin so konfigurieren, dass es mit jedem der Module (in <build> <plugins> ... </ plugins> </ build>) funktioniert, nämlich dem Jandex- Plugin... Da Quarkus CDI verwendet, fügt das Jandex-Plugin jedem Modul eine Indexdatei hinzu. Die Datei enthält Aufzeichnungen aller in diesem Modul verwendeten Anmerkungen und Links, die angeben, wo welche Anmerkungen verwendet werden. Infolgedessen ist CDI viel einfacher zu handhaben und viel weniger Arbeit muss später erledigt werden.
Nachdem die Grundstruktur fertig ist, können Sie mit dem Erstellen einer vollständigen Anwendung beginnen. Dazu müssen Sie sicherstellen, dass die Hauptpartition die ausführbare Quarkus-Anwendung erstellt. Dieser Mechanismus wird in jedem in Quarkus bereitgestellten Beispiel für einen Schnellstart veranschaulicht.
Zuerst konfigurieren wir den Build für die Verwendung des Quarkus-Plugins:
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Als Nächstes fügen wir jedem Anwendungsmodul Abhängigkeiten hinzu, in denen sie zusammen mit den Abhängigkeiten
quarkus-resteasy
und aufgeführt sind quarkus-jdbc-mysql
. In der letzten Abhängigkeit können Sie die Datenbank durch die Datenbank ersetzen, die Ihnen am besten gefällt (wenn Sie bedenken, dass wir später dem nativen Entwicklungspfad folgen und daher keine eingebettete Datenbank verwenden können, z. B. H2).
Alternativ können Sie ein Profil hinzufügen, um die native Anwendung später zu erstellen. Dazu benötigen Sie wirklich einen zusätzlichen Entwicklungsstand (GraalVM
native-image
und XCode, wenn Sie OSX verwenden).
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
Wenn Sie jetzt
mvn package quarkus:dev
vom Projektstamm aus laufen , haben Sie eine funktionierende Quarkus-App! Es gibt noch nicht viel zu sehen, da wir noch keine Controller oder Inhalte haben.
Hinzufügen eines REST-Controllers
In dieser Übung gehen wir von der Peripherie zum Kern. Erstellen wir zunächst einen REST-Controller, der Benutzerdaten zurückgibt (in diesem Beispiel ist dies nur der Name).
Um die JAX-RS-API verwenden zu können, muss dem Infra-Web-POM eine Abhängigkeit hinzugefügt werden:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
Der einfachste Controller-Code sieht folgendermaßen aus:
@Path("/customer")
@Produces(MediaType.APPLICATION_JSON)
public class CustomerResource {
@GET
public List<JsonCustomer> list() {
return getCustomers.getCustomer().stream()
.map(response -> new JsonCustomer(response.getName()))
.collect(Collectors.toList());
}
public static class JsonCustomer {
private String name;
public JsonCustomer(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Wenn wir die Anwendung jetzt ausführen, können wir localhost : 8080 / customer aufrufen und
Joe
im JSON-Format anzeigen.
Fügen Sie einen bestimmten Fall hinzu
Als nächstes fügen wir einen Fall und eine Implementierung für diesen praktischen Fall hinzu.
app-api
Definieren wir den folgenden Fall:
public interface GetCustomers {
List<Response> getCustomers();
class Response {
private String name;
public Response(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
}
Sie
app-impl
erstellen eine einfache Implementierung dieser Schnittstelle.
@UseCase
public class GetCustomersImpl implements GetCustomers {
private CustomerGateway customerGateway;
public GetCustomersImpl(CustomerGateway customerGateway) {
this.customerGateway = customerGateway;
}
@Override
public List<Response> getCustomers() {
return Arrays.asList(new Response("Jim"));
}
}
Damit CDI die Komponente sehen
GetCustomersImpl
kann, benötigen Sie eine benutzerdefinierte Anmerkung UseCase
wie unten definiert. Sie können auch die Standardanwendung ApplicationScoped und Annotation verwenden Transactional
. Wenn Sie jedoch eine eigene Annotation erstellen, können Sie Code logischer gruppieren und Ihren Implementierungscode von Frameworks wie CDI trennen.
@ApplicationScoped
@Transactional
@Stereotype
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
}
Um CDI-Annotationen verwenden zu können, müssen Sie der POM-Datei
app-impl
zusätzlich zu den Abhängigkeiten app-api
und die folgenden Abhängigkeiten hinzufügen domain
.
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
</dependency>
Als nächstes müssen wir den REST-Controller ändern, um ihn in den Fällen zu verwenden
app-api
.
...
private GetCustomers getCustomers;
public CustomerResource(GetCustomers getCustomers) {
this.getCustomers = getCustomers;
}
@GET
public List<JsonCustomer> list() {
return getCustomers.getCustomer().stream()
.map(response -> new JsonCustomer(response.getName()))
.collect(Collectors.toList());
}
...
Wenn Sie jetzt die Anwendung ausführen und localhost : 8080 / customer aufrufen , wird sie
Jim
im JSON-Format angezeigt.
Definition und Implementierung der Domain
Als nächstes konzentrieren wir uns auf die Domain. Das Wesentliche hier ist
domain
recht einfach: Es besteht aus Customer
einer Gateway-Schnittstelle, über die wir Verbraucher empfangen.
public class Customer {
private String name;
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public interface CustomerGateway {
List<Customer> getAllCustomers();
}
Wir müssen auch eine Implementierung für das Gateway bereitstellen, bevor wir es verwenden können. Wir bieten eine solche Schnittstelle in
infra-persistence
.
Für diese Implementierung verwenden wir die in Quarkus verfügbare JPA-Unterstützung sowie das Panache- Framework , das unser Leben ein wenig erleichtert. Zusätzlich zur Domain müssen wir die
infra-persistence
folgende Abhängigkeit hinzufügen zu:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
Zunächst definieren wir die JPA-Entität, die dem Verbraucher entspricht.
@Entity
public class CustomerJpa {
@Id
@GeneratedValue
private Long id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Wenn Sie mit Panache arbeiten, können Sie eine von zwei Optionen auswählen: Entweder erben Ihre Entitäten PanacheEntity oder Sie verwenden das Repository / DAO-Muster. Ich bin kein Fan des ActiveRecord-Musters, daher werde ich selbst beim Repository anhalten, aber es liegt an Ihnen, mit was Sie arbeiten werden.
@ApplicationScoped
public class CustomerRepository implements PanacheRepository<CustomerJpa> {
}
Nachdem wir unsere JPA-Entität und unser Repository haben, können wir das Gateway implementieren
Customer
.
@ApplicationScoped
public class CustomerGatewayImpl implements CustomerGateway {
private CustomerRepository customerRepository;
@Inject
public CustomerGatewayImpl(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
@Override
public List<Customer> getAllCustomers() {
return customerRepository.findAll().stream()
.map(c -> new Customer(c.getName()))
.collect(Collectors.toList());
}
}
Jetzt können Sie den Code in der Implementierung unseres Falls so ändern, dass er das Gateway verwendet.
...
private CustomerGateway customerGateway;
@Inject
public GetCustomersImpl(CustomerGateway customerGateway) {
this.customerGateway = customerGateway;
}
@Override
public List<Response> getCustomer() {
return customerGateway.getAllCustomers().stream()
.map(customer -> new GetCustomers.Response(customer.getName()))
.collect(Collectors.toList());
}
...
Wir können unsere Anwendung noch nicht starten, da die Quarkus-Anwendung noch mit den erforderlichen Persistenzparametern konfiguriert werden muss. Fügen Sie
src/main/resources/application.properties
im Modul main-partition
die folgenden Parameter hinzu.
quarkus.datasource.url=jdbc:mysql://localhost/test
quarkus.datasource.driver=com.mysql.cj.jdbc.Driver
quarkus.hibernate-orm.dialect=org.hibernate.dialect.MySQL8Dialect
quarkus.datasource.username=root
quarkus.datasource.password=root
quarkus.datasource.max-size=8
quarkus.datasource.min-size=2
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.sql-load-script=import.sql
Um die Originaldaten anzuzeigen, fügen wir die Datei auch
import.sql
demselben Verzeichnis hinzu, aus dem die Daten hinzugefügt werden.
insert into CustomerJpa(id, name) values(1, 'Joe');
insert into CustomerJpa(id, name) values(2, 'Jim');
Wenn Sie jetzt die Anwendung ausführen und localhost : 8080 / customer aufrufen , wird
Joe
sie auch Jim
im JSON-Format angezeigt. Wir haben also eine vollständige Anwendung, von REST bis zur Datenbank.
Native Option
Wenn Sie eine native Anwendung erstellen möchten, müssen Sie dies mit dem Befehl tun
mvn package -Pnative
. Dies kann je nach Entwicklungsstand einige Minuten dauern. Quarkus ist beim Start recht schnell und ohne native Unterstützung in 2-3 Sekunden gestartet. Wenn es jedoch mit GraalVM zu einer nativen ausführbaren Datei kompiliert wird, wird die entsprechende Zeit auf weniger als 100 Millisekunden reduziert. Für eine Java-Anwendung ist das blitzschnell.
Testen
Sie können die Quarkus-Anwendung mit dem entsprechenden Quarkus-Testframework testen. Wenn Sie den Test mit Anmerkungen versehen
@QuarkusTest
, startet JUnit zuerst den Quarkus-Kontext und führt dann den Test aus. Ein Test der gesamten Anwendung in main-partition
sieht ungefähr so aus:
@QuarkusTest
public class CustomerResourceTest {
@Test
public void testList() {
given()
.when().get("/customer")
.then()
.statusCode(200)
.body("$.size()", is(2),
"name", containsInAnyOrder("Joe", "Jim"));
}
}
Fazit
Quarkus ist in vielerlei Hinsicht ein starker Konkurrent von Spring Boot. Meiner Meinung nach sind einige Dinge in Quarkus noch besser gelöst. Obwohl App-Impl eine Framework-Abhängigkeit hat, ist es nur eine Abhängigkeit für Anmerkungen (im Fall von Spring fügen wir beim Hinzufügen
spring-context
von Get @Component
viele Spring -Kernabhängigkeiten hinzu). Wenn Ihnen dies nicht gefällt, können Sie dem Hauptabschnitt auch eine Java-Datei hinzufügen, indem Sie die Anmerkung @Produces
von CDI verwenden und die Komponente dort erstellen. In diesem Fall benötigen Sie keine zusätzlichen Abhängigkeiten in app-impl
. Aber aus irgendeinem Grund jakarta.enterprise.cdi-api
möchte ich dort weniger Sucht als Sucht sehen spring-context
.
Quarkus ist schnell, sehr schnell. Mit dieser Art von Anwendung ist es schneller als Spring Boot. Da laut Clean Architecture die meisten (wenn nicht alle) Abhängigkeiten des Frameworks außerhalb der Anwendung liegen sollten, wird die Wahl zwischen Quarkus und Spring Boot offensichtlich. In dieser Hinsicht liegt der Vorteil von Quarkus in der Tatsache, dass es sofort mit Blick auf die GraalVM-Unterstützung erstellt wurde und es Ihnen daher auf Kosten eines minimalen Aufwands ermöglicht, die Anwendung in eine native zu verwandeln. Spring Boot bleibt in dieser Hinsicht immer noch hinter Quarkus zurück, aber ich habe keinen Zweifel daran, dass es bald aufholen wird.
Das Experimentieren mit Quarkus hat mir jedoch auch geholfen, die vielen Unglücksfälle zu erkennen, die auf diejenigen warten, die versuchen, Quarkus mit klassischen Jakarta EE-Anwendungsservern zu verwenden. Während mit Quarkus noch nicht viel getan werden kann, unterstützt sein Codegenerator eine Vielzahl von Technologien, die im Kontext von Jakarta EE mit einem herkömmlichen Anwendungsserver noch nicht einfach zu verwenden sind. Quarkus deckt alle Grundlagen ab, die Menschen, die mit Jakarta EE vertraut sind, benötigen, und die Entwicklung darauf ist viel reibungsloser. Es wird interessant sein zu sehen, wie das Java-Ökosystem mit dieser Art von Wettbewerb umgehen kann.
Der gesamte Code für dieses Projekt ist auf Github veröffentlicht .