Migrate docs to Antora infrastructure (#460)

Resolves #385
This commit is contained in:
Chris Bono
2023-09-25 00:29:05 -05:00
committed by GitHub
parent 70eeb4aac6
commit 758153d140
45 changed files with 314 additions and 427 deletions

View File

@@ -0,0 +1,27 @@
name: Deploy Docs
on:
push:
branches-ignore: [ gh-pages ]
tags: '**'
workflow_dispatch:
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: docs-build
fetch-depth: 1
- name: Dispatch (partial build)
if: github.ref_type == 'branch'
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run deploy-docs-antora.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
- name: Dispatch (full build)
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run deploy-docs-antora.yml -r $(git rev-parse --abbrev-ref HEAD)

View File

@@ -6,7 +6,7 @@ Spring for Apache Pulsar provides a basic Spring-friendly API for developing htt
**#TIP#**: Most of the ideas in this project are borrowed from the Spring for Apache Kafka project, thus a familiarity with it would help.
include::spring-pulsar-docs/src/main/asciidoc/project-state.adoc[leveloffset=+1]
include::spring-pulsar-docs/src/main/antora/modules/ROOT/pages/intro/project-state.adoc[leveloffset=+1]
== Getting Started
The {docs}/html/[reference documentation] includes a {docs}/html/#quick-tourhtml[quick tour] section.

View File

@@ -35,6 +35,8 @@ nohttp {
source.exclude "**/out/**"
source.exclude "**/target/**"
source.exclude "**/*.dylib"
source.exclude "**/*.gif"
source.exclude "**/.gradle/**"
}
check {

41
gradle/antora-docs.gradle Normal file
View File

@@ -0,0 +1,41 @@
node {
version = '16.16.0'
}
antora {
version = '3.2.0-alpha.2'
playbook = file('src/main/antora/antora-playbook.yml')
options = ['to-dir' : project.layout.buildDirectory.dir('site').get().toString(), clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
dependencies = [
'@antora/atlas-extension': '1.0.0-alpha.1',
'@antora/collector-extension': '1.0.0-alpha.3',
'@asciidoctor/tabs': '1.0.0-beta.3',
'@springio/antora-extensions': '1.4.2',
'@springio/asciidoctor-extensions': '1.0.0-alpha.8',
]
}
tasks.named("generateAntoraYml") {
asciidocAttributes = project.provider( { generateAttributes() } )
baseAntoraYmlFile = file('src/main/antora/antora.yml')
}
tasks.create(name: 'createAntoraPartials', type: Sync) {
from { project(":spring-pulsar-docs").tasks.generateObservabilityDocs.outputs }
into layout.buildDirectory.dir('generated-antora-resources/modules/ROOT/partials')
}
tasks.create('generateAntoraResources') {
dependsOn 'createAntoraPartials'
dependsOn 'generateAntoraYml'
}
def generateAttributes() {
return ['spring-boot-version': project.springBootVersion ?: 'current',
'spring-framework-version': project.springFrameworkVersion ?: 'current',
'spring-cloud-stream-version': project.springCloudStreamVersion ?: 'current',
'spring-pulsar-version': project.version,
'pulsar-client-version': project.pulsarClientVersion ?: 'current',
'pulsar-client-reactive-version': project.pulsarClientReactiveVersion ?: 'current',
'is-snapshot-version': project.version.endsWith("-SNAPSHOT")]
}

View File

@@ -1,8 +1,11 @@
plugins {
id 'org.springframework.pulsar.spring-docs-module'
id 'org.asciidoctor.jvm.convert'
id 'org.springframework.pulsar.spring-module'
id 'org.antora' version '1.0.0'
id 'io.spring.antora.generate-antora-yml' version '0.0.1'
}
apply from: "${rootDir}/gradle/antora-docs.gradle"
description = 'Spring for Apache Pulsar Docs'
ext {
@@ -77,96 +80,17 @@ task generateObservabilityDocs(type: JavaExec) {
args observationsInputDir, /.+/, observationsOutputDir
}
tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) {
asciidoctorj {
fatalWarnings = ['^((?!successfully validated).)*$']
}
forkOptions {
jvmArgs "--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED"
}
doFirst {
attributes "spring-boot-version": project.springBootVersion ?: 'current',
"spring-framework-version": project.springFrameworkVersion ?: 'current',
"spring-cloud-stream-version": project.springCloudStreamVersion ?: 'current',
"spring-pulsar-version": project.version,
"pulsar-client-version": project.pulsarClientVersion ?: 'current',
"pulsar-client-reactive-version": project.pulsarClientReactiveVersion ?: 'current',
"is-snapshot-version": project.version.endsWith("-SNAPSHOT")
}
}
asciidoctor {
sources {
include "*.singleadoc"
}
}
task asciidoctorPdf(type: org.asciidoctor.gradle.jvm.AsciidoctorTask) {
sources {
include "*.adoc"
}
}
task asciidoctorMultipage(type: org.asciidoctor.gradle.jvm.AsciidoctorTask) {
sources {
include "*.adoc"
}
}
syncDocumentationSourceForAsciidoctor {
dependsOn generateObservabilityDocs
from("${buildDir}/docs/generated") {
into "asciidoc"
}
from("src/main/java") {
into "main/java"
}
}
syncDocumentationSourceForAsciidoctorMultipage {
dependsOn generateObservabilityDocs
from("${buildDir}/docs/generated") {
into "asciidoc"
}
from("src/main/java") {
into "main/java"
}
}
syncDocumentationSourceForAsciidoctorPdf {
dependsOn generateObservabilityDocs
from("${buildDir}/docs/generated") {
into "asciidoc"
}
from("src/main/java") {
into "main/java"
}
}
task asciidocs {
dependsOn asciidoctor, asciidoctorMultipage, asciidoctorPdf
}
task docsZip(type: Zip) {
dependsOn asciidocs
task docsZip(type: Zip, dependsOn: [':spring-pulsar-docs:antora']) {
group = 'Distribution'
archiveClassifier = 'docs'
description = "Builds docs archive containing api and reference for deployment at static.spring.io/spring-pulsar/docs."
duplicatesStrategy "fail"
from(asciidoctor.outputDir) {
into "reference/htmlsingle"
}
from(asciidoctorPdf.outputDir) {
into "reference/pdf"
include "index.pdf"
rename { "spring-pulsar-reference.pdf" }
}
from(asciidoctorMultipage.outputDir) {
into "reference/html"
}
from(aggregatedJavadoc) {
into "api"
}
from ('build/site') {
into 'reference/'
}
}
artifacts {

View File

@@ -0,0 +1,46 @@
antora:
extensions:
- '@springio/antora-extensions/partial-build-extension'
- require: '@springio/antora-extensions/latest-version-extension'
- require: '@springio/antora-extensions/inject-collector-cache-config-extension'
- '@antora/collector-extension'
- '@antora/atlas-extension'
- require: '@springio/antora-extensions/root-component-extension'
root_component_name: 'pulsar'
site:
title: Spring Pulsar
url: https://docs.spring.io/spring-pulsar/reference/
content:
sources:
- url: ./../../../../
branches: HEAD
start_path: spring-pulsar-docs/src/main/antora
worktrees: true
asciidoc:
attributes:
page-stackoverflow-url: https://stackoverflow.com/tags/spring-pulsar
page-pagination: ''
hide-uri-scheme: '@'
tabs-sync-option: '@'
chomp: 'all'
listener-class: PulsarListener@
template-class: PulsarTemplate@
extensions:
- '@asciidoctor/tabs'
- '@springio/asciidoctor-extensions'
sourcemap: true
urls:
latest_version_segment: ''
runtime:
log:
failure_level: warn
format: pretty
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.3.5/ui-bundle.zip

View File

@@ -0,0 +1,12 @@
name: pulsar
version: true
title: Spring for Apache Pulsar
nav:
- modules/ROOT/nav.adoc
ext:
collector:
run:
command: gradlew :spring-pulsar-docs:generateAntoraResources
local: true
scan:
dir: spring-pulsar-docs/build/generated-antora-resources

View File

@@ -0,0 +1,19 @@
* xref:index.adoc[Overview]
* xref:intro.adoc[Introduction]
** xref:intro/project-state.adoc[Project Status]
** xref:intro/system-requirements.adoc[System Requirements]
** xref:intro/building.adoc[Building the Project]
** xref:intro/getting-help.adoc[Getting Help]
* xref:reference/reference.adoc[]
** xref:reference/pulsar.adoc[]
** xref:reference/reactive-pulsar.adoc[]
** xref:reference/pulsar-admin.adoc[]
** xref:reference/pulsar-function.adoc[]
** xref:reference/observability.adoc[]
** xref:reference/pulsar-binder.adoc[]
* xref:other-resources.adoc[]
* Appendices
** xref:appendix/version-compatibility.adoc[]
** xref:appendix/override-boot-dependencies.adoc[]
** xref:appendix/non-ga-versions.adoc[]
** xref:appendix/native-image.adoc[]

View File

@@ -1,8 +1,7 @@
[appendix]
[[appendix.native-images]]
[[native-images]]
= GraalVM Native Image Support
include::attributes.adoc[]
include::../attributes/attributes.adoc[]
https://www.graalvm.org/native-image/[GraalVM Native Images] are standalone executables that can be generated by processing compiled Java applications ahead-of-time. Native Images generally have a smaller memory footprint and start faster than their JVM counterparts.

View File

@@ -1,13 +1,15 @@
[appendix]
[[appendix.non-ga-versions]]
[[non-ga-versions]]
= Non-GA Versions
include::attributes.adoc[]
include::../attributes/attributes.adoc[]
You can find snapshot or milestone versions of the dependencies in the following repositories:
[tabs]
======
Maven::
+
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<repositories>
<repository>
@@ -37,8 +39,9 @@ You can find snapshot or milestone versions of the dependencies in the following
</repositories>
----
Gradle::
+
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
repositories {
maven {
@@ -55,3 +58,4 @@ repositories {
}
}
----
======

View File

@@ -1,5 +1,4 @@
[appendix]
[[appendix.override-boot-deps]]
[[override-boot-deps]]
= Override Spring Boot Dependencies
When using Spring for Apache Pulsar in a Spring Boot application, the Apache Pulsar dependency versions are determined by Spring Boot's dependency management.
@@ -9,7 +8,6 @@ Or, to use a different Spring for Apache Pulsar version with a supported Spring
In the following example, snapshot version of the Pulsar clients and Spring for Apache Pulsar are being used.
====
[source, groovy, subs="+attributes", role="secondary"]
.Gradle
----
@@ -22,8 +20,11 @@ dependencies {
}
----
[tabs]
======
Maven::
+
[source, xml, subs="+attributes", role="primary"]
.Maven
----
<properties>
<pulsar.version>3.1.1-SNAPSHOT</pulsar.version>
@@ -36,5 +37,5 @@ dependencies {
<artifactId>spring-boot-starter-pulsar-reactive</artifactId>
</dependency>
----
======
====

View File

@@ -1,8 +1,7 @@
[appendix]
[[appendix.version-compatibility]]
[[version-compatibility]]
= Pulsar Clients and Spring Boot Compatibility
include::attributes.adoc[]
include::../attributes/attributes.adoc[]
The following is the compatibility matrix:
|===

View File

@@ -0,0 +1,12 @@
[[overview]]
= Overview
include::attributes/attributes.adoc[]
include::overview/authors.adoc[]
(v{spring-pulsar-version})
include::overview/copyright.adoc[]
ifeval::[{is-snapshot-version} == true]
CAUTION: You are viewing documentation for a SNAPSHOT version ({spring-pulsar-version}). While it is usually in-sync with the underlying code, it is subject to change and not guaranteed to be up-to-date with the underlying code.
endif::[]

View File

@@ -0,0 +1,7 @@
[[introduction]]
= Introduction
This project provides a basic Spring-friendly API for developing https://pulsar.apache.org/[Apache Pulsar] applications.
On a very high level, Spring for Apache Pulsar provides a `PulsarTemplate` for publishing to a Pulsar topic and a `PulsarListener` annotation for consuming from a Pulsar topic.
In addition, it also provides various convenience APIs for Spring developers to ramp up their development journey into Apache Pulsar.

View File

@@ -0,0 +1,25 @@
[[building-project]]
== Building the Project
include::../attributes/attributes-variables.adoc[]
If you have cloned the project locally, follow these steps to build the project from the source code.
NOTE: Gradle `7.x (7.4 or above)` is required to build.
Run the following command to do a full build of the project:
[indent=0]
----
./gradlew clean build
----
You can build without running tests by using the following command:
[indent=0]
----
./gradlew clean build -x test
----
You can build the reference documentation using this command:
----
./gradlew :spring-pulsar-docs:antora
----
You can view the generated HTML in `_docs/build/site_` directory.

View File

@@ -1,6 +1,7 @@
[[getting-help]]
= Getting Help
include::attributes.adoc[]
include::../attributes/attributes-variables.adoc[]
If you have trouble with Spring for Apache Pulsar, we would like to help.

View File

@@ -1,4 +1,6 @@
[[project-status]]
= Project Status
While the code and functionality of the framework is thoroughly tested and ready for production, the project is in a bit of flux while transitioning from experimental GA (`0.2.x`) to GA (`1.0.0`).
The evolution of the project (by version) is as follows:

View File

@@ -0,0 +1,12 @@
[[system-requirements]]
= System Requirements
include::../attributes/attributes-variables.adoc[]
Spring for Apache Pulsar `{spring-pulsar-version}` requires the following:
- https://www.java.com[Java 17] and is compatible up to and including Java 20
- {spring-framework-docs}[Spring Framework `{spring-framework-version}`] or above
- {apache-pulsar-cient-docs}/[Apache Pulsar Java Client `{pulsar-client-version}`] or above
The version compatibility matrix (including Spring Boot) can be found in the xref:appendix/version-compatibility.adoc#version-compatibility[appendix].

View File

@@ -0,0 +1,12 @@
[[other-resources]]
= Other Resources
include::attributes/attributes-variables.adoc[]
In addition to this reference documentation, we recommend a number of other resources that may help you learn about Spring and Apache Pulsar.
- {github}[Spring for Apache Pulsar GitHub Repository]
- https://pulsar.apache.org/[Apache Pulsar Project Home Page]
- {apache-pulsar-docs}/client-libraries-java/[Apache Pulsar Java Client]
- https://github.com/apache/pulsar[Apache Pulsar GitHub Repository]
- https://github.com/apache/pulsar-client-reactive[Apache Pulsar Reactive Client GitHub Repository]

View File

@@ -1,5 +1,5 @@
include::attributes.adoc[]
include::../attributes/attributes.adoc[]
To connect to a Pulsar cluster that requires authentication, you need to specify which authentication plugin to use and any parameters required by the specified plugin.
When **using Spring Boot** auto-configuration, you can set the plugin and the plugin parameters via configuration properties (in most cases).
@@ -21,7 +21,6 @@ The following listings show how to configure each of the supported authenticatio
[[Athenz]]
.[.underline]#Click ##here## for **Athenz**#
[%collapsible]
====
[source, yaml]
----
spring:
@@ -36,13 +35,11 @@ spring:
privateKey: ...
keyId: ...
----
NOTE: This also requires TLS encryption as described in <<pulsar.adoc#tls-encryption>>.
====
NOTE: This also requires xref:reference/pulsar.adoc#tls-encryption[TLS encryption].
[[Token]]
.[.underline]#Click ##here## for **Token**#
[%collapsible]
====
[source, yaml]
----
spring:
@@ -53,12 +50,10 @@ spring:
param:
token: some-token-goes-here
----
====
[[Basic]]
.[.underline]#Click ##here## for **Basic**#
[%collapsible]
====
[source, yaml]
----
spring:
@@ -70,12 +65,10 @@ spring:
userId: ...
password: ...
----
====
[[OAuth2]]
.[.underline]#Click ##here## for **OAuth2**#
[%collapsible]
====
[source, yaml]
----
spring:
@@ -89,12 +82,10 @@ spring:
audience: ...
scope: ...
----
====
[[Sasl]]
.[.underline]#Click ##here## for **Sasl**#
[%collapsible]
====
[source, yaml]
----
spring:
@@ -106,13 +97,12 @@ spring:
saslJaasClientSectionName: ...
serverType: ...
----
====
[[mTlS-pem]]
.[.underline]#Click ##here## for **mTLS (PEM)**#
[%collapsible]
====
NOTE: Because this option requires TLS encryption, which already requires you to <<pulsar.adoc#tls-encryption,provide a client builder customizer>>, it is recommended to simply add the authentication directly on the client builder in your provided TLS customizer.
NOTE: Because this option requires TLS encryption, which already requires you to xref:reference/pulsar.adoc#tls-encryption[provide a client builder customizer], it is recommended to simply add the authentication directly on the client builder in your provided TLS customizer.
You can use the `org.apache.pulsar.client.api.AuthenticationFactory` to help create the authentication object as follows:
[source,java]
----
@@ -125,7 +115,7 @@ See the official Pulsar documentation on {apache-pulsar-docs}/security-tls-authe
.[.underline]#Click ##here## for **mTLS (JKS)**#
[%collapsible]
====
NOTE: Because this option requires TLS encryption, which already requires you to <<pulsar.adoc#tls-encryption,provide a client builder customizer>>, it is recommended to simply add the authentication directly on the client builder in your provided TLS customizer.
NOTE: Because this option requires TLS encryption, which already requires you to xref:reference/pulsar.adoc#tls-encryption[provide a client builder customizer], it is recommended to simply add the authentication directly on the client builder in your provided TLS customizer.
You can use the `org.apache.pulsar.client.api.AuthenticationFactory` to help create the authentication object as follows:
[source,java]
----

View File

@@ -19,9 +19,9 @@ To add other tags to timers and traces, configure a custom `PulsarTemplateObserv
TIP: You can subclass either `DefaultPulsarTemplateObservationConvention` or `DefaultPulsarListenerObservationConvention` or provide completely new implementations.
include::observation/_metrics.adoc[]
include::partial$_metrics.adoc[]
include::observation/_spans.adoc[]
include::partial$_spans.adoc[]
See https://micrometer.io/docs/tracing[Micrometer Tracing] for more information.
@@ -39,8 +39,11 @@ The following example shows the steps to configure your Spring Boot application
. Add the required dependencies to your application (in Maven or Gradle, respectively):
+
[tabs]
======
Maven::
+
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencies>
<dependency>
@@ -61,6 +64,7 @@ The following example shows the steps to configure your Spring Boot application
</dependency>
</dependencies>
----
======
+
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
@@ -74,9 +78,7 @@ dependencies {
----
+
NOTE
====
You need the `'io.zipkin.reporter2:zipkin-sender-urlconnection'` dependency only if your application does not have a configured WebClient or RestTemplate.
====
. Add the required properties to your application:
+
[source,yaml,indent=0,subs="verbatim"]

View File

@@ -1,6 +1,6 @@
[[pulsar-admin]]
= Pulsar Administration
include::attributes.adoc[]
include::../attributes/attributes.adoc[]
[[pulsar-admin-client]]
== Pulsar Admin Client
@@ -18,7 +18,7 @@ See the {spring-boot-pulsar-config-props}[`spring.pulsar.admin.*`] application p
[[pulsar-admin-authentication]]
=== Authentication
When accessing a Pulsar cluster that requires authentication, the admin client requires the same security configuration as the regular Pulsar client.
You can use the aforementioned <<pulsar.adoc#client-authentication,security configuration>> by replacing `spring.pulsar.client` with `spring.pulsar.admin`.
You can use the aforementioned xref:reference/pulsar.adoc#client-authentication[security configuration] by replacing `spring.pulsar.client` with `spring.pulsar.admin`.
[[pulsar-auto-topic-creation]]
== Automatic Topic Creation
@@ -28,7 +28,6 @@ For all such beans, the `PulsarAdministration` either creates the corresponding
The following example shows how to add `PulsarTopic` beans to let the `PulsarAdministration` auto-create topics for you:
====
[source,java]
----
@Bean
@@ -43,4 +42,3 @@ PulsarTopic partitionedTopic {
return PulsarTopic.builder("persistent://my-tenant/my-namespace/partitioned-topic", 3).build();
}
----
====

View File

@@ -1,6 +1,6 @@
[[pulsar-binder]]
= Spring Cloud Stream Binder for Apache Pulsar
include::attributes.adoc[]
include::../attributes/attributes.adoc[]
Spring for Apache Pulsar provides a binder for Spring Cloud Stream that we can use to build event-driven microservices using pub-sub paradigms.
In this section, we will go through the basic details of this binder.
@@ -11,8 +11,11 @@ TIP: For those unfamiliar with the concepts in Spring Cloud Stream, go through t
We need to include the following dependency on your application to use Apache Pulsar binder for Spring Cloud Stream.
[tabs]
======
Maven::
+
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencies>
<dependency>
@@ -23,13 +26,15 @@ We need to include the following dependency on your application to use Apache Pu
</dependencies>
----
Gradle::
+
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
dependencies {
implementation 'org.springframework.cloud:spring-cloud-stream-binder-pulsar:{spring-cloud-stream-version}'
}
----
======
== Overview
@@ -40,7 +45,6 @@ Applications can start from a middleware-neutral manner and then map Pulsar topi
Spring Cloud Stream is built on top of Spring Boot, and when writing an event-driven microservice using Spring Cloud Stream, you are essentially writing a Boot application.
Here is a straightforward Spring Cloud Stream application.
====
[source, java]
----
@SpringBootApplication
@@ -80,7 +84,6 @@ public class SpringPulsarBinderSampleApp {
}
----
====
The above sample application, a full-blown Spring Boot application, deserves a few explanations. However, on a first pass, you can see that this is just plain Java and a few Spring and Spring Boot annotations.
We have three `Bean` methods here - a `java.util.function.Supplier`, a `java.util.function.Function`, and finally, a `java.util.function.Consumer`.
@@ -191,7 +194,7 @@ This information is provided as extended binding properties.
As you can see above in the configuration, the properties are - `spring.cloud.stream.pulsar.bindings.<binding-name>.producer|consumer.schema-type` for schema information and `spring.cloud.stream.pulsar.bindings.<binding-name>.producer|consumer.message-type` for the actual target type.
If you have both keys and values on the message, you can use `message-key-type` and `message-value-type` to specify their target types.
TIP: Any configured <<pulsar.adoc#schema-info-template-imperative,custom schema mappings>> will be consulted when the `schema-type` property is omitted.
TIP: Any configured xref:reference/pulsar.adoc#schema-info-template-imperative[custom schema mappings] will be consulted when the `schema-type` property is omitted.
=== Message Header Conversion
Each message typically has header information that needs to be carried along as the message traverses between Pulsar and Spring Messaging via Spring Cloud Stream input and output bindings.

View File

@@ -1,6 +1,6 @@
[[pulsar-function]]
= Pulsar Functions
include::attributes.adoc[]
include::../attributes/attributes.adoc[]
Spring for Apache Pulsar provides basic suppport for {apache-pulsar-io-docs}/[Pulsar IO] (connectors) and {apache-pulsar-function-docs}[Pulsar Functions] which allow users to define stream processing pipelines made up of `sources`, `processors`, and `sinks`.
The `sources` and `sinks` are modeled by _Pulsar IO (connectors)_ and the `processors` are represented by _Pulsar Functions_.
@@ -24,7 +24,7 @@ The framework provides the `PulsarFunctionAdministration` component to manage Pu
When you use the Pulsar Spring Boot starter, you get the `PulsarFunctionAdministration` auto-configured.
By default, the application tries to connect to a local Pulsar instance at `http://localhost:8080`.
However, because it leverages the already configured `PulsarAdministration`, see <<pulsar-admin.adoc#pulsar-admin-client,Pulsar Admin Client>> for available client options (including authentication).
However, because it leverages the already configured `PulsarAdministration`, see xref:reference/pulsar-admin.adoc#pulsar-admin-client[Pulsar Admin Client] for available client options (including authentication).
Additional configuration options are available with the {spring-boot-pulsar-config-props}[`spring.pulsar.function.*`] application properties.
== Automatic Function Management
@@ -99,7 +99,6 @@ The jar file can be uploaded to the Pulsar package manager and then referenced v
Here are some examples that show how to configure a `PulsarSource` bean which results in the `PulsarFunctionAdministration` auto-creating the backing Pulsar source connector.
.PulsarSource using built-in Rabbit connector
====
[source,java]
----
@Bean
@@ -122,12 +121,10 @@ PulsarSource rabbitSource() {
return new PulsarSource(sourceConfig, null);
}
----
====
This next example is the same as the previous, except that it uses the Spring Boot auto-configured `RabbitProperties` to ease the configuration burden. This of course requires the application to be using Spring Boot with Rabbit auto-configuration enabled.
.PulsarSource using built-in Rabbit connector and Spring Boot RabbitProperties
====
[source,java]
----
@Bean
@@ -150,6 +147,5 @@ PulsarSource rabbitSourceWithBootProps(RabbitProperties props) {
return new PulsarSource(sourceConfig, null);
}
----
====
TIP: For a more elaborate example see the link:{github}/blob/main/spring-pulsar-sample-apps/sample-pulsar-functions/README.adoc[Sample Stream Pipeline with Pulsar Functions] sample app

View File

@@ -1,5 +1,5 @@
include::attributes-variables.adoc[]
include::../attributes/attributes-variables.adoc[]
=== Pulsar Headers
Pulsar does not have a first-class "`header`" concept but instead provides a map for custom user properties as well as methods to access the message metadata typically stored in a message header (eg. `id` and `event-time`).
@@ -13,7 +13,6 @@ Spring Messaging provides first-class "`header`" support via its `MessageHeaders
The `PulsarHeaderMapper` strategy is provided to map headers to and from Pulsar user properties and Spring `MessageHeaders`.
Its interface definition is as follows:
====
[source, java]
----
public interface PulsarHeaderMapper {
@@ -23,7 +22,6 @@ public interface PulsarHeaderMapper {
MessageHeaders toSpringHeaders(Message<?> pulsarMessage);
}
----
====
The framework provides a couple of mapper implementations.

View File

@@ -1,6 +1,6 @@
[[pulsar]]
= Using Spring for Apache Pulsar
include::attributes.adoc[]
include::../attributes/attributes.adoc[]
== Preface
@@ -58,14 +58,12 @@ The template provides a {javadocs}/org/springframework/pulsar/core/PulsarOperati
==== Message customization
You can specify a `TypedMessageBuilderCustomizer` to configure the outgoing message. For example, the following code shows how to send a keyed message:
====
[source, java]
----
template.newMessage(msg)
.withMessageCustomizer((mb) -> mb.key("foo-msg-key"))
.send();
----
====
==== Producer customization
You can specify a `ProducerBuilderCustomizer` to configure the underlying Pulsar producer builder that ultimately constructs the producer used to send the outgoing message.
@@ -73,37 +71,31 @@ You can specify a `ProducerBuilderCustomizer` to configure the underlying Pulsar
WARNING: Use with caution as this gives full access to the producer builder and invoking some of its methods (such as `create`) may have unintended side effects.
For example, the following code shows how to disable batching and enable chunking:
====
[source, java]
----
template.newMessage(msg)
.withProducerCustomizer((pb) -> pb.enableChunking(true).enableBatching(false))
.send();
----
====
This other example shows how to use custom routing when publishing records to partitioned topics.
Specify your custom `MessageRouter` implementation on the `Producer` builder such as:
====
[source, java]
----
template.newMessage(msg)
.withProducerCustomizer((pb) -> pb.messageRouter(messageRouter))
.send();
----
====
TIP: Note that, when using a `MessageRouter`, the only valid setting for `spring.pulsar.producer.message-routing-mode` is `custom`.
This other example shows how to add a `ProducerInterceptor` that will intercept and mutate messages received by the producer before being published to the brokers:
====
[source, java]
----
template.newMessage(msg)
.withProducerCustomizer((pb) -> pb.intercept(interceptor))
.send();
----
====
[[schema-info-template-imperative]]
:template-class: PulsarTemplate
@@ -131,7 +123,6 @@ If you use Spring Boot auto-configuration, you can specify the interceptors as B
They are passed automatically to the `PulsarTemplate`.
Ordering of the interceptors is achieved by using the `@Order` annotation as follows:
====
[source, java]
----
@Bean
@@ -146,7 +137,6 @@ ProducerInterceptor secondInterceptor() {
...
}
----
====
NOTE: If you are not using the starter, you will need to configure and register the aforementioned components yourself.
@@ -165,7 +155,6 @@ Spring Boot auto-configuration also provides this consumer factory which you can
Let us revisit the `PulsarListener` code snippet we saw in the quick-tour section:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar")
@@ -173,11 +162,9 @@ public void listen(String message) {
System.out.println("Message Received: " + message);
}
----
====
You can further simplify this method:
====
[source, java]
----
@PulsarListener
@@ -185,7 +172,6 @@ public void listen(String message) {
System.out.println("Message Received: " + message);
}
----
====
In this most basic form, you must provide the following two properties with their corresponding values:
@@ -208,7 +194,6 @@ For any complex types (such as JSON, AVRO, and others), the framework cannot do
The following example shows another `PulsarListener` method, which takes an `Integer`:
====
[source, java]
----
@PulsarListener(subscriptionName = "my-subscription-1", topics = "my-topic-1")
@@ -216,11 +201,9 @@ public void listen(Integer message) {
System.out.println(message);
}
----
====
The following `PulsarListener` method shows how we can consume complex types from a topic:
====
[source, java]
----
@PulsarListener(subscriptionName = "my-subscription-2", topics = "my-topic-2", schemaType = SchemaType.JSON)
@@ -228,7 +211,6 @@ public void listen(Foo message) {
System.out.println(message);
}
----
====
Note the addition of a `schemaType` property on `PulsarListener`.
That is because the library is not capable of inferring the schema type from the provided type: `Foo`. We must tell the framework what schema to use.
@@ -237,7 +219,6 @@ Let us look at a few more ways.
You can consume the Pulsar message directly:
====
[source, java]
----
@PulsarListener(subscriptionName = "my-subscription", topics = "my-topic")
@@ -245,11 +226,9 @@ public void listen(org.apache.pulsar.client.api.Message<String> message) {
System.out.println(message.getValue());
}
----
====
The following example consumes the record by using the Spring messaging envelope:
====
[source, java]
----
@PulsarListener(subscriptionName = "my-subscription", topics = "my-topic")
@@ -257,12 +236,10 @@ public void listen(org.springframework.messaging.Message<String> message) {
System.out.println(message.getPayload());
}
----
====
Now let us see how we can consume records in batches.
The following example uses `PulsarListener` to consume records in batches as POJOs:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-batch-subscription", topics = "hello-batch", schemaType = SchemaType.JSON, batch = true)
@@ -271,7 +248,6 @@ public void listen(List<Foo> messages) {
messages.forEach((message) -> System.out.println("record : " + message));
}
----
====
Note that, in this example, we receive the records as a collection (`List`) of objects.
In addition, to enable batch consumption at the `PulsarListener` level, you need to set the `batch` property on the annotation to `true`.
@@ -281,7 +257,6 @@ If the `List` contains a complex type, you still need to provide the `schemaType
The following uses the `Message` envelope provided by the Pulsar Java client:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-batch-subscription", topics = "hello-batch", schemaType = SchemaType.JSON, batch = true)
@@ -290,11 +265,9 @@ public void listen(List<Message<Foo>> messages) {
messages.forEach((message) -> System.out.println("record : " + message.getValue()));
}
----
====
The following example consumes batch records with an envelope of the Spring messaging `Message` type:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-batch-subscription", topics = "hello-batch", schemaType = SchemaType.JSON, batch = true)
@@ -303,11 +276,9 @@ public void listen(List<org.springframework.messaging.Message<Foo>> messages) {
messages.forEach((message) -> System.out.println("record : " + message.getPayload()));
}
----
====
Finally, you can also use the `Messages` holder object from Pulsar for the batch listener:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-batch-subscription", topics = "hello-batch", schemaType = SchemaType.JSON, batch = true)
@@ -316,21 +287,18 @@ public void listen(org.apache.pulsar.client.api.Messages<Foo>> messages) {
messages.forEach((message) -> System.out.println("record : " + message.getValue()));
}
----
====
When you use `PulsarListener`, you can provide Pulsar consumer properties directly on the annotation itself.
This is convenient if you do not want to use the Boot configuration properties mentioned earlier or have multiple `PulsarListener` methods.
The following example uses Pulsar consumer properties directly on `PulsarListener`:
====
[source, java]
----
@PulsarListener(properties = { "subscriptionName=subscription-1", "topicNames=foo-1", "receiverQueueSize=5000" })
void listen(String message) {
}
----
====
TIP: The properties used are direct Pulsar consumer properties, not the `spring.pulsar.consumer` application configuration properties
@@ -342,7 +310,6 @@ include::schema-info/schema-info-listener.adoc[leveloffset=+1]
Sometimes, you need direct access to the Pulsar Consumer object.
The following example shows how to get it:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar")
@@ -352,7 +319,6 @@ public void listen(String message, org.apache.pulsar.client.api.Consumer<String>
...
}
----
====
CAUTION: When accessing the `Consumer` object this way, do NOT invoke any operations that would change the Consumer's cursor position by invoking any receive methods.
All such operations must be done by the container.
@@ -394,31 +360,26 @@ Before doing so, however, let us take a closer look at the container itself.
This is a single consumer-based message listener container.
The following listing shows its constructor:
====
[source, java]
----
public DefaultPulsarMessageListenerContainer(PulsarConsumerFactory<? super T> pulsarConsumerFactory,
PulsarContainerProperties pulsarContainerProperties)
}
----
====
It receives a `PulsarConsumerFactory` (which it uses to create the consumer) and a `PulsarContainerProperties` object (which contains information about the container properties).
`PulsarContainerProperties` has the following constructors:
====
[source, java]
----
public PulsarContainerProperties(String... topics)
public PulsarContainerProperties(Pattern topicPattern)
----
====
You can provide the topic information through `PulsarContainerProperties` or as a consumer property that is provided to the consumer factory.
The following example uses the `DefaultPulsarMessageListenerContainer`:
====
[source, java]
----
Map<String, Object> config = new HashMap<>();
@@ -435,7 +396,6 @@ DefaultPulsarMessageListenerContainer<String> pulsarListenerContainer = new Defa
return pulsarListenerContainer;
----
====
NOTE: If topic information is not specified when using the listener containers directly, the same <<topic-resolution-process-imperative,topic resolution process>> used by the `PulsarListener` is used with the one exception that the "Message type default" step is **omitted**.
@@ -446,13 +406,11 @@ If you want to have multiple consumers managed through multiple threads, you nee
`ConcurrentPulsarMessageListenerContainer` has the following constructor:
====
[source, java]
----
public ConcurrentPulsarMessageListenerContainer(PulsarConsumerFactory<? super T> pulsarConsumerFactory,
PulsarContainerProperties pulsarContainerProperties)
----
====
`ConcurrentPulsarMessageListenerContainer` lets you specify a `concurrency` property through a setter.
Concurrency of more than `1` is allowed only on non-exclusive subscriptions (`failover`, `shared`, and `key-shared`).
@@ -460,7 +418,6 @@ You can only have the default `1` for concurrency when you have an exclusive sub
The following example enables `concurrency` through the `PulsarListener` annotation for a `failover` subscription.
====
[source, java]
----
@PulsarListener(topics = "my-topic", subscriptionName = "subscription-1",
@@ -471,7 +428,6 @@ void listen(String message, Consumer<String> consumer) {
System.out.println("Current Consumer: " + consumer.getConsumerName());
}
----
====
In the preceding listener, it is assumed that the topic `my-topic` has three partitions.
If it is a non-partitioned topic, having concurrency set to `3` does nothing. You get two idle consumers in addition to the main active one.
@@ -482,7 +438,6 @@ NOTE: When you use the `Failover` subscription this way on partitioned topics, P
The following listing shows another example of `PulsarListener`, but with `Shared` subscription and `concurrency` enabled.
====
[source, java]
----
@PulsarListener(topics = "my-topic", subscriptionName = "subscription-1",
@@ -491,7 +446,6 @@ void listen(String message) {
...
}
----
====
In the preceding example, the `PulsarListener` creates five different consumers (this time, we assume that the topic has five partitions).
@@ -507,7 +461,6 @@ Let us take a look at how the message listener container enables both single-rec
==== Single Record Consumption
Let us revisit our basic `PulsarListener` for the sake of this discussion:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar")
@@ -515,7 +468,6 @@ public void listen(String message) {
System.out.println("Message Received: " + message);
}
----
====
With this `PulsarListener` method, we essential ask Spring for Apache Pulsar to invoke the listener method with a single record each time.
We mentioned that the message listener container consumes the data in batches using the `batchReceive` method on the consumer.
@@ -527,7 +479,6 @@ As you can see in the previous section, `PulsarRecordMessageListener` extends fr
==== Batch Consumption
The following example shows the `PulsarListener` consuming records in batches:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-batch-subscription", topics = "hello-batch", schemaType = SchemaType.JSON, batch = true)
@@ -536,7 +487,6 @@ public void listen4(List<Foo> messages) {
messages.forEach((message) -> System.out.println("record : " + message));
}
----
====
When you use this type of `PulsarListener`, the framework detects that you are in batch mode.
Since it already received the data in batches by using the Consumer's `batchReceive` method, it hands off the entire batch to the listener method through an adapter for `PulsarBatchMessageListener`.
@@ -550,7 +500,6 @@ The list of available headers can be found in {github}/blob/main/spring-pulsar/s
The following example shows how you can access the various Pulsar Headers in an application that uses the single record mode of consuming:
====
[source,java]
----
@PulsarListener(topics = "simpleListenerWithHeaders")
@@ -560,7 +509,6 @@ void simpleListenerWithHeaders(String data, @Header(PulsarHeaders.MESSAGE_ID) Me
}
----
====
In the preceding example, we access the values for the `messageId` and `rawData` message metadata as well as a custom message property named `foo`.
The Spring `@Header` annotation is used for each header field.
@@ -574,7 +522,6 @@ Note that you can also use the Spring messaging `Message` envelope to carry the
In this section, we see how to access the various Pulsar Headers in an application that uses a batch consumer:
====
[source,java]
----
@PulsarListener(topics = "simpleBatchListenerWithHeaders", batch = true)
@@ -584,7 +531,6 @@ void simpleBatchListenerWithHeaders(List<String> data,
}
----
====
In the preceding example, we consume the data as a `List<String>`.
When extracting the various headers, we do so as a `List<>` as well.
@@ -613,7 +559,6 @@ In the following sections, we see how acknowledgment works when you use both sin
Let us revisit our basic single message based `PulsarListener`:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar")
@@ -621,7 +566,6 @@ public void listen(String message) {
System.out.println("Message Received: " + message);
}
----
====
It is natural to wonder, how acknowledgment works when you use `PulsarListener`, especially if you are familiar with using Pulsar consumer directly.
The answer comes down to the message listener container, as that is the central place in Spring for Apache Pulsar that coordinates all the consumer related activities.
@@ -641,7 +585,6 @@ If the application wants the acknowledgment or negative acknowledgment to occur
In that case, after handling each record, the message is acknowledged if no error and negatively acknowledged if there was an error.
The following example enables `RECORD` ack mode on the Pulsar listener:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar", ackMode = AckMode.RECORD)
@@ -649,7 +592,6 @@ public void listen(String message) {
System.out.println("Message Received: " + message);
}
----
====
You can also set the listener property, `spring.pulsar.listner.ack-mode`, to set the ack mode application-wide.
When doing this, you need not set this on the `PulsarListener` annotation.
@@ -660,7 +602,6 @@ In that case, all the `PulsarListener` methods in the application acquire that p
You might not always want the framework to send acknowledgments but, rather, do that directly from the application itself.
Spring for Apache Pulsar provides a couple of ways to enable manual message acknowledgments. The following example shows one of them:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar", ackMode = AckMode.MANUAL)
@@ -669,7 +610,6 @@ public void listen(Message<String> message, Acknowledgment acknowledgment) {
acknowledgment.acknowledge();
}
----
====
A few things merit explanation here. First, we enablE manual ack mode by setting `ackMode` on `PulsarListener`.
When enabling manual ack mode, Spring for Apache Pulsar lets the application inject an `Acknowledgment` object.
@@ -677,7 +617,6 @@ The framework achieves this by selecting a compatible message listener container
The `Acknowledgment` object provides the following API methods:
====
[source, java]
----
void acknowledge();
@@ -690,7 +629,6 @@ void nack();
void nack(MessageId messageId);
----
====
You can inject this `Acknowledgment` object into your `PulsarListener` while using `MANUAL` ack mode and then call one of the corresponding methods.
@@ -705,7 +643,6 @@ See the nack methods shown earlier.
You can also call `acknowledge` directly on the Pulsar consumer:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar", ackMode = AckMode.MANUAL)
@@ -719,7 +656,6 @@ public void listen(Message<String> message, Consumer<String> consumer) {
}
}
----
====
When calling `acknowledge` directly on the underlying consumer, you need to do error handling by yourself.
Using the `Acknowledgment` does not require that, as the framework can do that for you.
@@ -736,7 +672,6 @@ Note that this may not be the same batch that was batched on the producer side.
Consider the following batch listener:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar", batch = true)
@@ -746,7 +681,6 @@ public void batchListen(List<Foo> messages) {
}
}
----
====
When all the messages in the incoming collection (`messages` in this example) are processed, the framework acknowledges all of them.
@@ -761,7 +695,6 @@ It is entirely up to the application to take care of such concerns.
When `MANUAL` ack mode is set, Spring for Apache Pulsar selects a compatible message listener container: `PulsarBatchAcknowledgingMessageListener` for batch consumption, which gives you access to an `Acknowledgment` object.
The following are the methods available in the `Acknowledgment` API:
====
[source, java]
----
void acknowledge();
@@ -774,12 +707,10 @@ void nack();
void nack(MessageId messageId);
----
====
You can inject this `Acknowledgment` object into your `PulsarListener` while using `MANUAL` ack mode.
The following listing shows a basic example for a batch based listener:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar")
@@ -795,7 +726,6 @@ public void listen(List<Message<String>> messgaes, Acknowlegement acknowledgment
}
}
----
====
When you use a batch listener, the message listener container cannot know which record it is currently operating upon.
Therefore, to manually acknowledge, you need to use one of the overloaded `acknowledge` method that takes a `MessageId` or a `List<MessageId>`.
@@ -814,7 +744,6 @@ If this property has a value above zero and if the Pulsar consumer does not ackn
You can also specify this property directly as a Pulsar consumer property on the `PulsarListener` itself:
====
[source, java]
----
@PulsarListener(subscriptionName = "subscription-1", topics = "topic-1"
@@ -823,13 +752,11 @@ public void listen(String s) {
...
}
----
====
When you specify `ackTimeout` (as seen in the preceding `PulsarListener` method), if the consumer does not send an acknowledgement within 60 seconds, the message is redelivered by Pulsar to the consumer.
If you want to specify some advanced backoff options for ack timeout with different delays, you can do the following:
====
[source, java]
----
@EnablePulsar
@@ -852,7 +779,6 @@ class AckTimeoutRedeliveryConfig {
}
----
====
In the preceding example, we specify a bean for Pulsar's `RedeliveryBackoff` with a minimum delay of 1 second, a maximum delay of 10 seconds, and a backoff multiplier of 2.
After the initial ack timeout occurs, the message redeliveries are controlled through this backoff bean.
@@ -864,7 +790,6 @@ When acknowledging negatively, Pulsar consumer lets you specify how the applicat
The default is to redeliver the message in one minute, but you can change it by setting `spring.pulsar.consumer.negative-ack-redelivery-delay`.
You can also set it as a consumer property directly on `PulsarListener`, as follows:
====
[source, java]
----
@PulsarListener(subscriptionName = "subscription-1", topics = "topic-1"
@@ -873,11 +798,9 @@ public void listen(String s) {
...
}
----
====
You can also specify different delays and backoff mechanisms with a multiplier by providing a `RedeliveryBackoff` bean and providing the bean name as the `negativeAckRedeliveryBackoff` property on the PulsarProducer, as follows:
====
[source, java]
----
@EnablePulsar
@@ -899,7 +822,6 @@ class NegativeAckRedeliveryConfig {
}
----
====
==== Using Dead Letter Topic from Apache Pulsar for Message Redelivery and Error Handling
@@ -908,7 +830,6 @@ For the `Exclusive` and `Failover` subscription types, this feature is not avail
The basic idea is that, if a message is retried a certain number of times (maybe due to an ack timeout or nack redelivery), once the number of retries are exhausted, the message can be sent to a special topic called the dead letter queue (DLQ).
Let us see some details around this feature in action by inspecting some code snippets:
====
[source, java]
----
@EnablePulsar
@@ -934,7 +855,6 @@ class DeadLetterPolicyConfig {
}
----
====
First, we have a special bean for `DeadLetterPolicy`, and it is named as `deadLetterPolicy` (it can be any name as you wish).
This bean specifies a number of things, such as the max delivery (10, in this case) and the name of the dead letter topic -- `my-dlq-topic`, in this case.
@@ -967,7 +887,6 @@ When you use `PulsarConsumerErrorHandler` from Spring for Apache Pulsar, make su
Let us see some details by examining a few code snippets:
====
[source, java]
----
@EnablePulsar
@@ -995,14 +914,12 @@ class PulsarConsumerErrorHandlerConfig {
}
----
====
Consider the `pulsarConsumerErrorHandler` bean.
This creates a bean of type `PulsarConsumerErrorHandler` and uses the default implementation provided out of the box by Spring for Apache Pulsar: `DefaultPulsarConsumerErrorHandler`.
`DefaultPulsarConsumerErrorHandler` has a constructor that takes a `PulsarMessageRecovererFactory` and a `org.springframework.util.backoff.Backoff`.
`PulsarMessageRecovererFactory` is a functional interface with the following API:
====
[source, java]
----
@FunctionalInterface
@@ -1018,12 +935,10 @@ public interface PulsarMessageRecovererFactory<T> {
}
----
====
The `recovererForConsumer` method takes a Pulsar consumer and returns a `PulsarMessageRecoverer`, which is another functional interface.
Here is the API of `PulsarMessageRecoverer`:
====
[source, java]
----
public interface PulsarMessageRecoverer<T> {
@@ -1037,7 +952,6 @@ public interface PulsarMessageRecoverer<T> {
}
----
====
Spring for Apache Pulsar provides an implementation for `PulsarMessageRecovererFactory` called `PulsarDeadLetterPublishingRecoverer` that provides a default implementation that can recover the message by sending it to a Dead Letter Topic (DLT).
We provide this implementation to the constructor for the preceding `DefaultPulsarConsumerErrorHandler`.
@@ -1054,7 +968,6 @@ In most cases, the same auto-configured `PulsarTemplate` from Spring Boot is suf
When using partitioned topics and using custom message routing for the main topic, you must use a different `PulsarTemplate` that does not take the auto-configured `PulsarProducerFactory` that is populated with a value of `custompartition` for `message-routing-mode`.
You can use a `PulsarConsumerErrorHandler` with the following blueprint:
====
[source, java]
----
@Bean
@@ -1072,7 +985,6 @@ PulsarConsumerErrorHandler<Integer> pulsarConsumerErrorHandler(PulsarClient puls
new FixedBackOff(100, 5));
}
----
====
Note that we are provide a destination resolver to the `PulsarDeadLetterPublishingRecoverer` as the second constructor argument.
If not provided, `PulsarDeadLetterPublishingRecoverer` uses `<subscription-name>-<topic-name>-DLT>` as the DLT topic name.
@@ -1090,7 +1002,6 @@ Next, we look at how you can use this on batch listeners.
First, let us look at a batch `PulsarListener` method:
====
[source, java]
----
@PulsarListener(subscriptionName = "batch-demo-5-sub", topics = "batch-demo-4", batch = true, concurrency = "3",
@@ -1118,7 +1029,6 @@ void dltReceiver(Message<Integer> message) {
}
----
====
Once again, we provide the `pulsarConsumerErrorHandler` property with the `PulsarConsumerErrorHandler` bean name.
When you use a batch listener (as shown in the preceding example) and want to use the `PulsarConsumerErrorHandler` from Spring for Apache Pulsar, you need to use manual acknowledgment.
@@ -1134,7 +1044,6 @@ At that point, the message is acknowledged by the container, and the listener is
Spring for Apache Pulsar provides a convenient way to customize the consumer created by the container used by the `PulsarListener`.
Applications can provide a bean for `ConsumerBuilderCustomizer`.
Here is an example.
====
[source, java]
----
@Bean
@@ -1144,11 +1053,9 @@ public ConsumerBuilderCustomizer<String> myCustomizer() {
};
}
----
====
Then this customizer bean name can be provided as an attribute on the `PuslarListener` annotation as shown below.
====
[source, java]
----
@PulsarListener(subscriptionName = "my-subscription",
@@ -1157,7 +1064,6 @@ void listen(String message) {
}
----
====
The framework detects the provided bean through the `PulsarListener` and applies this customizer on the Consumer builder before creating the Pulsar Consumer.
@@ -1172,7 +1078,6 @@ When the Pulsar message listener container is paused, any polling done by the co
Similarly, when the container is resumed, the next poll starts returning data if the topic has any new records added while paused.
To pause or resume a listener container, first obtain the container instance via the `PulsarListenerEndpointRegistry` bean and then invoke the pause/resume API on the container instance - as shown in the snippet below:
====
[source, java]
----
@Autowired
@@ -1183,7 +1088,6 @@ void someMethod() {
container.pause();
}
----
====
TIP: The id parameter passed to `getListenerContainer` is the container id - which will be the value of the `@PulsarListener` id attribute when pausing/resuming a `@PulsarListener`.
@@ -1199,7 +1103,6 @@ While it is possible to use `PulsarReaderFactory` directly, Spring for Apache Pu
This is similar to the same ideas behind `PulsarListener.`
Here is a quick example.
====
[source, java]
----
@PulsarReader(id = "pulsar-reader-demo-id", subscriptionName = "pulsar-reader-demo-subscription",
@@ -1208,7 +1111,6 @@ void read(String message) {
//...
}
----
====
As you can see from the above example, `PulsarReader` is a quick way to read from a Pulsar topic.
The `id` and `subscriptionName` attributes are optional, but always a best practice to provide them.
@@ -1223,7 +1125,6 @@ Suppose you want the reader to start reading messages arbitrarily from a topic o
You can customize any fields available through `ReaderBuilder` using a `ReaderBuilderCustomizer` in Spring for Apache Pulsar.
You can provide a `@Bean` from `ReaderBuilderCustomizer` and then make it available to the `PulsarReader` as below.
====
[source, java]
----
@PulsarReader(id = "with-customizer-reader", subscriptionName = "with-customizer-reader-subscription",
@@ -1240,7 +1141,6 @@ public ReaderBuilderCustomizer<String> myCustomizer() {
};
}
----
====
[[topic-resolution-process-imperative]]
== Topic Resolution
@@ -1251,7 +1151,6 @@ include::topic-resolution.adoc[leveloffset=+1]
In the following example, we publish to a topic called `hello-pulsar-partitioned`.
It is a topic that is partitioned, and, for this sample, we assume that the topic is already created with three partitions.
====
[source, java]
----
@SpringBootApplication
@@ -1304,7 +1203,6 @@ public class PulsarBootPartitioned {
}
----
====
In the preceding example, we publish to a partitioned topic, and we would like to publish some data segment to a specific partition.
If you leave it to Pulsar's default, it follows a round-robin mode of partition assignments, and we would like to override that.
@@ -1320,7 +1218,6 @@ This means that data from all the partitions ends up in the same consumer and th
What can we do if we want each partition to be consumed by a single distinct consumer?
We can switch to the `failover` subscription mode and add three separate consumers:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-pulsar-partitioned-subscription", topics = "hello-pulsar-partitioned", subscriptionType = SubscriptionType.Failover)
@@ -1338,7 +1235,6 @@ public void listen3(String foo) {
System.out.println("Message Received 3: " + foo);
}
----
====
When you follow this approach, a single partition always gets consumed by a dedicated consumer.
@@ -1347,7 +1243,6 @@ However, when you use the `shared` mode, you lose any ordering guarantees, as a
Consider the following example:
====
[source, java]
----
@PulsarListener(subscriptionName = "hello-pulsar-shared-subscription", topics = "hello-pulsar-partitioned", subscriptionType = SubscriptionType.Shared)
@@ -1360,4 +1255,3 @@ public void listen2(String foo) {
System.out.println("Message Received 2: " + foo);
}
----
====

View File

@@ -1,6 +1,6 @@
[[quick-tour]]
= Quick Tour
include::attributes.adoc[]
include::../attributes/attributes.adoc[]
We will take a quick tour of Spring for Apache Pulsar by showing a sample Spring Boot application that produces and consumes.
This is a complete application and does not require any additional configuration, as long as you have a Pulsar cluster running on the default location - `localhost:6650`.
@@ -8,8 +8,11 @@ This is a complete application and does not require any additional configuration
== Dependencies
Spring Boot applications need only the `spring-pulsar-spring-boot-starter` dependency. The following listings show how to define the dependency for Maven and Gradle, respectively:
[tabs]
======
Maven::
+
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencies>
<dependency>
@@ -20,20 +23,25 @@ Spring Boot applications need only the `spring-pulsar-spring-boot-starter` depen
</dependencies>
----
Gradle::
+
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-pulsar:{spring-boot-version}'
}
----
======
[CAUTION]
====
When using `Version 0.2.x` the above coordinates change as follows:
[tabs]
======
Maven::
+
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencies>
<dependency>
@@ -43,13 +51,16 @@ When using `Version 0.2.x` the above coordinates change as follows:
</dependency>
</dependencies>
----
Gradle::
+
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
dependencies {
implementation 'org.springframework.pulsar:spring-pulsar-spring-boot-starter:0.2.0'
}
----
======
====
== Application Code

View File

@@ -1,6 +1,6 @@
[[reactive-pulsar]]
= Reactive Support
include::attributes.adoc[]
include::../attributes/attributes.adoc[]
The framework provides a Reactive counterpart for almost all supported features.
@@ -56,7 +56,7 @@ See the {spring-boot-pulsar-config-props}[`spring.pulsar.client.*`] application
[[reactive-client-authentication]]
=== Authentication
To connect to a Pulsar cluster that requires authentication, follow <<pulsar.adoc#client-authentication,the same steps>> as the imperative client.
To connect to a Pulsar cluster that requires authentication, follow xref:reference/pulsar.adoc#client-authentication[the same steps] as the imperative client.
Again, this is because the reactive client adapts the imperative client which handles all security configuration.
[[reactive-message-production]]
@@ -76,14 +76,12 @@ The template provides a {javadocs}/org/springframework/pulsar/reactive/core/Reac
==== Message customization
You can specify a `MessageSpecBuilderCustomizer` to configure the outgoing message. For example, the following code shows how to send a keyed message:
====
[source, java]
----
template.newMessage(msg)
.withMessageCustomizer((mc) -> mc.key("foo-msg-key"))
.send();
----
====
==== Sender customization
You can specify a `ReactiveMessageSenderBuilderCustomizer` to configure the underlying Pulsar sender builder that ultimately constructs the sender used to send the outgoing message.
@@ -91,25 +89,21 @@ You can specify a `ReactiveMessageSenderBuilderCustomizer` to configure the unde
WARNING: Use with caution as this gives full access to the sender builder and invoking some of its methods (such as `create`) may have unintended side effects.
For example, the following code shows how to disable batching and enable chunking:
====
[source, java]
----
template.newMessage(msg)
.withSenderCustomizer((sc) -> sc.enableChunking(true).enableBatching(false))
.send();
----
====
This other example shows how to use custom routing when publishing records to partitioned topics.
Specify your custom `MessageRouter` implementation on the sender builder such as:
====
[source, java]
----
template.newMessage(msg)
.withSenderCustomizer((sc) -> sc.messageRouter(messageRouter))
.send();
----
====
TIP: Note that, when using a `MessageRouter`, the only valid setting for `spring.pulsar.reactive.sender.message-routing-mode` is `custom`.
@@ -144,7 +138,6 @@ When you use Spring Boot support, it automatically enables this annotation and c
Let us revisit the `ReactivePulsarListener` code snippet we saw in the quick-tour section:
====
[source, java]
----
@ReactivePulsarListener(subscriptionName = "hello-pulsar-sub", topics = "hello-pulsar-topic")
@@ -153,12 +146,10 @@ Mono<Void> listen(String message) {
return Mono.empty();
}
----
====
NOTE: The listener method returns a `Mono<Void>` to signal whether the message was successfully processed. `Mono.empty()` indicates success (acknowledgment) and `Mono.error()` indicates failure (negative acknowledgment).
You can also further simplify this method:
====
[source, java]
----
@ReactivePulsarListener
@@ -167,7 +158,6 @@ Mono<Void> listen(String message) {
return Mono.empty();
}
----
====
In this most basic form, you must still provide the topic name by setting the following property:
@@ -190,7 +180,6 @@ For all the primitive types in Java, the framework does this inference.
For any complex types (such as JSON, AVRO, and others), the framework cannot do this inference and the user needs to provide the schema type on the annotation using the `schemaType` property.
This example shows how we can consume complex types from a topic:
====
[source, java]
----
@ReactivePulsarListener(topics = "my-topic-2", schemaType = SchemaType.JSON)
@@ -199,7 +188,6 @@ Mono<Void> listen(Foo message) {
return Mono.empty();
}
----
====
Note the addition of a `schemaType` property on `ReactivePulsarListener`.
That is because the library is not capable of inferring the schema type from the provided type: `Foo`. We must tell the framework what schema to use.
@@ -207,7 +195,6 @@ That is because the library is not capable of inferring the schema type from the
Let us look at a few more ways we can consume.
This example consumes the Pulsar message directly:
====
[source, java]
----
@ReactivePulsarListener(topics = "my-topic")
@@ -216,10 +203,8 @@ Mono<Void> listen(org.apache.pulsar.client.api.Message<String> message) {
return Mono.empty();
}
----
====
This example consumes the record wrapped in a Spring messaging envelope:
====
[source, java]
----
@ReactivePulsarListener(topics = "my-topic")
@@ -228,7 +213,6 @@ Mono<Void> listen(org.springframework.messaging.Message<String> message) {
return Mono.empty();
}
----
====
==== Streaming
All of the above are examples of consuming a single record one-by-one.
@@ -236,7 +220,6 @@ However, one of the compelling reasons to use Reactive is for the streaming capa
The following example uses `ReactivePulsarListener` to consume a stream of POJOs:
====
[source, java]
----
@ReactivePulsarListener(topics = "streaming-1", stream = true)
@@ -245,7 +228,6 @@ Flux<MessageResult<Void>> listen(Flux<Message<String>> messages) {
.doOnNext((msg) -> System.out.println("Received: " + msg.getValue()))
.map(MessageResult::acknowledge);
----
====
Here we receive the records as a `Flux` of messages.
In addition, to enable stream consumption at the `ReactivePulsarListener` level, you need to set the `stream` property on the annotation to `true`.
@@ -255,7 +237,6 @@ Based on the actual type of the messages in the `Flux`, the framework tries to i
If it contains a complex type, you still need to provide the `schemaType` on `ReactivePulsarListener`.
The following listener uses the Spring messaging `Message` envelope with a complex type :
====
[source, java]
----
@ReactivePulsarListener(topics = "streaming-2", stream = true, schemaType = SchemaType.JSON)
@@ -265,7 +246,6 @@ Flux<MessageResult<Void>> listen2(Flux<org.springframework.messaging.Message<Foo
.map(MessageResult::acknowledge);
}
----
====
==== Configuration - Application Properties
The listener ultimately relies on `ReactivePulsarConsumerFactory` to create and manage the underlying Pulsar consumer.
@@ -281,7 +261,6 @@ WARNING: Use with caution as this gives full access to the consumer builder and
For example, the following code shows how to set the initial position of the subscription to the earliest messaage on the topic.
====
[source, java]
----
@ReactivePulsarListener(topics = "hello-pulsar-topic", consumerCustomizer = "myConsumerCustomizer")
@@ -295,14 +274,12 @@ ReactiveMessageConsumerBuilderCustomizer<String> myConsumerCustomizer() {
return b -> b.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest);
}
----
====
You can also use the customizer to provide direct Pulsar consumer properties to the consumer builder.
This is convenient if you do not want to use the Boot configuration properties mentioned earlier or have multiple `ReactivePulsarListener` methods whose configuration varies.
The following customizer example uses direct Pulsar consumer properties:
====
[source, java]
----
@Bean
@@ -310,7 +287,6 @@ ReactiveMessageConsumerBuilderCustomizer<String> directConsumerPropsCustomizer()
return b -> b.property("subscriptionName", "subscription-1").property("topicNames", "foo-1");
}
----
====
CAUTION: The properties used are direct Pulsar consumer properties, not the `spring.pulsar.reactive.consumer` Spring Boot configuration properties
@@ -368,7 +344,6 @@ The list of available headers can be found in {github}/blob/main/spring-pulsar/s
==== Accessing In OneByOne Listener
The following example shows how you can access Pulsar Headers when using a one-by-one message listener:
====
[source,java]
----
@ReactivePulsarListener(topics = "some-topic")
@@ -379,7 +354,6 @@ Mono<Void> listen(String data,
return Mono.empty();
}
----
====
In the preceding example, we access the values for the `messageId` message metadata as well as a custom message property named `foo`.
The Spring `@Header` annotation is used for each header field.
@@ -421,7 +395,6 @@ If this property has a value above zero and if the Pulsar consumer does not ackn
You can also specify this property directly as a Pulsar consumer property via a <<reactive-consumer-customizer,consumer customizer>> such as:
====
[source, java]
----
@Bean
@@ -429,7 +402,6 @@ ReactiveMessageConsumerBuilderCustomizer<String> consumerCustomizer() {
return b -> b.property("ackTimeout", "60s");
}
----
====
==== Negative Acknowledgment Redelivery Delay
When acknowledging negatively, Pulsar consumer lets you specify how the application wants the message to be re-delivered.
@@ -437,7 +409,6 @@ The default is to redeliver the message in one minute, but you can change it by
You can also set it directly as a Pulsar consumer property via a <<reactive-consumer-customizer,consumer customizer>> such as:
====
[source, java]
----
@Bean
@@ -445,7 +416,6 @@ ReactiveMessageConsumerBuilderCustomizer<String> consumerCustomizer() {
return b -> b.property("negativeAckRedeliveryDelay", "10ms");
}
----
====
==== Dead Letter Topic
Apache Pulsar lets applications use a dead letter topic on consumers with a `Shared` subscription type.
@@ -453,7 +423,6 @@ For the `Exclusive` and `Failover` subscription types, this feature is not avail
The basic idea is that, if a message is retried a certain number of times (maybe due to an ack timeout or nack redelivery), once the number of retries are exhausted, the message can be sent to a special topic called the dead letter queue (DLQ).
Let us see some details around this feature in action by inspecting some code snippets:
====
[source, java]
----
@Configuration(proxyBeanMethods = false)
@@ -484,7 +453,6 @@ class DeadLetterPolicyConfig {
}
}
----
====
First, we have a special bean for `DeadLetterPolicy`, and it is named as `deadLetterPolicy` (it can be any name as you wish).
This bean specifies a number of things, such as the max delivery (10, in this case) and the name of the dead letter topic -- `my-dlq-topic`, in this case.

View File

@@ -1,6 +1,6 @@
[[quick-tour-reactive]]
= Quick Tour
include::attributes.adoc[]
include::../attributes/attributes.adoc[]
We will take a quick tour of the Reactive support in Spring for Apache Pulsar by showing a sample Spring Boot application that produces and consumes in a Reactive fashion.
This is a complete application and does not require any additional configuration, as long as you have a Pulsar cluster running on the default location - `localhost:6650`.
@@ -9,8 +9,11 @@ This is a complete application and does not require any additional configuration
Spring Boot applications need only the `spring-pulsar-reactive-spring-boot-starter` dependency. The following listings show how to define the dependency for Maven and Gradle, respectively:
[tabs]
======
Maven::
+
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencies>
<dependency>
@@ -20,20 +23,26 @@ Spring Boot applications need only the `spring-pulsar-reactive-spring-boot-start
</dependency>
</dependencies>
----
Gradle::
+
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-pulsar-reactive:{spring-boot-version}'
}
----
======
[CAUTION]
====
When using `Version 0.2.x` the above coordinates change as follows:
[tabs]
======
Maven::
+
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<dependencies>
<dependency>
@@ -43,13 +52,16 @@ When using `Version 0.2.x` the above coordinates change as follows:
</dependency>
</dependencies>
----
Gradle::
+
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
dependencies {
implementation 'org.springframework.pulsar:spring-pulsar-reactive-spring-boot-starter:0.2.0'
}
----
======
====

View File

@@ -0,0 +1,4 @@
[[reference]]
= Reference
This part of the reference documentation details the various components that comprise Spring for Apache Pulsar.

View File

@@ -1,4 +1,3 @@
====
[source,java,subs="attributes,verbatim"]
----
@PulsarListener(subscriptionName = "user-sub", topics = "user-topic")
@@ -6,4 +5,3 @@ public void listen(User user) {
System.out.println(user);
}
----
====

View File

@@ -1,4 +1,3 @@
====
[source,java,subs="attributes,verbatim"]
----
@ReactivePulsarListener(topics = "user-topic")
@@ -7,4 +6,3 @@ Mono<Void> listen(User user) {
return Mono.empty();
}
----
====

View File

@@ -22,7 +22,6 @@ However, if more control is needed you can provide a schema resolver customizer
The following example uses a schema resolver customizer to add mappings for the `User` and `Address` complex objects using `AVRO` and `JSON` schemas, respectively:
====
[source, java]
----
@Bean
@@ -33,4 +32,3 @@ public SchemaResolverCustomizer<DefaultSchemaResolver> schemaResolverCustomizer(
}
}
----
====

View File

@@ -1,5 +1,4 @@
== Specifying Schema Information
As indicated earlier, for Java primitives, the Spring for Apache Pulsar framework can infer the proper Schema to use on the `{listener-class}`.
For non-primitive types, if the Schema is not explicitly specified on the annotation, the Spring for Apache Pulsar framework will try to build a `Schema.JSON` from the type.

View File

@@ -1,5 +1,5 @@
include::attributes.adoc[]
include::../attributes/attributes.adoc[]
By default, Pulsar clients communicate with Pulsar services in plain text.
The following section describes how to configure Pulsar clients to use TLS encryption (SSL).

View File

@@ -1,31 +0,0 @@
@import 'css/spring.css';
.listingblock .switch {
border-style: none;
display: inline-block;
position: relative;
bottom: -3px;
}
.listingblock .switch--item {
padding: 10px;
background-color: #e6e1dc;
color: #282c34;
display: inline-block;
cursor: pointer;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.listingblock .switch--item:not(:first-child) {
border-style: none;
}
.listingblock .switch--item.selected {
background-color: #282c34;
color: #e6e1dc;
}
.listingblock pre.highlightjs {
padding: 0;
}

View File

@@ -1,55 +0,0 @@
[[spring-pulsar-reference]]
= Spring for Apache Pulsar
include::attributes.adoc[]
include::authors.adoc[]
(v{spring-pulsar-version})
include::copyright.adoc[]
ifeval::[{is-snapshot-version} == true]
CAUTION: You are viewing documentation for a SNAPSHOT version ({spring-pulsar-version}). While it is usually in-sync with the underlying code, it is subject to change and not guaranteed to be up-to-date with the underlying code.
endif::[]
include::intro.adoc[leveloffset=+1]
include::getting-help.adoc[leveloffset=+2]
[[reference]]
== Reference
This part of the reference documentation goes through the details of the various components in Spring for Apache Pulsar.
include::pulsar.adoc[leveloffset=+2]
include::reactive-pulsar.adoc[leveloffset=+2]
include::pulsar-admin.adoc[leveloffset=+2]
include::pulsar-function.adoc[leveloffset=+2]
include::observability.adoc[leveloffset=+2]
include::pulsar-binder.adoc[leveloffset=+2]
:sectnums!:
[[other-resources]]
== Other Resources
In addition to this reference documentation, we recommend a number of other resources that may help you learn about Spring and Apache Pulsar.
- {github}[Spring for Apache Pulsar GitHub Repository]
- https://pulsar.apache.org/[Apache Pulsar Project Home Page]
- {apache-pulsar-docs}/client-libraries-java/[Apache Pulsar Java Client]
- https://github.com/apache/pulsar[Apache Pulsar GitHub Repository]
- https://github.com/apache/pulsar-client-reactive[Apache Pulsar Reactive Client GitHub Repository]
[[appendix]]
== Appendices
include::version-compatibility.adoc[leveloffset=+2]
include::override-boot-dependencies.adoc[leveloffset=+2]
include::non-ga-versions.adoc[leveloffset=+2]
include::native-image.adoc[leveloffset=+2]

View File

@@ -1,38 +0,0 @@
include::attributes-variables.adoc[]
= Introduction
This project provides a basic Spring-friendly API for developing https://pulsar.apache.org/[Apache Pulsar] applications.
On a very high level, Spring for Apache Pulsar provides a `PulsarTemplate` for publishing to a Pulsar topic and a `PulsarListener` annotation for consuming from a Pulsar topic.
In addition, it also provides various convenience APIs for Spring developers to ramp up their development journey into Apache Pulsar.
include::project-state.adoc[leveloffset=+1]
== System Requirements
Spring for Apache Pulsar `{spring-pulsar-version}` requires the following:
- https://www.java.com[Java 17] and is compatible up to and including Java 20
- {spring-framework-docs}[Spring Framework `{spring-framework-version}`] or above
- {apache-pulsar-cient-docs}/[Apache Pulsar Java Client `{pulsar-client-version}`] or above
====
The version compatibility matrix (including Spring Boot) can be found in the <<version-compatibility.adoc#appendix.version-compatibility,appendix>>.
====
== Building the Project
If you have cloned the project locally, follow these steps to build the project from the source code.
NOTE: Gradle `7.x (7.4 or above)` is required to build.
Run the following command to do a full build of the project:
[indent=0]
----
./gradlew clean build
----
You can build without running tests by using the following command:
[indent=0]
----
./gradlew clean build -x test
----

View File

@@ -117,9 +117,6 @@ public final class DefaultReactivePulsarSenderFactory<T>
private ReactiveMessageSender<T> doCreateReactiveMessageSender(Schema<T> schema, @Nullable String topic,
@Nullable List<ReactiveMessageSenderBuilderCustomizer<T>> customizers) {
Objects.requireNonNull(schema, "Schema must be specified");
this.logger.warn(() -> "**** Du CreateMessageSender for topic=" + topic);
String resolvedTopic = this.topicResolver.resolveTopic(topic, () -> getDefaultTopic()).orElseThrow();
this.logger.trace(() -> "Creating reactive message sender for '%s' topic".formatted(resolvedTopic));