Skip to content

Commit

Permalink
Dev services documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
holly-cummins committed Aug 14, 2024
1 parent 66429bd commit 789d480
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 0 deletions.
123 changes: 123 additions & 0 deletions docs/src/main/asciidoc/extension-writing-dev-service.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
////
This document is maintained in the main Quarkus repository
and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
////
[id="extension-writing-dev-service"]
= Writing a dev service
include::_attributes.adoc[]
:categories: writing-extensions
:diataxis-type: howto
:topics: extensions
////
////


== Prerequisites

- You should already have an xref:building-my-first-extension.adoc[extension structure] in place
- You should have a containerised version of your external service (not all dev services rely on containers, but most do)

== Creating a dev service

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.

To create a dev service, add a new build step into the extension processor class that returns a `DevServicesResultBuildItem`.
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.

[source%nowrap,java]
----
@BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) {
public DevServicesResultBuildItem createContainer() {
DockerImageName dockerImageName = DockerImageName.parse("hello-world");
GenericContainer container = new GenericContainer<>(dockerImageName)
.withExposedPorts(SERVICE_PORT, OTHER_SERVICE_PORT)
.waitingFor(Wait.forLogMessage(".*" + "Started" + ".*", 1))
.withReuse(true);
container.start();
String newUrl = "http://" + container.getHost() + ":" + container.getMappedPort(SERVICE_PORT);
Map<String, String> configOverrides = Map.of("some-service.base-url", newUrl);
return new DevServicesResultBuildItem.RunningDevService(FEATURE, container.getContainerId(),
container::close, configOverrides)
.toBuildItem();
}
----

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`.
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.
For example,

[source%nowrap,java]
----
GenericContainer container = new GenericContainer<>(dockerImageName)
.withExposedPorts(SERVICE_PORT, OTHER_SERVICE_PORT);
----

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?

Check warning on line 59 in docs/src/main/asciidoc/extension-writing-dev-service.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Testcontainers'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Testcontainers'?", "location": {"path": "docs/src/main/asciidoc/extension-writing-dev-service.adoc", "range": {"start": {"line": 59, "column": 1}}}, "severity": "WARNING"}

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

[source%nowrap,java]
----
container.start();
Map<String, String> configOverrides = Map.of("some-service.base-url",
"http://" + container.getHost() + ":" + container.getMappedPort(SERVICE_PORT));
----

Other configuration overrides may be included in the same map.

Check warning on line 72 in docs/src/main/asciidoc/extension-writing-dev-service.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/extension-writing-dev-service.adoc", "range": {"start": {"line": 72, "column": 31}}}, "severity": "WARNING"}

== Waiting for the container to start

You should add a `.waitingFor` call to the container construction, to wait for the container to start. For example

[source%nowrap,java]
----
.waitingFor(Wait.forLogMessage(".*" + "Started" + ".*", 1))
----

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.

== Configuring the dev service

To configure the dev service launch process, your build step can accept a `ConfigPhase.BUILD_TIME` config class in its constructor.

Check warning on line 87 in docs/src/main/asciidoc/extension-writing-dev-service.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'start' or 'open' rather than 'launch' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'start' or 'open' rather than 'launch' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/extension-writing-dev-service.adoc", "range": {"start": {"line": 87, "column": 30}}}, "severity": "WARNING"}
For example,

[source%nowrap,java]
----
@BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) {
public DevServicesResultBuildItem createContainer(MyConfig config) {
----

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

Check warning on line 96 in docs/src/main/asciidoc/extension-writing-dev-service.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/extension-writing-dev-service.adoc", "range": {"start": {"line": 96, "column": 5}}}, "severity": "WARNING"}

Check warning on line 96 in docs/src/main/asciidoc/extension-writing-dev-service.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'want' rather than 'wish' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'want' rather than 'wish' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/extension-writing-dev-service.adoc", "range": {"start": {"line": 96, "column": 9}}}, "severity": "WARNING"}

[source%nowrap,java]
----
if (config.port.isPresent()) {
container.setPortBindings(List.of(config.port.get() + ":" + SERVICE_PORT));
}
----

== Controlling re-use

In dev mode, with hot reload, Quarkus may restart frequently. By default, this will also restart test containers.

Check warning on line 107 in docs/src/main/asciidoc/extension-writing-dev-service.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/extension-writing-dev-service.adoc", "range": {"start": {"line": 107, "column": 39}}}, "severity": "WARNING"}
Quarkus restarts are usually very fast, but containers may take much longer to restart.

Check warning on line 108 in docs/src/main/asciidoc/extension-writing-dev-service.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/extension-writing-dev-service.adoc", "range": {"start": {"line": 108, "column": 56}}}, "severity": "WARNING"}
To prevent containers restarting on every code change, you can mark the container as reusable:

Check warning on line 109 in docs/src/main/asciidoc/extension-writing-dev-service.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/extension-writing-dev-service.adoc", "range": {"start": {"line": 109, "column": 83}}}, "severity": "INFO"}

[source%nowrap,java]
----
.withReuse(true)
----

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

Check warning on line 117 in docs/src/main/asciidoc/extension-writing-dev-service.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/extension-writing-dev-service.adoc", "range": {"start": {"line": 117, "column": 5}}}, "severity": "WARNING"}


== References

- xref:dev-services.adoc[Dev services overview]
- xref:writing-extensions.adoc[Guide to writing extensions]
6 changes: 6 additions & 0 deletions docs/src/main/asciidoc/writing-extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1978,6 +1978,12 @@ in your runtime module, and add a `META-INF/services/io.quarkus.dev.spi.HotRepla
On startup the `setupHotDeployment` method will be called, and you can use the provided `io.quarkus.dev.spi.HotReplacementContext`

Check warning on line 1978 in docs/src/main/asciidoc/writing-extensions.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.CaseSensitiveTerms] Use 'Dev Services' rather than 'Dev services'. Raw Output: {"message": "[Quarkus.CaseSensitiveTerms] Use 'Dev Services' rather than 'Dev services'.", "location": {"path": "docs/src/main/asciidoc/writing-extensions.adoc", "range": {"start": {"line": 1978, "column": 15}}}, "severity": "INFO"}
to initiate a scan for changed files.

==== Dev services

Where extensions use an external service, adding dev service can improve the user experience in development and test modes.
See xref:extension-writing-dev-service.adoc[how to write a dev service] for more details.


=== Testing Extensions

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

0 comments on commit 789d480

Please sign in to comment.