├── service-idl
├── src
│ ├── generated
│ │ └── .gitignore
│ └── main
│ │ └── proto
│ │ └── service.proto
├── README.md
└── build.gradle
├── ci
├── client1.sh
└── client2.sh
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle
├── number-generator
├── README.md
├── src
│ └── main
│ │ ├── java
│ │ └── io
│ │ │ └── netifi
│ │ │ └── proteus
│ │ │ └── example
│ │ │ └── kafka
│ │ │ └── generator
│ │ │ ├── StreamNumberResponseSerializer.java
│ │ │ ├── Main.java
│ │ │ └── GeneratorRunner.java
│ │ └── resources
│ │ ├── application.properties
│ │ └── logback.xml
└── build.gradle
├── client
├── src
│ └── main
│ │ ├── java
│ │ └── io
│ │ │ └── netifi
│ │ │ └── proteus
│ │ │ └── example
│ │ │ └── kafka
│ │ │ └── client
│ │ │ ├── Main.java
│ │ │ └── ClientRunner.java
│ │ └── resources
│ │ ├── logback.xml
│ │ └── application.properties
├── README.md
└── build.gradle
├── client-js
├── src
│ └── main
│ │ ├── resources
│ │ └── web
│ │ │ └── index.html
│ │ └── js
│ │ ├── index.js
│ │ └── proteus
│ │ ├── service_rsocket_pb.js
│ │ └── service_pb.js
├── webpack.config.js
├── package.json
└── README.md
├── service
├── README.md
├── build.gradle
└── src
│ └── main
│ ├── java
│ └── io
│ │ └── netifi
│ │ └── proteus
│ │ └── example
│ │ └── kafka
│ │ └── service
│ │ ├── StreamNumbersResponseDeserializer.java
│ │ ├── Main.java
│ │ └── DefaultNumberService.java
│ └── resources
│ ├── logback.xml
│ └── application.properties
├── README.md
├── gradlew.bat
├── .gitignore
├── gradlew
└── LICENSE
/service-idl/src/generated/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 |
--------------------------------------------------------------------------------
/ci/client1.sh:
--------------------------------------------------------------------------------
1 | cd .. && ./gradlew --no-daemon service:run
--------------------------------------------------------------------------------
/ci/client2.sh:
--------------------------------------------------------------------------------
1 | cd .. && ./gradlew --no-daemon client:run
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netifi/proteus-spring-kafka-example/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'proteus-spring-kafka-example'
2 | include 'client'
3 | include 'number-generator'
4 | include 'service'
5 | include 'service-idl'
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Dec 03 11:13:29 PST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip
7 |
--------------------------------------------------------------------------------
/number-generator/README.md:
--------------------------------------------------------------------------------
1 | # number-generator
2 | Generates random numbers and pushes them to a Kafka topic
3 |
4 |
5 | ## Running the Service
6 | From the root project, run the following command to start the service:
7 |
8 | ./gradlew :number-generator:run
9 |
10 | Note: The client and service can be started in any order. The client will not send data until it detects that the service has started.
11 |
--------------------------------------------------------------------------------
/client/src/main/java/io/netifi/proteus/example/kafka/client/Main.java:
--------------------------------------------------------------------------------
1 | package io.netifi.proteus.example.kafka.client;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class Main {
8 |
9 | public static void main(String... args) {
10 | SpringApplication.run(Main.class, args);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/client-js/src/main/resources/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Proteus Quickstart
6 |
7 |
8 | All Numbers
9 |
10 | Positive Numbers
11 |
12 | Negative Numbers
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/service-idl/README.md:
--------------------------------------------------------------------------------
1 | # service-idl
2 | This project holds the definition of your service's public interface (The methods your service will expose to other clients and services).
3 |
4 | ## Instructions
5 | Define the interface for your service using ProtoBuf IDL in the `src/main/proto` directory.
6 |
7 | An example service interface has been provided for you: [service.proto](https://github.com/netifi/proteus-quickstart/blob/master/service-idl/src/main/proto/io/netifi/proteus/quickstart/service/protobuf/service.proto)
8 |
9 | ## Documentation
10 | Documentation on the IDL format can be found here: [http://docs.netifi.com/protobuf_rsocket/](http://docs.netifi.com/protobuf_rsocket/)
11 |
--------------------------------------------------------------------------------
/number-generator/src/main/java/io/netifi/proteus/example/kafka/generator/StreamNumberResponseSerializer.java:
--------------------------------------------------------------------------------
1 | package io.netifi.proteus.example.kafka.generator;
2 |
3 |
4 | import io.netifi.proteus.example.kafka.service.StreamNumbersResponse;
5 | import org.apache.kafka.common.serialization.Serializer;
6 |
7 | import java.util.Map;
8 |
9 | public class StreamNumberResponseSerializer implements Serializer {
10 | @Override
11 | public void configure(Map configs, boolean isKey) {}
12 |
13 | @Override
14 | public byte[] serialize(String topic, StreamNumbersResponse data) {
15 | return data.toByteArray();
16 | }
17 |
18 | @Override
19 | public void close() {}
20 | }
21 |
--------------------------------------------------------------------------------
/service/README.md:
--------------------------------------------------------------------------------
1 | # service
2 | Emits a stream of random numbers based on a type passed to it by a caller.
3 |
4 | ## Prerequisites
5 | The service requires that a Netifi Proteus Broker instance is running and configued with the following access key and token:
6 |
7 | * Access Key: `9007199254740991`
8 | * Access Token: `kTBDVtfRBO4tHOnZzSyY5ym2kfY=`
9 |
10 | Instructions for starting the Netifi Proteus Broker can be found in the main [README](../README.md).
11 |
12 | ## Running the Service
13 | From the root project, run the following command to start the service:
14 |
15 | ./gradlew :service:run
16 |
17 | Note: The client and service can be started in any order. The client will not send data until it detects that the service has started.
18 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # client
2 | Receives a stream of random numbers qualified by a Type supplied when asking for the stream
3 |
4 | ## Prerequisites
5 | The client requires that a Netifi Proteus Broker instance is running and configued with the following access key and token:
6 |
7 | * Access Key: `9007199254740991`
8 | * Access Token: `kTBDVtfRBO4tHOnZzSyY5ym2kfY=`
9 |
10 | Instructions for starting the Netifi Proteus Broker can be found in the main [README](../README.md).
11 |
12 | ## Running the Client
13 | From the root project, run the following command to start the client:
14 |
15 | ./gradlew :client:run
16 |
17 | Note: The client and service can be started in any order. The client will not send data until it detects that the service has started.
18 |
--------------------------------------------------------------------------------
/client-js/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = {
5 | entry: path.resolve(__dirname, './src/main/js/index.js'),
6 | output: {
7 | path: path.resolve(__dirname, './src/main/resources/web/'),
8 | filename: 'app.js'
9 | },
10 | plugins: [
11 | new webpack.DefinePlugin({
12 | '__DEV__': true,
13 | '__WS_URL__': JSON.stringify(process.env.WS_URL || 'ws://localhost:8101/')
14 | })
15 | ],
16 | module: {
17 | loaders: [
18 | {
19 | test: /\.js?$/,
20 | loader: 'babel-loader',
21 | exclude: /node_modules/
22 | }
23 | ]
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/service-idl/src/main/proto/service.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package io.netifi.proteus.example.kafka.service;
4 |
5 | option java_package = "io.netifi.proteus.example.kafka.service";
6 | option java_outer_classname = "ServiceProto";
7 | option java_multiple_files = true;
8 |
9 | service NumberService {
10 |
11 | // A Stream of Random numbers can be filtered by type;
12 | rpc streamNumbers (StreamNumbersRequest) returns (stream StreamNumbersResponse) {}
13 | }
14 |
15 | message StreamNumbersRequest {
16 | enum Type {
17 | EVEN = 0;
18 | ODD = 1;
19 | ALL = 2;
20 | NEGATIVE = 3;
21 | POSITIVE = 4;
22 | }
23 | Type type = 1;
24 | }
25 |
26 | message StreamNumbersResponse {
27 | int64 number = 1;
28 | }
29 |
--------------------------------------------------------------------------------
/number-generator/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Netifi Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | netifi.proteus.kafka.bootstrapServers=localhost:9092
18 | netifi.proteus.kafka.topic=demo-topic
19 |
--------------------------------------------------------------------------------
/client/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | id 'application'
4 | id 'io.spring.dependency-management' version '1.0.6.RELEASE'
5 | }
6 |
7 | mainClassName = 'io.netifi.proteus.example.kafka.client.Main'
8 | sourceCompatibility = 1.8
9 |
10 | dependencyManagement {
11 | imports {
12 | mavenBom "org.springframework.boot:spring-boot-dependencies:${springBootBomVersion}"
13 | }
14 | }
15 |
16 | dependencies {
17 | compile project(':service-idl')
18 |
19 | compile "io.netifi.proteus:proteus-spring-boot-starter:$proteusSpringVersion"
20 | }
21 |
22 | configurations.all {
23 | resolutionStrategy {
24 | dependencySubstitution {
25 | substitute module('com.google.guava:guava') with module('com.google.guava:guava:22.0')
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/service/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | id 'application'
4 | id 'io.spring.dependency-management' version '1.0.6.RELEASE'
5 | }
6 |
7 | mainClassName = 'io.netifi.proteus.example.kafka.service.Main'
8 | sourceCompatibility = 1.8
9 |
10 | dependencyManagement {
11 | imports {
12 | mavenBom "org.springframework.boot:spring-boot-dependencies:${springBootBomVersion}"
13 | }
14 | }
15 |
16 | dependencies {
17 | compile project(':service-idl')
18 | implementation 'io.projectreactor.kafka:reactor-kafka:1.1.0.RELEASE'
19 | compile "io.netifi.proteus:proteus-spring-boot-starter:$proteusSpringVersion"
20 | }
21 |
22 | configurations.all {
23 | resolutionStrategy {
24 | dependencySubstitution {
25 | substitute module('com.google.guava:guava') with module('com.google.guava:guava:22.0')
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/service/src/main/java/io/netifi/proteus/example/kafka/service/StreamNumbersResponseDeserializer.java:
--------------------------------------------------------------------------------
1 | package io.netifi.proteus.example.kafka.service;
2 |
3 | import com.google.protobuf.InvalidProtocolBufferException;
4 | import org.apache.kafka.common.serialization.Deserializer;
5 | import reactor.core.Exceptions;
6 |
7 | import java.util.Map;
8 |
9 | public class StreamNumbersResponseDeserializer implements Deserializer {
10 | @Override
11 | public void configure(Map configs, boolean isKey) {}
12 |
13 | @Override
14 | public StreamNumbersResponse deserialize(String topic, byte[] data) {
15 | try {
16 | return StreamNumbersResponse.parseFrom(data);
17 | } catch (InvalidProtocolBufferException e) {
18 | throw Exceptions.propagate(e);
19 | }
20 | }
21 |
22 | @Override
23 | public void close() {}
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | ${CONSOLE_LOG_PATTERN}
9 | utf8
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/number-generator/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | id 'application'
4 | id 'io.spring.dependency-management' version '1.0.6.RELEASE'
5 | }
6 |
7 | mainClassName = 'io.netifi.proteus.example.kafka.generator.Main'
8 | sourceCompatibility = 1.8
9 |
10 | dependencyManagement {
11 | imports {
12 | mavenBom "org.springframework.boot:spring-boot-dependencies:${springBootBomVersion}"
13 | }
14 | }
15 |
16 | dependencies {
17 | compile project(':service-idl')
18 | implementation 'io.projectreactor.kafka:reactor-kafka:1.1.0.RELEASE'
19 | implementation "org.springframework.boot:spring-boot-starter:${springBootBomVersion}"
20 | }
21 |
22 | configurations.all {
23 | resolutionStrategy {
24 | dependencySubstitution {
25 | substitute module('com.google.guava:guava') with module('com.google.guava:guava:22.0')
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/number-generator/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | ${CONSOLE_LOG_PATTERN}
9 | utf8
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/service/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | ${CONSOLE_LOG_PATTERN}
9 | utf8
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/client/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Netifi Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | netifi.proteus.broker.hostname=localhost
18 | netifi.proteus.broker.port=8001
19 |
20 | netifi.proteus.access.key=9007199254740991
21 | netifi.proteus.access.token=kTBDVtfRBO4tHOnZzSyY5ym2kfY=
22 |
23 | netifi.proteus.group=proteus.kafkaDemo.client
24 |
25 | netifi.proteus.ssl.disabled=true
--------------------------------------------------------------------------------
/service/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Netifi Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | netifi.proteus.broker.hostname=localhost
18 | netifi.proteus.broker.port=8001
19 |
20 | netifi.proteus.access.key=9007199254740991
21 | netifi.proteus.access.token=kTBDVtfRBO4tHOnZzSyY5ym2kfY=
22 |
23 | netifi.proteus.group=proteus.kafkaDemo.numberService
24 |
25 | netifi.proteus.kafka.bootstrapServers=localhost:9092
26 | netifi.proteus.kafka.topic=demo-topic
27 |
28 | netifi.proteus.ssl.disabled=true
--------------------------------------------------------------------------------
/service-idl/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.google.protobuf' version '0.8.7'
3 | id 'java'
4 | }
5 |
6 | sourceCompatibility = 1.8
7 |
8 | dependencies {
9 | compile "io.netifi.proteus:proteus-client:$proteusVersion"
10 | compile "com.google.protobuf:protobuf-java:$protobufVersion"
11 | }
12 |
13 | sourceSets {
14 | main {
15 | proto { srcDir 'src/main/proto' }
16 | }
17 |
18 | test {
19 | proto { srcDir 'src/test/proto' }
20 | }
21 | }
22 |
23 | protobuf {
24 | generatedFilesBaseDir = "${projectDir}/src/generated"
25 |
26 | protoc {
27 | artifact = "com.google.protobuf:protoc:$protobufVersion"
28 | }
29 | plugins {
30 | rsocketRpc {
31 | artifact = "io.rsocket.rpc:rsocket-rpc-protobuf:$rsocketRpcVersion"
32 | }
33 | }
34 | generateProtoTasks {
35 | ofSourceSet('main')*.plugins {
36 | rsocketRpc {}
37 | }
38 | }
39 | }
40 |
41 | idea {
42 | module {
43 | sourceDirs += file("src/main/proto")
44 | sourceDirs += file("src/generated/main/java")
45 | sourceDirs += file("src/generated/main/proteus")
46 |
47 | generatedSourceDirs += file('src/generated/main/java')
48 | generatedSourceDirs += file('src/generated/main/proteus')
49 | }
50 | }
51 |
52 | clean {
53 | delete 'src/generated/main'
54 | }
55 |
--------------------------------------------------------------------------------
/client-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "proteus-browser-demo",
3 | "version": "0.0.1",
4 | "description": "A demo application of Proteus using RSocket from a Browser",
5 | "main": "index.js",
6 | "repository": "https://github.com/netifi-proteus/proteus-browser-demo",
7 | "author": "Kyle Bahr ",
8 | "license": "MIT",
9 | "private": false,
10 | "scripts": {
11 | "protoc": "protoc --proto_path=../service-idl/src/main/proto --proto_path=node_modules/rsocket-rpc-protobuf/proto --js_out=import_style=commonjs,binary:src/main/js/proteus --rsocket_rpc_out=src/main/js/proteus --plugin=protoc-gen-rsocket_rpc=node_modules/.bin/rsocket_rpc_js_protoc_plugin ../service-idl/src/main/proto/service.proto",
12 | "build": "webpack",
13 | "start": "webpack && http-server ./src/main/resources/web/ -p 3000 -c-1"
14 | },
15 | "devDependencies": {
16 | "babel-core": "^6.26.3",
17 | "babel-loader": "^7.1.4",
18 | "babel-preset-env": "^1.7.0",
19 | "http-server": "^0.11.1",
20 | "rsocket-rpc-core": "^0.0.5-0",
21 | "rsocket-rpc-frames": "^0.0.3",
22 | "rsocket-rpc-metrics": "^0.0.1",
23 | "rsocket-rpc-protobuf": "^0.1.5",
24 | "rsocket-rpc-tracing": "^0.0.3",
25 | "webpack": "3.2.0",
26 | "copy-webpack-plugin": "^4.6.0"
27 | },
28 | "dependencies": {
29 | "google-protobuf": "^3.6.1",
30 | "proteus-js-client": "^1.5.3",
31 | "ws": "^6.1.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/client/src/main/java/io/netifi/proteus/example/kafka/client/ClientRunner.java:
--------------------------------------------------------------------------------
1 | package io.netifi.proteus.example.kafka.client;
2 |
3 | import io.netifi.proteus.example.kafka.service.NumberServiceClient;
4 | import io.netifi.proteus.example.kafka.service.StreamNumbersRequest;
5 | import io.netifi.proteus.spring.core.annotation.Group;
6 | import org.apache.logging.log4j.LogManager;
7 | import org.apache.logging.log4j.Logger;
8 | import org.springframework.boot.CommandLineRunner;
9 | import org.springframework.stereotype.Component;
10 |
11 | import java.util.concurrent.ThreadLocalRandom;
12 |
13 | @Component
14 | public class ClientRunner implements CommandLineRunner {
15 | private static final Logger logger = LogManager.getLogger(ClientRunner.class);
16 |
17 | @Group("proteus.kafkaDemo.numberService")
18 | private NumberServiceClient client;
19 |
20 | @Override
21 | public void run(String... args) throws Exception {
22 | // Pick a type at random stream the results
23 | StreamNumbersRequest.Type type = randomType();
24 | logger.info("Streaming numbers of type " + type.toString());
25 |
26 | client
27 | .streamNumbers(StreamNumbersRequest.newBuilder().setType(type).build())
28 | .doOnNext(s -> logger.info("found number {} of {}", s, type.toString()))
29 | .blockLast();
30 | }
31 |
32 | private StreamNumbersRequest.Type randomType() {
33 | StreamNumbersRequest.Type[] values = StreamNumbersRequest.Type.values();
34 | int i = ThreadLocalRandom.current().nextInt(0, values.length - 1);
35 | return values[i];
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/number-generator/src/main/java/io/netifi/proteus/example/kafka/generator/Main.java:
--------------------------------------------------------------------------------
1 | package io.netifi.proteus.example.kafka.generator;
2 |
3 | import io.netifi.proteus.example.kafka.service.StreamNumbersResponse;
4 | import org.apache.kafka.clients.producer.ProducerConfig;
5 | import org.apache.kafka.common.serialization.IntegerSerializer;
6 | import org.springframework.beans.factory.annotation.Value;
7 | import org.springframework.boot.SpringApplication;
8 | import org.springframework.boot.autoconfigure.SpringBootApplication;
9 | import org.springframework.context.annotation.Bean;
10 | import reactor.kafka.sender.KafkaSender;
11 | import reactor.kafka.sender.SenderOptions;
12 |
13 | import java.util.HashMap;
14 | import java.util.Map;
15 |
16 | @SpringBootApplication
17 | public class Main {
18 |
19 | @Value("${netifi.proteus.kafka.bootstrapServers}")
20 | private String bootstrapServers;
21 |
22 | public static void main(String... args) {
23 | SpringApplication.run(Main.class, args);
24 | }
25 |
26 | @Bean
27 | KafkaSender