Das Quarkus-Framework: Wie saubere Architektur darin implementiert ist

Hallo Habr!



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 Objekte
  • app-api: Anwendungsschnittstellen entsprechend praktischen Fällen
  • app-impl: Umsetzung dieser Fälle anhand des Themenbereichs. Kommt auf app-apiund an domain.
  • infra-persistence: Implementiert Gateways, mit denen die Domäne mit der Datenbank-API interagieren kann. Kommt drauf an domain.
  • infra-web: Öffnet die betrachteten Fälle für die Interaktion mit der Außenwelt mithilfe von REST. Kommt drauf an app-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-resteasyund 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-imageund 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:devvom 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 Joeim 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-apiDefinieren 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-implerstellen 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 GetCustomersImplkann, benötigen Sie eine benutzerdefinierte Anmerkung UseCasewie 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-implzusätzlich zu den Abhängigkeiten app-apiund 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 Jimim JSON-Format angezeigt.



Definition und Implementierung der Domain



Als nächstes konzentrieren wir uns auf die Domain. Das Wesentliche hier ist domainrecht einfach: Es besteht aus Customereiner 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-persistencefolgende 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.propertiesim Modul main-partitiondie 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.sqldemselben 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 Joesie auch Jimim 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-partitionsieht 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-contextvon Get @Componentviele 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 @Producesvon 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-apimö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 .



All Articles