Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
stuartwdouglas committed Feb 23, 2021
1 parent 56240a5 commit 8c6d022
Showing 78 changed files with 2,293 additions and 131 deletions.
39 changes: 39 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
@@ -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>
@@ -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>
@@ -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>
@@ -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>
2 changes: 0 additions & 2 deletions build-parent/pom.xml
Original file line number Diff line number Diff line change
@@ -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>
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;
@@ -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;
@@ -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) {
53 changes: 50 additions & 3 deletions docs/src/main/asciidoc/datasource.adoc
Original file line number Diff line number Diff line change
@@ -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`.
@@ -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 one 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

@@ -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]
----
@@ -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.
@@ -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.

== DevServices (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

Original file line number Diff line number Diff line change
@@ -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;
@@ -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;

@@ -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. "
@@ -82,7 +84,7 @@ void build(

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

if (aggregatedDataSourceBuildTimeConfigs.isEmpty()) {
@@ -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) {
@@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.quarkus.agroal.test;

import static io.quarkus.agroal.test.MultipleDataSourcesTestUtil.testDataSource;

import java.sql.SQLException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.agroal.api.AgroalDataSource;
import io.quarkus.agroal.DataSource;
import io.quarkus.test.QuarkusUnitTest;

public class MultipleDevServicesDataSourcesConfigTest {

//tag::injection[]
@Inject
AgroalDataSource defaultDataSource;

@Inject
@DataSource("users")
AgroalDataSource dataSource1;

@Inject
@DataSource("inventory")
AgroalDataSource dataSource2;
//end::injection[]

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClass(MultipleDataSourcesTestUtil.class))
.withConfigurationResource("application-multiple-devservices-datasources.properties");

@Test
public void testDataSourceInjection() throws SQLException {
testDataSource("default", defaultDataSource,
"jdbc:h2:tcp://localhost:" + extractPort(defaultDataSource) + "/mem:default;DB_CLOSE_DELAY=-1", "sa", 20);
testDataSource("users", dataSource1,
"jdbc:h2:tcp://localhost:" + extractPort(dataSource1) + "/mem:users;DB_CLOSE_DELAY=-1", "sa", 20);
testDataSource("inventory", dataSource2,
"jdbc:h2:tcp://localhost:" + extractPort(dataSource2) + "/mem:inventory;DB_CLOSE_DELAY=-1", "sa", 20);
}

public int extractPort(AgroalDataSource ds) {
String url = ds.getConfiguration().connectionPoolConfiguration().connectionFactoryConfiguration().jdbcUrl();
Matcher matcher = Pattern.compile("jdbc:h2:tcp://localhost:(\\d+)/").matcher(url);
matcher.find();
return Integer.parseInt(matcher.group(1));
}
}
Loading

0 comments on commit 8c6d022

Please sign in to comment.