Skip to content

Commit 209c1d7

Browse files
committed
Support runtime active / inactive Mongo Clients
1 parent 180aed6 commit 209c1d7

File tree

14 files changed

+424
-89
lines changed

14 files changed

+424
-89
lines changed

docs/src/main/asciidoc/mongodb.adoc

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,71 @@ MongoClient mongoClient1;
303303
ReactiveMongoClient mongoClient2;
304304
----
305305

306+
=== Activate or deactivate Mongo Clients
307+
308+
When a Mongo Client is configured at build time, and its URL is set at runtime, it is active by default. Quarkus
309+
starts the corresponding Mongo Client when the application starts.
310+
311+
To deactivate a Mongo Client at runtime, either:
312+
313+
* Do not set `quarkus.mongodb[.optional name].hosts` or `quarkus.mongodb[.optional name].connection-#string#`.
314+
* Set `quarkus.mongodb[.optional name].active` to `false`.
315+
316+
If a Mongo Client is not active:
317+
318+
* The Mongo Client does not attempt to connect to Mongo during application startup.
319+
* The Mongo Client does not contribute a <<mongo-health-check,health check>>.
320+
* Static CDI injection points involving the Mongo Client, such as `@Inject ReactiveMongoClient mongoClient` or `@Inject MongoClient mongoClient`, cause application startup to fail.
321+
* Dynamic retrieval of the Mongo Client, such as through `CDI.getBeanContainer()`, `Arc.instance()`, or an injected `Instance<ReactiveMongoClient>`, causes an exception to be thrown.
322+
* Other Quarkus extensions that consume the Mongo Client may cause application startup to fail.
323+
324+
This feature is especially useful when the application must dynamically select a Mongo Client from a predefined set at
325+
runtime.
326+
327+
.An example of configuring multiple Mongo Clients for runtime selection:
328+
329+
[source,properties]
330+
----
331+
quarkus.mongodb.one.active=false
332+
quarkus.mongodb.one.connection-string=mongodb://127.0.0.1:27018
333+
quarkus.mongodb.two.active=false
334+
quarkus.mongodb.two.connection-string=mongodb://127.0.0.1:27019
335+
----
336+
337+
[source,java]
338+
----
339+
import io.quarkus.arc.InjectableInstance;
340+
341+
import com.mongodb.client.MongoClient;
342+
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
343+
344+
@ApplicationScoped
345+
public class MyConsumer {
346+
@Inject
347+
@MongoClientName("one")
348+
InjectableInstance<ReactiveMongoClient> one;
349+
@Inject
350+
@MongoClientName("two")
351+
InjectableInstance<MongoClient> two;
352+
public void doSomething() {
353+
ReactiveMongoClient mongoClient = one.getActive();
354+
// ...
355+
}
356+
}
357+
----
358+
359+
Setting `quarkus.mongodb.one.active=true` xref:config-reference.adoc#configuration-sources[at runtime] makes only the
360+
Mongo Client `one` available.
361+
Setting `quarkus.mongodb.two.active=true` at runtime makes only the Mongo Client `two` available.
362+
363+
[IMPORTANT]
364+
====
365+
A Mongo Client (either default or named) must always be discoverable at build-time to be considered for runtime
366+
injection. This can be done by injecting the Mongo Client name with `@MongoClientName`. If the Mongo Client name may be
367+
active or inactive, it needs to use the wrapper `InjectableInstance<>`, or else Quarkus will throw an exception at
368+
startup time if the Mongo Client is inactive. Alternatively, Mongo Clients may also be discovered via configuration.
369+
====
370+
306371
== Running a MongoDB Database
307372
As by default, `MongoClient` is configured to access a local MongoDB database on port 27017 (the default MongoDB port), if you have a local running database on this port, there is nothing more to do before being able to test it!
308373

@@ -605,6 +670,7 @@ The xref:mongodb-panache.adoc[MongoDB with Panache] extension facilitates the us
605670
The xref:liquibase-mongodb.adoc[Liquibase MongoDB] extension facilitates the initialization of a MongoDB database including indices and initial data.
606671
It implements the same schema migration facilities that Liquibase offers for SQL databases.
607672

673+
[[mongo-health-check]]
608674
== Connection Health Check
609675

610676
If you are using the `quarkus-smallrye-health` extension, `quarkus-mongodb-client` will automatically add a readiness health check

extensions/liquibase/liquibase-mongodb/runtime/src/main/java/io/quarkus/liquibase/mongodb/LiquibaseMongodbFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public Liquibase createLiquibase() {
146146

147147
private Database createDatabase(MongoClients clients, String clientName, String databaseName) {
148148
MongoConnection databaseConnection = new MongoConnection();
149-
MongoClient mongoClient = clients.createMongoClient(clientName);
149+
MongoClient mongoClient = clients.unmanagedMongoClient(clientName);
150150
databaseConnection.setMongoClient(mongoClient);
151151
databaseConnection.setMongoDatabase(mongoClient.getDatabase(databaseName));
152152
Database database = new MongoLiquibaseDatabase();

extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -397,9 +397,9 @@ private SyntheticBeanBuildItem createBlockingSyntheticBean(MongoClientRecorder r
397397
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
398398
.configure(MongoClient.class)
399399
.scope(ApplicationScoped.class)
400-
// pass the runtime config into the recorder to ensure that the DataSource related beans
401-
// are created after runtime configuration has been set up
402400
.supplier(recorder.mongoClientSupplier(clientName))
401+
.checkActive(recorder.checkActive(clientName))
402+
.startup()
403403
.setRuntimeInit();
404404

405405
return applyCommonBeanConfig(makeUnremovable, clientName, addMongoClientQualifier, configurator, false);
@@ -411,9 +411,9 @@ private SyntheticBeanBuildItem createReactiveSyntheticBean(MongoClientRecorder r
411411
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
412412
.configure(ReactiveMongoClient.class)
413413
.scope(ApplicationScoped.class)
414-
// pass the runtime config into the recorder to ensure that the DataSource related beans
415-
// are created after runtime configuration has been set up
416414
.supplier(recorder.reactiveMongoClientSupplier(clientName))
415+
.checkActive(recorder.checkActive(clientName))
416+
.startup()
417417
.setRuntimeInit();
418418

419419
return applyCommonBeanConfig(makeUnremovable, clientName, addMongoClientQualifier, configurator, true);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.quarkus.mongodb;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import jakarta.inject.Inject;
6+
7+
import org.junit.jupiter.api.Assertions;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.condition.DisabledOnOs;
10+
import org.junit.jupiter.api.condition.OS;
11+
import org.junit.jupiter.api.extension.RegisterExtension;
12+
13+
import com.mongodb.client.MongoClient;
14+
15+
import io.quarkus.arc.InactiveBeanException;
16+
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
17+
import io.quarkus.test.QuarkusUnitTest;
18+
19+
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "Flapdoodle doesn't work very well on Windows with replicas")
20+
public class MongoActiveClientsMissingConfigTest {
21+
@RegisterExtension
22+
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
23+
.withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class))
24+
.overrideRuntimeConfigKey("quarkus.mongodb.active.hosts", "")
25+
.overrideRuntimeConfigKey("quarkus.mongodb.active.connection-string", "")
26+
.assertException(e -> assertThat(e)// Can't use isInstanceOf due to weird classloading in tests
27+
.satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName()))
28+
.hasMessageContainingAll(
29+
"""
30+
Mongo Client 'active' was deactivated automatically because neither the \
31+
hosts nor the connectionString is set. \
32+
To activate the Mongo Client, set the configuration property 'quarkus.mongodb.active.hosts' \
33+
or 'quarkus.mongodb.active.connection-string' \
34+
Refer to https://quarkus.io/guides/mongodb for guidance.
35+
"""));
36+
37+
@Inject
38+
@MongoClientName("active")
39+
MongoClient mongoClient;
40+
@Inject
41+
@MongoClientName("active")
42+
ReactiveMongoClient reactiveMongoClient;
43+
44+
@Test
45+
void missingConfig() {
46+
Assertions.fail();
47+
}
48+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.quarkus.mongodb;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import jakarta.enterprise.inject.Any;
6+
import jakarta.inject.Inject;
7+
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.condition.DisabledOnOs;
10+
import org.junit.jupiter.api.condition.OS;
11+
import org.junit.jupiter.api.extension.RegisterExtension;
12+
13+
import com.mongodb.client.MongoClient;
14+
15+
import io.quarkus.arc.InjectableInstance;
16+
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
17+
import io.quarkus.test.QuarkusUnitTest;
18+
19+
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "Flapdoodle doesn't work very well on Windows with replicas")
20+
public class MongoActiveClientsTest extends MongoWithReplicasTestBase {
21+
@RegisterExtension
22+
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
23+
.withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class));
24+
25+
@Inject
26+
InjectableInstance<MongoClient> mongoClient;
27+
@Inject
28+
InjectableInstance<ReactiveMongoClient> reactiveMongoClient;
29+
30+
@Inject
31+
@MongoClientName("active")
32+
InjectableInstance<MongoClient> activeMongoClient;
33+
@Inject
34+
@MongoClientName("active")
35+
InjectableInstance<ReactiveMongoClient> activeReactiveMongoClient;
36+
37+
@Inject
38+
@Any
39+
InjectableInstance<MongoClient> all;
40+
@Inject
41+
@Any
42+
InjectableInstance<ReactiveMongoClient> allReactive;
43+
44+
@Test
45+
void inactiveClients() {
46+
assertEquals(1, mongoClient.listActive().size());
47+
assertEquals(1, reactiveMongoClient.listActive().size());
48+
49+
assertEquals(1, activeMongoClient.listActive().size());
50+
assertEquals(1, activeReactiveMongoClient.listActive().size());
51+
52+
assertEquals(2, all.listActive().size());
53+
assertEquals(2, allReactive.listActive().size());
54+
}
55+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.quarkus.mongodb;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import jakarta.inject.Inject;
6+
7+
import org.junit.jupiter.api.Assertions;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.condition.DisabledOnOs;
10+
import org.junit.jupiter.api.condition.OS;
11+
import org.junit.jupiter.api.extension.RegisterExtension;
12+
13+
import com.mongodb.client.MongoClient;
14+
15+
import io.quarkus.arc.InactiveBeanException;
16+
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
17+
import io.quarkus.test.QuarkusUnitTest;
18+
19+
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "Flapdoodle doesn't work very well on Windows with replicas")
20+
public class MongoInactiveClientsByConfigExceptionTest {
21+
@RegisterExtension
22+
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
23+
.withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class))
24+
.overrideRuntimeConfigKey("quarkus.mongodb.active.active", "false")
25+
.assertException(e -> assertThat(e)// Can't use isInstanceOf due to weird classloading in tests
26+
.satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName()))
27+
.hasMessageContainingAll(
28+
"""
29+
Mongo Client 'active' was deactivated through configuration properties. \
30+
To activate the Mongo Client, set configuration property \
31+
'quarkus.mongodb.active.active' to 'true' and configure the Mongo Client 'active'. \
32+
Refer to https://quarkus.io/guides/mongodb for guidance.
33+
"""));
34+
35+
@Inject
36+
@MongoClientName("active")
37+
MongoClient mongoClient;
38+
@Inject
39+
@MongoClientName("active")
40+
ReactiveMongoClient reactiveMongoClient;
41+
42+
@Test
43+
void inactive() {
44+
Assertions.fail();
45+
}
46+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.quarkus.mongodb;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import jakarta.inject.Inject;
6+
7+
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.api.condition.DisabledOnOs;
9+
import org.junit.jupiter.api.condition.OS;
10+
import org.junit.jupiter.api.extension.RegisterExtension;
11+
12+
import com.mongodb.client.MongoClient;
13+
14+
import io.quarkus.arc.InjectableInstance;
15+
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
16+
import io.quarkus.test.QuarkusUnitTest;
17+
18+
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "Flapdoodle doesn't work very well on Windows with replicas")
19+
public class MongoInactiveClientsByConfigTest extends MongoWithReplicasTestBase {
20+
@RegisterExtension
21+
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
22+
.withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class))
23+
.overrideRuntimeConfigKey("quarkus.mongodb.inactive.active", "false");
24+
25+
@Inject
26+
@MongoClientName("inactive")
27+
InjectableInstance<MongoClient> mongoClient;
28+
@Inject
29+
@MongoClientName("inactive")
30+
InjectableInstance<ReactiveMongoClient> reactiveMongoClient;
31+
32+
@Test
33+
void inactiveClients() {
34+
assertEquals(0, mongoClient.listActive().size());
35+
assertEquals(0, reactiveMongoClient.listActive().size());
36+
}
37+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.quarkus.mongodb;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import jakarta.inject.Inject;
6+
7+
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.api.condition.DisabledOnOs;
9+
import org.junit.jupiter.api.condition.OS;
10+
import org.junit.jupiter.api.extension.RegisterExtension;
11+
12+
import com.mongodb.client.MongoClient;
13+
14+
import io.quarkus.arc.InjectableInstance;
15+
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
16+
import io.quarkus.test.QuarkusUnitTest;
17+
18+
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "Flapdoodle doesn't work very well on Windows with replicas")
19+
public class MongoInactiveClientsTest extends MongoWithReplicasTestBase {
20+
@RegisterExtension
21+
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
22+
.withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class))
23+
.overrideRuntimeConfigKey("quarkus.mongodb.inactive.hosts", "")
24+
.overrideRuntimeConfigKey("quarkus.mongodb.inactive.connection-string", "");
25+
26+
@Inject
27+
@MongoClientName("inactive")
28+
InjectableInstance<MongoClient> mongoClient;
29+
@Inject
30+
@MongoClientName("inactive")
31+
InjectableInstance<ReactiveMongoClient> reactiveMongoClient;
32+
33+
@Test
34+
void inactiveClients() {
35+
assertEquals(0, mongoClient.listActive().size());
36+
assertEquals(0, reactiveMongoClient.listActive().size());
37+
}
38+
}

extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoMetricsTest.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,8 @@ void cleanup() {
3838

3939
@Test
4040
void testMetricsInitialization() {
41-
// Clients are created lazily, this metric should not be present yet
42-
assertThat(getMetric("mongodb.driver.pool.size")).isNull();
43-
assertThat(getMetric("mongodb.driver.pool.checkedout")).isNull();
44-
4541
// Just need to execute something so that a connection is opened
46-
String name = client.listDatabaseNames().first();
42+
client.listDatabaseNames().first();
4743

4844
assertThat(getMetric("mongodb.driver.pool.size")).isOne();
4945
assertThat(getMetric("mongodb.driver.commands")).isOne();

0 commit comments

Comments
 (0)