Skip to content

Commit d4f441b

Browse files
authored
Merge pull request #45703 from ibethus/fix-doc-writing-dev-service
Fix code format and missing dep on "writing a dev service" page
2 parents 92b7b79 + fc70d27 commit d4f441b

File tree

1 file changed

+73
-43
lines changed

1 file changed

+73
-43
lines changed

docs/src/main/asciidoc/extension-writing-dev-service.adoc

Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,114 +9,144 @@ include::_attributes.adoc[]
99
:categories: writing-extensions
1010
:diataxis-type: howto
1111
:topics: extensions
12-
////
13-
////
1412

13+
Learn how to develop a xref:dev-services.adoc[Dev Service] for your extension in order to replace an external service in development mode.
1514

1615
== Prerequisites
1716

18-
- You should already have an xref:building-my-first-extension.adoc[extension structure] in place
19-
- You should have a containerised version of your external service (not all Dev Services rely on containers, but most do)
17+
:prerequisites-time: 15 minutes
18+
:prerequisites-docker:
19+
:prerequisites-no-cli:
20+
:prerequisites-no-graalvm:
21+
include::{includes}/prerequisites.adoc[]
22+
* An xref:building-my-first-extension.adoc[extension structure] in place
23+
* A containerised version of your external service (not all Dev Services rely on containers, but most do)
2024

2125
== Creating a Dev Service
2226

23-
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+
If your extension provides APIs for connecting to an external service, it's a good idea to provide a dev service implementation.
28+
29+
First, you must add the following dependency to the build file, in your xref:writing-extensions.adoc#project-setup[deployement] module :
30+
31+
[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"]
32+
.pom.xml
33+
----
34+
<dependency>
35+
<groupId>io.quarkus</groupId>
36+
<artifactId>quarkus-devservices-deployment</artifactId>
37+
</dependency>
38+
----
39+
40+
[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"]
41+
.build.gradle
42+
----
43+
implementation("io.quarkus:quarkus-devservices-deployment")
44+
----
2445

25-
To create a Dev Service, add a new build step into the extension processor class that returns a `DevServicesResultBuildItem`.
46+
Then, add a new build step into the extension processor class that returns a `DevServicesResultBuildItem`.
2647
Here, the https://hub.docker.com/_/hello-world[`hello-world`] image is used, but you should set up the right image for your service.
2748

28-
[source%nowrap,java]
49+
[source,java]
2950
----
30-
@BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class)
31-
public DevServicesResultBuildItem createContainer() {
32-
DockerImageName dockerImageName = DockerImageName.parse("hello-world");
33-
GenericContainer container = new GenericContainer<>(dockerImageName)
34-
.withExposedPorts(SERVICE_PORT, OTHER_SERVICE_PORT)
35-
.waitingFor(Wait.forLogMessage(".*" + "Started" + ".*", 1))
36-
.withReuse(true);
51+
@BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class)
52+
public DevServicesResultBuildItem createContainer() {
53+
DockerImageName dockerImageName = DockerImageName.parse("hello-world");
54+
GenericContainer container = new GenericContainer<>(dockerImageName)
55+
.withExposedPorts(SERVICE_PORT, OTHER_SERVICE_PORT)
56+
.waitingFor(Wait.forLogMessage(".*Started.*", 1))
57+
.withReuse(true);
3758
38-
container.start();
59+
container.start();
3960
40-
String newUrl = "http://" + container.getHost() + ":" + container.getMappedPort(SERVICE_PORT);
41-
Map<String, String> configOverrides = Map.of("some-service.base-url", newUrl);
61+
String newUrl = "http://%s:%d".formatted(container.getHost(),
62+
container.getMappedPort(SERVICE_PORT));
63+
Map<String, String> configOverrides = Map.of("some-service.base-url", newUrl);
4264
43-
return new DevServicesResultBuildItem.RunningDevService(FEATURE, container.getContainerId(),
44-
container::close, configOverrides)
45-
.toBuildItem();
46-
}
65+
return new DevServicesResultBuildItem.RunningDevService(FEATURE,
66+
container.getContainerId(),
67+
container::close,
68+
configOverrides).toBuildItem();
69+
}
4770
----
4871

4972
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`.
50-
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.
73+
However, the application will not be able to connect to it, because no ports are exposed.
74+
To expose ports, add `withExposedPorts` to the container construction.
5175
For example,
5276

53-
[source%nowrap,java]
77+
[source,java]
5478
----
5579
GenericContainer container = new GenericContainer<>(dockerImageName)
5680
.withExposedPorts(SERVICE_PORT, OTHER_SERVICE_PORT);
5781
----
5882

59-
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?
83+
`Testcontainers` will map these ports to random ports on the host.
84+
This avoids port conflicts, but presents a new problem – how do applications connect to the service in the container?
6085

6186
To allow applications to connect, the extension should override the default configuration for the service with the mapped ports.
6287
This must be done after starting the container.
6388
For example,
6489

65-
[source%nowrap,java]
90+
[source,java]
6691
----
67-
container.start();
68-
Map<String, String> configOverrides = Map.of("some-service.base-url",
69-
"http://" + container.getHost() + ":" + container.getMappedPort(SERVICE_PORT));
92+
container.start();
93+
String serviceUrl = "http://%s:%d".formatted(container.getHost(),
94+
container.getMappedPort(SERVICE_PORT));
95+
Map<String, String> configOverrides = Map.of("some-service.base-url",
96+
serviceUrl);
7097
----
7198

7299
Other configuration overrides may be included in the same map.
73100

74101
== Waiting for the container to start
75102

76-
You should add a `.waitingFor` call to the container construction, to wait for the container to start. For example
103+
You should add a `.waitingFor` call to the container construction, to wait for the container to start.
104+
For example
77105

78-
[source%nowrap,java]
106+
[source,java]
79107
----
80-
.waitingFor(Wait.forLogMessage(".*" + "Started" + ".*", 1))
108+
container.waitingFor(Wait.forLogMessage(".*Started.*", 1));
81109
----
82110

83-
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.
111+
Waiting for a port to be open is another option.
112+
See the link:https://java.testcontainers.org/features/startup_and_waits/[Testcontainers documentation] for a full discussion on wait strategies.
84113

85114
== Configuring the Dev Service
86115

87116
To configure the Dev Service launch process, your build step can accept a `ConfigPhase.BUILD_TIME` config class in its constructor.
88117
For example,
89118

90-
[source%nowrap,java]
119+
[source,java]
91120
----
92-
@BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class)
93-
public DevServicesResultBuildItem createContainer(MyConfig config) {
121+
@BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class)
122+
public DevServicesResultBuildItem createContainer(MyConfig config) {}
94123
----
95124

96125
You may wish to use this config to set a fixed port, or set an image name, for example.
97126

98-
[source%nowrap,java]
127+
[source,java]
99128
----
100-
if (config.port.isPresent()) {
101-
container.setPortBindings(List.of(config.port.get() + ":" + SERVICE_PORT));
102-
}
129+
if (config.port.isPresent()) {
130+
String portBinding = "%d:%d".formatted(config.port.get(), SERVICE_PORT);
131+
container.setPortBindings(List.of(portBinding));
132+
}
103133
----
104134

105135
== Controlling re-use
106136

107-
In dev mode, with live reload, Quarkus may restart frequently. By default, this will also restart test containers.
137+
In dev mode, with live reload, Quarkus may restart frequently.
138+
By default, this will also restart test containers.
108139
Quarkus restarts are usually very fast, but containers may take much longer to restart.
109140
To prevent containers restarting on every code change, you can mark the container as reusable:
110141

111-
[source%nowrap,java]
142+
[source,java]
112143
----
113-
.withReuse(true)
144+
container.withReuse(true);
114145
----
115146

116147
Some Dev Services implement sophisticated reuse logic in which they track the state of the container in the processor itself.
117148
You may need this if your service has more complex requirements, or needs sharing across instances.
118149

119-
120150
== References
121151

122152
- xref:dev-services.adoc[Dev services overview]

0 commit comments

Comments
 (0)