Skip to content

Commit b48d37c

Browse files
committed
Dev services documentation
1 parent 66429bd commit b48d37c

File tree

2 files changed

+132
-0
lines changed

2 files changed

+132
-0
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
////
2+
This document is maintained in the main Quarkus repository
3+
and pull requests should be submitted there:
4+
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
5+
////
6+
[id="extension-writing-dev-service"]
7+
= Writing a dev service
8+
include::_attributes.adoc[]
9+
:categories: writing-extensions
10+
:diataxis-type: howto
11+
:topics: extensions
12+
////
13+
////
14+
15+
How-to guides are goal-oriented and should help the reader accomplish a task (where there may be forks in the path).
16+
17+
////
18+
19+
== Prerequisites
20+
21+
- You should already have an xref:building-my-first-extension.adoc[extension structure] in place
22+
- You should have a containerised version of your external service (not all dev services rely on containers, but most do)
23+
24+
== Creating a dev service
25+
26+
If your extension provides APIs for connecting to an external service, it's a good idea to provide a xref:dev-services.adoc[dev service] implementation.
27+
28+
To create a dev service, add a new build step into the extension processor class that returns a `DevServicesResultBuildItem`.
29+
Here, the link:https://hub.docker.com/_/hello-world`hello-world` image is used, but you should set up the right image for your service.
30+
31+
[source%nowrap,java]
32+
----
33+
@BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) {
34+
public DevServicesResultBuildItem createContainer() {
35+
DockerImageName dockerImageName = DockerImageName.parse("hello-world");
36+
GenericContainer container = new GenericContainer<>(dockerImageName)
37+
.withExposedPorts(SERVICE_PORT, OTHER_SERVICE_PORT)
38+
.waitingFor(Wait.forLogMessage(".*" + "Started" + ".*", 1))
39+
.withReuse(true);
40+
41+
container.start();
42+
43+
String newUrl = "http://" + container.getHost() + ":" + container.getMappedPort(SERVICE_PORT);
44+
Map<String, String> configOverrides = Map.of("some-service.base-url", newUrl);
45+
46+
return new DevServicesResultBuildItem.RunningDevService(FEATURE, container.getContainerId(),
47+
container::close, configOverrides)
48+
.toBuildItem();
49+
}
50+
----
51+
52+
With this code, you should be able to see your container starting if you add your extension to a test application and run `quarkus dev`.
53+
However, the application will not be able to connect to it, because no ports are exposed. To expose ports, add `withExposedPorts` to the container construction.
54+
For example,
55+
56+
[source%nowrap,java]
57+
----
58+
GenericContainer container = new GenericContainer<>(dockerImageName)
59+
.withExposedPorts(SERVICE_PORT, OTHER_SERVICE_PORT);
60+
----
61+
62+
Testcontainers will map these ports to random ports on the host. This avoids port conflicts, but presents a new problem – how do applications connect to the service in the container?
63+
64+
To allow applications to connect, the extension should override the default configuration for the service with the mapped ports.
65+
This must be done after starting the container.
66+
For example,
67+
68+
[source%nowrap,java]
69+
----
70+
container.start();
71+
Map<String, String> configOverrides = Map.of("some-service.base-url",
72+
"http://" + container.getHost() + ":" + container.getMappedPort(SERVICE_PORT));
73+
----
74+
75+
Other configuration overrides may be included in the same map.
76+
77+
== Waiting for the container to start
78+
79+
You should add a `.waitingFor` call to the container construction, to wait for the container to start. For example
80+
81+
[source%nowrap,java]
82+
----
83+
.waitingFor(Wait.forLogMessage(".*" + "Started" + ".*", 1))
84+
----
85+
86+
Waiting for a port to be open is another option. See the link:https://java.testcontainers.org/features/startup_and_waits/[Testcontainers documentation] for a full discussion of wait strategies.
87+
88+
== Configuring the dev service
89+
90+
To configure the dev service launch process, your build step can accept a `ConfigPhase.BUILD_TIME` config class in its constructor.
91+
For example,
92+
93+
[source%nowrap,java]
94+
----
95+
@BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) {
96+
public DevServicesResultBuildItem createContainer(MyConfig config) {
97+
----
98+
99+
You may wish to use this config to set a fixed port, or set an image name, for example.
100+
101+
[source%nowrap,java]
102+
----
103+
if (config.port.isPresent()) {
104+
container.setPortBindings(List.of(config.port.get() + ":" + SERVICE_PORT));
105+
}
106+
----
107+
108+
== Controlling re-use
109+
110+
In dev mode, with hot reload, Quarkus may restart frequently. By default, this will also restart test containers.
111+
Quarkus restarts are usually very fast, but containers may take much longer to restart.
112+
To prevent containers restarting on every code change, you can mark the container as reusable:
113+
114+
[source%nowrap,java]
115+
----
116+
.withReuse(true)
117+
----
118+
119+
Some dev services implement sophisticated reuse logic in which they track the state of the container in the processor itself.
120+
You may need this if your service has more complex requirements, or needs sharing across instances.
121+
122+
123+
== References
124+
125+
- xref:dev-services.adoc[Dev services overview]
126+
- xref:writing-extensions.adoc[Guide to writing extensions]

docs/src/main/asciidoc/writing-extensions.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1978,6 +1978,12 @@ in your runtime module, and add a `META-INF/services/io.quarkus.dev.spi.HotRepla
19781978
On startup the `setupHotDeployment` method will be called, and you can use the provided `io.quarkus.dev.spi.HotReplacementContext`
19791979
to initiate a scan for changed files.
19801980

1981+
==== Dev services
1982+
1983+
Where extensions use an external service, adding dev service can improve the user experience in development and test modes.
1984+
See xref:extension-writing-dev-service.adoc[how to write a dev service] for more details.
1985+
1986+
19811987
=== Testing Extensions
19821988

19831989
Testing of Quarkus extensions should be done with the `io.quarkus.test.QuarkusUnitTest` JUnit 5 extension.

0 commit comments

Comments
 (0)