Skip to content

Commit

Permalink
DevDB Support
Browse files Browse the repository at this point in the history
DevDB 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 11, 2021
1 parent 071a720 commit c1203d1
Show file tree
Hide file tree
Showing 70 changed files with 2,228 additions and 113 deletions.
39 changes: 39 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@
<log4j2-jboss-logmanager.version>1.0.0.Beta1</log4j2-jboss-logmanager.version>
<log4j-jboss-logmanager.version>1.2.0.Final</log4j-jboss-logmanager.version>
<avro.version>1.10.0</avro.version>
<testcontainers.version>1.15.1</testcontainers.version>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -321,6 +322,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 @@ -506,6 +515,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 @@ -516,6 +530,31 @@
<artifactId>quarkus-datasource-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devdb-postgresql</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devdb-h2</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devdb-mysql</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devdb-mariadb</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devdb-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 @@ -481,14 +481,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
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 (DevDB)

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 DevDB. Depending on your database type you may need docker installed in order to use this feature. DevDB
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 DevDB 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 DevDB 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 DevDB that allows you to create datasources without any config. If
you have a database extension that supports it and not config is provided Quarkus all 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 DevDB we recommended that you use the `%prod.` profile to define your
database settings.

=== Configuring DevDB

DevDB supports the following config options:

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

=== Named Datasources

When using DevDB 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".devdb=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
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 MultipleDevDBDataSourcesConfigTest {

//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-devdb-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 c1203d1

Please sign in to comment.