Skip to content

Commit

Permalink
DevServices Support
Browse files Browse the repository at this point in the history
DevServices allows for zero configuration databases in dev
and test mode, either starting the databases using
testcontainers, or creating them in process.
  • Loading branch information
stuartwdouglas committed Feb 18, 2021
1 parent 40b89fd commit 0623bb4
Show file tree
Hide file tree
Showing 79 changed files with 2,317 additions and 127 deletions.
39 changes: 39 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@
<log4j-jboss-logmanager.version>1.2.0.Final</log4j-jboss-logmanager.version>
<avro.version>1.10.0</avro.version>
<jacoco.version>0.8.6</jacoco.version>
<testcontainers.version>1.15.1</testcontainers.version>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -322,6 +323,14 @@
<scope>import</scope>
</dependency>

<!-- Testcontainers, imported as BOM -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>${testcontainers.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Quarkus core -->

<dependency>
Expand Down Expand Up @@ -517,6 +526,11 @@
<artifactId>quarkus-datasource-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-datasource-deployment-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-datasource</artifactId>
Expand All @@ -527,6 +541,31 @@
<artifactId>quarkus-datasource-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-postgresql</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-h2</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-mysql</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-mariadb</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-derby</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elasticsearch-rest-client-common</artifactId>
Expand Down
2 changes: 0 additions & 2 deletions build-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -485,14 +485,12 @@
<exclude>io.rest-assured:*</exclude>
<exclude>org.assertj:*</exclude>
<exclude>org.junit.jupiter:*</exclude>
<exclude>org.testcontainers:*</exclude>
</excludes>
<includes>
<include>io.quarkus:quarkus-test-*:*:*:test</include>
<include>io.rest-assured:*:*:*:test</include>
<include>org.assertj:*:*:*:test</include>
<include>org.junit.jupiter:*:*:*:test</include>
<include>org.testcontainers:*:*:*:test</include>
</includes>
<message>Found test dependencies with wrong scope:</message>
</bannedDependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package io.quarkus.runner.bootstrap;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand All @@ -13,6 +15,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
Expand Down Expand Up @@ -132,6 +135,55 @@ public AugmentActionImpl(CuratedApplication curatedApplication, List<Consumer<Bu
this.devModeType = devModeType;
}

@Override
public void performCustomBuild(String resultHandler, Object context, String... finalOutputs) {
ClassLoader classLoader = curatedApplication.createDeploymentClassLoader();
Class<? extends BuildItem>[] targets = Arrays.stream(finalOutputs)
.map(new Function<String, Class<? extends BuildItem>>() {
@Override
public Class<? extends BuildItem> apply(String s) {
try {
return (Class<? extends BuildItem>) Class.forName(s, false, classLoader);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}).toArray(Class[]::new);
BuildResult result = runAugment(true, Collections.emptySet(), null, classLoader, targets);

String debugSourcesDir = BootstrapDebug.DEBUG_SOURCES_DIR;
if (debugSourcesDir != null) {
for (GeneratedClassBuildItem i : result.consumeMulti(GeneratedClassBuildItem.class)) {
try {
if (i.getSource() != null) {
File debugPath = new File(debugSourcesDir);
if (!debugPath.exists()) {
debugPath.mkdir();
}
File sourceFile = new File(debugPath, i.getName() + ".zig");
sourceFile.getParentFile().mkdirs();
Files.write(sourceFile.toPath(), i.getSource().getBytes(StandardCharsets.UTF_8),
StandardOpenOption.CREATE);
log.infof("Wrote source: %s", sourceFile.getAbsolutePath());
} else {
log.infof("Source not available: %s", i.getName());
}
} catch (Exception t) {
log.errorf(t, "Failed to write debug source file: %s", i.getName());
}
}
}
try {
BiConsumer<Object, BuildResult> consumer = (BiConsumer<Object, BuildResult>) Class
.forName(resultHandler, false, classLoader)
.getConstructor().newInstance();
consumer.accept(context, result);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException
| InvocationTargetException e) {
throw new RuntimeException(e);
}
}

@Override
public AugmentResult createProductionApplication() {
if (launchMode != LaunchMode.NORMAL) {
Expand Down
13 changes: 13 additions & 0 deletions devtools/bom-descriptor-json/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jacoco</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jaeger</artifactId>
Expand Down
13 changes: 13 additions & 0 deletions docs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jacoco-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jaeger-deployment</artifactId>
Expand Down
53 changes: 50 additions & 3 deletions docs/src/main/asciidoc/datasource.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ please refer to the link:reactive-sql-clients[Reactive SQL clients guide].
This is a quick introduction to datasource configuration.
If you want a better understanding of how all this works, this guide has a lot more information in the subsequent paragraphs.

=== Zero Config Setup (DevServices)

When testing or running in dev mode Quarkus can even provide you with a zero config database out of the box, a feature
we refer to as DevServices. Depending on your database type you may need docker installed in order to use this feature. DevServices
is supported for the following open source databases:

* Postgresql (container)
* MySQL (container)
* MariaDB (container)
* H2 (in-process)
* Apache Derby (in-process)

If you want to use DevServices then all you need to do is include the relevant extension for the type of database you want (either reactive or
JDBC, or both), and don't configure a database URL, username and password, Quarkus will provide the database and you can just start
coding without worrying about config.

=== JDBC datasource

Add the `agroal` extension plus one of `jdbc-db2`, `jdbc-derby`, `jdbc-h2`, `jdbc-mariadb`, `jdbc-mssql`, `jdbc-mysql`, or `jdbc-postgresql`.
Expand All @@ -48,13 +64,16 @@ Then configure your datasource:

[source, properties]
----
quarkus.datasource.db-kind=postgresql
quarkus.datasource.db-kind=postgresql <1>
quarkus.datasource.username=<your username>
quarkus.datasource.password=<your password>
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_orm_test
quarkus.datasource.jdbc.max-size=16
----
<1> If you only have a single JDBC extension, or you are running tests and only have a single test scoped JDBC extension installed then this is
optional. If there is only once possible extension we assume this is the correct one, and if a driver has been added with test scope then
we assume that this should be used in testing.

=== Reactive datasource

Expand All @@ -64,20 +83,22 @@ Then configure your reactive datasource:

[source, properties]
----
quarkus.datasource.db-kind=postgresql
quarkus.datasource.db-kind=postgresql<1>
quarkus.datasource.username=<your username>
quarkus.datasource.password=<your password>
quarkus.datasource.reactive.url=postgresql:///your_database
quarkus.datasource.reactive.max-size=20
----
<1> As specified above this is optional.

== Default datasource

A datasource can be either a JDBC datasource, a reactive one or both.
It all depends on how you configure it and which extensions you added to your project.

To define a datasource, start with the following:
To define a datasource, start with the following (note that this is only required if you have more than one
database type installed):

[source, properties]
----
Expand Down Expand Up @@ -382,6 +403,10 @@ quarkus.datasource.inventory.jdbc.max-size=12
Notice there is an extra bit in the key.
The syntax is as follows: `quarkus.datasource.[optional name.][datasource property]`.

NOTE: Even when only one database extension is installed named databases need to specify at least one build time
property so that Quarkus knows they exist. Generally this will be the `db-kind` property, although you can also
specify DevServices properties to create named datasources (covered later in this guide).

=== Named Datasource Injection

When using multiple datasources, each `DataSource` also has the `io.quarkus.agroal.DataSource` qualifier with the name of the datasource as the value.
Expand Down Expand Up @@ -435,6 +460,28 @@ If the Narayana JTA extension is also available, integration is automatic.

You can override this by setting the `transactions` configuration property - see the <<configuration-reference, Configuration Reference>> below.

== DevDb (Configuration Free Databases)

As mentioned above Quarkus supports a feature called DevServices that allows you to create datasources without any config. If
you have a database extension that supports it and no config is provided Quarkus will automatically start a database (either
using Testcontainers, or by starting a Java DB in process), and automatically configure a connection to this database.

Production databases need to be configured as normal, so if you want to include a production database config in your
application.properties and continue to use DevServices we recommend that you use the `%prod.` profile to define your
database settings.

=== Configuring DevServices

DevServices supports the following config options:

include::{generated-dir}/config/quarkus-datasource-config-group-dev-services-build-time-config.adoc[opts=optional, leveloffset=+1]

=== Named Datasources

When using DevServices the default datasource will always be created, but to specify a named datasource you need to have
at least one build time property so Quarkus knows to create the datasource. In general you will usually either specify
the `db-kind` property, or explicitly enable DevDb: `quarkus.datasource."named".devservices=true`.

[[in-memory-databases]]
== Testing with in-memory databases

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@
import io.quarkus.agroal.runtime.DataSources;
import io.quarkus.agroal.runtime.DataSourcesJdbcBuildTimeConfig;
import io.quarkus.agroal.runtime.TransactionIntegration;
import io.quarkus.agroal.spi.DefaultDataSourceDbKindBuildItem;
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.agroal.spi.JdbcDriverBuildItem;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.datasource.deployment.spi.DefaultDataSourceDbKindBuildItem;
import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig;
import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig;
import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig;
Expand All @@ -47,6 +47,7 @@
import io.quarkus.deployment.builditem.SslNativeConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem;

Expand All @@ -73,7 +74,8 @@ void build(
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<NativeImageResourceBuildItem> resource,
BuildProducer<ExtensionSslNativeSupportBuildItem> sslNativeSupport,
BuildProducer<AggregatedDataSourceBuildTimeConfigBuildItem> aggregatedConfig) throws Exception {
BuildProducer<AggregatedDataSourceBuildTimeConfigBuildItem> aggregatedConfig,
CurateOutcomeBuildItem curateOutcomeBuildItem) throws Exception {
if (dataSourcesBuildTimeConfig.driver.isPresent() || dataSourcesBuildTimeConfig.url.isPresent()) {
throw new ConfigurationException(
"quarkus.datasource.url and quarkus.datasource.driver have been deprecated in Quarkus 1.3 and removed in 1.9. "
Expand All @@ -82,7 +84,7 @@ void build(

List<AggregatedDataSourceBuildTimeConfigBuildItem> aggregatedDataSourceBuildTimeConfigs = getAggregatedConfigBuildItems(
dataSourcesBuildTimeConfig,
dataSourcesJdbcBuildTimeConfig,
dataSourcesJdbcBuildTimeConfig, curateOutcomeBuildItem,
jdbcDriverBuildItems, defaultDbKinds);

if (aggregatedDataSourceBuildTimeConfigs.isEmpty()) {
Expand Down Expand Up @@ -257,13 +259,12 @@ void generateDataSourceBeans(AgroalRecorder recorder,
private List<AggregatedDataSourceBuildTimeConfigBuildItem> getAggregatedConfigBuildItems(
DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig,
DataSourcesJdbcBuildTimeConfig dataSourcesJdbcBuildTimeConfig,
CurateOutcomeBuildItem curateOutcomeBuildItem,
List<JdbcDriverBuildItem> jdbcDriverBuildItems, List<DefaultDataSourceDbKindBuildItem> defaultDbKinds) {
List<AggregatedDataSourceBuildTimeConfigBuildItem> dataSources = new ArrayList<>();

Optional<String> effectiveDbKind = dataSourcesBuildTimeConfig.defaultDataSource.dbKind;
if (!effectiveDbKind.isPresent() && (defaultDbKinds.size() == 1)) {
effectiveDbKind = Optional.of(defaultDbKinds.get(0).getDbKind());
}
Optional<String> effectiveDbKind = DefaultDataSourceDbKindBuildItem
.resolve(dataSourcesBuildTimeConfig.defaultDataSource.dbKind, defaultDbKinds, curateOutcomeBuildItem);

if (effectiveDbKind.isPresent()) {
if (dataSourcesJdbcBuildTimeConfig.jdbc.enabled) {
Expand All @@ -282,11 +283,16 @@ private List<AggregatedDataSourceBuildTimeConfigBuildItem> getAggregatedConfigBu
if (!jdbcBuildTimeConfig.enabled) {
continue;
}
Optional<String> dbKind = DefaultDataSourceDbKindBuildItem
.resolve(entry.getValue().dbKind, defaultDbKinds, curateOutcomeBuildItem);
if (!dbKind.isPresent()) {
continue;
}
dataSources.add(new AggregatedDataSourceBuildTimeConfigBuildItem(entry.getKey(),
entry.getValue(),
jdbcBuildTimeConfig,
entry.getValue().dbKind.get(),
resolveDriver(entry.getKey(), entry.getValue().dbKind.get(), jdbcBuildTimeConfig, jdbcDriverBuildItems)));
dbKind.get(),
resolveDriver(entry.getKey(), dbKind.get(), jdbcBuildTimeConfig, jdbcDriverBuildItems)));
}

return dataSources;
Expand Down
Loading

0 comments on commit 0623bb4

Please sign in to comment.