From 65913e23c018858ef5b0551e415da959e0519409 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 1 Feb 2018 16:00:36 +0100 Subject: [PATCH] Restructure Cloud messaging This commit restructures the cloud messaging group by removing binder specific checkbox for Spring Cloud Bus and Spring Cloud Stream. A new "Messaging" group is added with RabbitMQ, Kafka and JMS. To create a new cloud messaging project, the user should now select the technology and a binder. To support Spring Cloud Stream with Kafka streams, a dedicated "Kafka Streams" has been introduced. Closes gh-557 --- ...ingCloudMessagingRequestPostProcessor.java | 70 +++++++ .../src/main/resources/application.yml | 168 +++++++++-------- .../AbstractRequestPostProcessorTests.java | 5 + ...oudMessagingRequestPostProcessorTests.java | 175 ++++++++++++++++++ 4 files changed, 337 insertions(+), 81 deletions(-) create mode 100644 initializr-service/src/main/java/io/spring/initializr/service/extension/SpringCloudMessagingRequestPostProcessor.java create mode 100644 initializr-service/src/test/java/io/spring/initializr/service/extension/SpringCloudMessagingRequestPostProcessorTests.java diff --git a/initializr-service/src/main/java/io/spring/initializr/service/extension/SpringCloudMessagingRequestPostProcessor.java b/initializr-service/src/main/java/io/spring/initializr/service/extension/SpringCloudMessagingRequestPostProcessor.java new file mode 100644 index 0000000000..c8afe11e07 --- /dev/null +++ b/initializr-service/src/main/java/io/spring/initializr/service/extension/SpringCloudMessagingRequestPostProcessor.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.initializr.service.extension; + +import io.spring.initializr.generator.ProjectRequest; +import io.spring.initializr.metadata.Dependency; +import io.spring.initializr.metadata.InitializrMetadata; + +import org.springframework.stereotype.Component; + +/** + * Determine the appropriate Spring Cloud stream dependency to use based on the + * selected messaging technology. + * + * @author Stephane Nicoll + */ +@Component +public class SpringCloudMessagingRequestPostProcessor + extends AbstractProjectRequestPostProcessor { + + static final Dependency KAFKA_BINDER = Dependency.withId("cloud-stream-binder-kafka", + "org.springframework.cloud", "spring-cloud-stream-binder-kafka"); + + static final Dependency KAFKA_STREAMS_BINDER = Dependency.withId( + "cloud-stream-binder-kafka-streams", "org.springframework.cloud", + "spring-cloud-stream-binder-kstream"); + + static final Dependency RABBIT_BINDER = Dependency.withId( + "cloud-stream-binder-rabbit", "org.springframework.cloud", + "spring-cloud-stream-binder-rabbit"); + + @Override + public void postProcessAfterResolution(ProjectRequest request, + InitializrMetadata metadata) { + boolean hasSpringCloudStream = hasDependency(request, "cloud-stream"); + boolean hasReactiveSpringCloudStream = hasDependency(request, + "reactive-cloud-stream"); + boolean hasSpringCloudBus = hasDependency(request, "cloud-bus"); + if (hasSpringCloudStream || hasReactiveSpringCloudStream || hasSpringCloudBus) { + if (hasDependencies(request, "amqp")) { + request.getResolvedDependencies().add(RABBIT_BINDER); + } + if (hasDependencies(request, "kafka")) { + request.getResolvedDependencies().add(KAFKA_BINDER); + } + } + // Spring Cloud Stream specific + if (hasSpringCloudStream || hasReactiveSpringCloudStream) { + if (hasDependencies(request, "kafka-streams")) { + request.getResolvedDependencies().add(KAFKA_STREAMS_BINDER); + } + } + } + +} + diff --git a/initializr-service/src/main/resources/application.yml b/initializr-service/src/main/resources/application.yml index 8360f3d12f..b69c2753cc 100644 --- a/initializr-service/src/main/resources/application.yml +++ b/initializr-service/src/main/resources/application.yml @@ -722,6 +722,64 @@ initializr: description: Accessing Data with GemFire - rel: reference href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-gemfire + - name: Messaging + content: + - name: RabbitMQ + id: amqp + description: Advanced Message Queuing Protocol via spring-rabbit + keywords: + - stream + - messaging + links: + - rel: guide + href: https://spring.io/guides/gs/messaging-rabbitmq/ + description: Messaging with RabbitMQ + - rel: reference + href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-amqp + - name: Kafka + id: kafka + weight: 100 + description: Kafka messaging support using Spring Kafka + versionRange: 1.5.0.RC1 + groupId: org.springframework.kafka + artifactId: spring-kafka + starter: false + keywords: + - stream + - messaging + links: + - rel: reference + href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-kafka + - name: JMS (ActiveMQ) + id: activemq + description: Java Message Service API via Apache ActiveMQ + versionRange: 1.4.0.RC1 + links: + - rel: guide + href: https://spring.io/guides/gs/messaging-jms/ + description: Messaging with JMS + - rel: reference + href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-activemq + - name: JMS (Artemis) + id: artemis + description: Java Message Service API via Apache Artemis + versionRange: 1.3.0.RELEASE + links: + - rel: guide + href: https://spring.io/guides/gs/messaging-jms/ + description: Messaging with JMS + - rel: reference + href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-artemis + - name: JMS (HornetQ) + id: hornetq + description: Java Message Service API via HornetQ + versionRange: "[1.1.0.RELEASE,1.4.0.RC1)" + links: + - rel: guide + href: https://spring.io/guides/gs/messaging-jms/ + description: Messaging with JMS + - rel: reference + href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-hornetq - name: Cloud Core bom: spring-cloud versionRange: 1.2.3.RELEASE @@ -974,41 +1032,23 @@ initializr: bom: spring-cloud versionRange: 1.2.3.RELEASE content: - - name: Cloud Bus AMQP - id: cloud-bus-amqp - description: A simple control bus with AMQP and spring-cloud-bus-amqp - groupId: org.springframework.cloud - artifactId: spring-cloud-starter-bus-amqp - - name: Cloud Bus Kafka - id: cloud-bus-kafka - description: A simple control bus with Kafka and spring-cloud-bus - versionRange: "1.3.0.RELEASE" + - name: Cloud Bus + id: cloud-bus + description: A simple control bus using Spring Cloud Stream (requires a binder, e.g. Kafka or RabbitMQ) groupId: org.springframework.cloud - artifactId: spring-cloud-starter-bus-kafka - - name: Stream Binder Rabbit - id: cloud-stream-binder-rabbit - description: Messaging microservices with RabbitMQ + artifactId: spring-cloud-bus + - name: Cloud Stream + id: cloud-stream + description: Messaging microservices with Spring Cloud Stream (requires a binder, e.g. Kafka or RabbitMQ) versionRange: 1.3.0.RELEASE groupId: org.springframework.cloud - artifactId: spring-cloud-starter-stream-rabbit - - name: Stream Binder Kafka - id: cloud-stream-binder-kafka - description: Messaging microservices with Kafka - versionRange: 1.3.0.RELEASE - groupId: org.springframework.cloud - artifactId: spring-cloud-stream-binder-kafka - - name: Stream Binder "Kafka Streams" - id: cloud-stream-binder-kafka-streams - description: Stream processing microservices with Kafka Streams API - versionRange: 1.5.7.RELEASE + artifactId: spring-cloud-stream + - name: Reactive Cloud Stream + id: reactive-cloud-stream + description: Reactive messaging microservices with Spring Cloud Stream (requires a binder, e.g. Kafka or RabbitMQ) + versionRange: 2.0.0.BUILD-SNAPSHOT groupId: org.springframework.cloud - artifactId: spring-cloud-stream-binder-kstream - links: - - rel: guide - href: https://github.com/spring-cloud/spring-cloud-stream-samples/tree/master/kstream - description: Samples for using Kafka Streams with Spring Cloud stream - - rel: reference - href: https://docs.spring.io/spring-cloud-stream/docs/current/reference/htmlsingle/#_kafka_streams_binding_capabilities_of_spring_cloud_stream + artifactId: spring-cloud-stream-reactive - name: Cloud AWS bom: spring-cloud versionRange: 1.2.3.RELEASE @@ -1170,63 +1210,29 @@ initializr: description: Integrating Data - rel: reference href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-integration - - name: Mail - id: mail - description: javax.mail - versionRange: 1.2.0.RC1 - links: - - rel: reference - href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-email - - name: AMQP - id: amqp - description: Advanced Message Queuing Protocol via spring-rabbit - links: - - rel: guide - href: https://spring.io/guides/gs/messaging-rabbitmq/ - description: Messaging with RabbitMQ - - rel: reference - href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-amqp - - name: Kafka - id: kafka - weight: 100 - description: Kafka messaging support using Spring Kafka + - name: Kafka Streams + id: kafka-streams + weight: 90 + description: Client library for building applications and microservices, where the input and output data are stored in Kafka clusters. versionRange: 1.5.0.RC1 - groupId: org.springframework.kafka - artifactId: spring-kafka + groupId: org.apache.kafka + artifactId: kafka-streams starter: false - links: - - rel: reference - href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-kafka - - name: JMS (ActiveMQ) - id: activemq - description: Java Message Service API via Apache ActiveMQ - versionRange: 1.4.0.RC1 - links: - - rel: guide - href: https://spring.io/guides/gs/messaging-jms/ - description: Messaging with JMS - - rel: reference - href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-activemq - - name: JMS (Artemis) - id: artemis - description: Java Message Service API via Apache Artemis - versionRange: 1.3.0.RELEASE + keywords: + - stream links: - rel: guide - href: https://spring.io/guides/gs/messaging-jms/ - description: Messaging with JMS + href: https://github.com/spring-cloud/spring-cloud-stream-samples/tree/master/kstream + description: Samples for using Kafka Streams with Spring Cloud stream - rel: reference - href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-artemis - - name: JMS (HornetQ) - id: hornetq - description: Java Message Service API via HornetQ - versionRange: "[1.1.0.RELEASE,1.4.0.RC1)" + href: https://docs.spring.io/spring-cloud-stream/docs/current/reference/htmlsingle/#_kafka_streams_binding_capabilities_of_spring_cloud_stream + - name: Mail + id: mail + description: javax.mail + versionRange: 1.2.0.RC1 links: - - rel: guide - href: https://spring.io/guides/gs/messaging-jms/ - description: Messaging with JMS - rel: reference - href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-hornetq + href: http://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-email - name: Apache Camel id: camel versionRange: "[1.4.0.RELEASE,2.0.0.M1)" diff --git a/initializr-service/src/test/java/io/spring/initializr/service/extension/AbstractRequestPostProcessorTests.java b/initializr-service/src/test/java/io/spring/initializr/service/extension/AbstractRequestPostProcessorTests.java index dc2d58e401..fe1926034c 100644 --- a/initializr-service/src/test/java/io/spring/initializr/service/extension/AbstractRequestPostProcessorTests.java +++ b/initializr-service/src/test/java/io/spring/initializr/service/extension/AbstractRequestPostProcessorTests.java @@ -21,6 +21,7 @@ import io.spring.initializr.generator.ProjectGenerator; import io.spring.initializr.generator.ProjectRequest; import io.spring.initializr.generator.ProjectRequestPostProcessor; +import io.spring.initializr.metadata.Dependency; import io.spring.initializr.metadata.InitializrMetadataProvider; import io.spring.initializr.test.generator.GradleBuildAssert; import io.spring.initializr.test.generator.PomAssert; @@ -45,6 +46,10 @@ public abstract class AbstractRequestPostProcessorTests { @Autowired private InitializrMetadataProvider metadataProvider; + protected Dependency getDependency(String id) { + return this.metadataProvider.get().getDependencies().get(id); + } + protected PomAssert generateMavenPom(ProjectRequest request) { request.setType("maven-build"); String content = new String(projectGenerator.generateMavenPom(request)); diff --git a/initializr-service/src/test/java/io/spring/initializr/service/extension/SpringCloudMessagingRequestPostProcessorTests.java b/initializr-service/src/test/java/io/spring/initializr/service/extension/SpringCloudMessagingRequestPostProcessorTests.java new file mode 100644 index 0000000000..4afb60ddfd --- /dev/null +++ b/initializr-service/src/test/java/io/spring/initializr/service/extension/SpringCloudMessagingRequestPostProcessorTests.java @@ -0,0 +1,175 @@ +/* + * Copyright 2012-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.initializr.service.extension; + +import io.spring.initializr.generator.ProjectRequest; +import org.junit.Test; + +import static io.spring.initializr.service.extension.SpringCloudMessagingRequestPostProcessor.KAFKA_BINDER; +import static io.spring.initializr.service.extension.SpringCloudMessagingRequestPostProcessor.KAFKA_STREAMS_BINDER; +import static io.spring.initializr.service.extension.SpringCloudMessagingRequestPostProcessor.RABBIT_BINDER; + +/** + * Tests for {@link SpringCloudMessagingRequestPostProcessor}. + * + * @author Stephane Nicoll + */ +public class SpringCloudMessagingRequestPostProcessorTests + extends AbstractRequestPostProcessorTests { + + @Test + public void springCloudStreamWithRabbit() { + ProjectRequest request = createProjectRequest("cloud-stream", "amqp"); + generateMavenPom(request) + .hasDependency(getDependency("cloud-stream")) + .hasDependency(getDependency("amqp")) + .hasDependency(RABBIT_BINDER) + .hasSpringBootStarterTest() + .hasDependenciesCount(4); + } + + @Test + public void springCloudStreamWithKafka() { + ProjectRequest request = createProjectRequest("cloud-stream", "kafka"); + generateMavenPom(request) + .hasDependency(getDependency("cloud-stream")) + .hasDependency(getDependency("kafka")) + .hasDependency(KAFKA_BINDER) + .hasSpringBootStarterTest() + .hasDependenciesCount(4); + } + + @Test + public void springCloudStreamWithKafkaStreams() { + ProjectRequest request = createProjectRequest("cloud-stream", "kafka-streams"); + generateMavenPom(request) + .hasDependency(getDependency("cloud-stream")) + .hasDependency(getDependency("kafka-streams")) + .hasDependency(KAFKA_STREAMS_BINDER) + .hasSpringBootStarterTest() + .hasDependenciesCount(4); + } + + @Test + public void springCloudStreamWithAllBinders() { + ProjectRequest request = createProjectRequest("cloud-stream", "amqp", "kafka", + "kafka-streams"); + generateMavenPom(request) + .hasDependency(getDependency("cloud-stream")) + .hasDependency(getDependency("amqp")) + .hasDependency(getDependency("kafka")) + .hasDependency(getDependency("kafka-streams")) + .hasDependency(RABBIT_BINDER) + .hasDependency(KAFKA_BINDER) + .hasDependency(KAFKA_STREAMS_BINDER) + .hasSpringBootStarterTest() + .hasDependenciesCount(8); + } + + @Test + public void reactiveSpringCloudStreamWithRabbit() { + ProjectRequest request = createProjectRequest("reactive-cloud-stream", "amqp"); + request.setBootVersion("2.0.0.BUILD-SNAPSHOT"); + generateMavenPom(request) + .hasDependency(getDependency("reactive-cloud-stream")) + .hasDependency(getDependency("amqp")) + .hasDependency(RABBIT_BINDER) + .hasSpringBootStarterTest() + .hasDependenciesCount(4); + } + + @Test + public void reactiveSpringCloudStreamWithKafka() { + ProjectRequest request = createProjectRequest("reactive-cloud-stream", "kafka"); + request.setBootVersion("2.0.0.BUILD-SNAPSHOT"); + generateMavenPom(request) + .hasDependency(getDependency("reactive-cloud-stream")) + .hasDependency(getDependency("kafka")) + .hasDependency(KAFKA_BINDER) + .hasSpringBootStarterTest() + .hasDependenciesCount(4); + } + + @Test + public void reactiveSpringCloudStreamWithKafkaStreams() { + ProjectRequest request = createProjectRequest("reactive-cloud-stream", + "kafka-streams"); + request.setBootVersion("2.0.0.BUILD-SNAPSHOT"); + generateMavenPom(request) + .hasDependency(getDependency("reactive-cloud-stream")) + .hasDependency(getDependency("kafka-streams")) + .hasDependency(KAFKA_STREAMS_BINDER) + .hasSpringBootStarterTest() + .hasDependenciesCount(4); + } + + @Test + public void reactiveSpringCloudStreamWithAllBinders() { + ProjectRequest request = createProjectRequest("reactive-cloud-stream", "amqp", + "kafka", "kafka-streams"); + request.setBootVersion("2.0.0.BUILD-SNAPSHOT"); + generateMavenPom(request) + .hasDependency(getDependency("reactive-cloud-stream")) + .hasDependency(getDependency("amqp")) + .hasDependency(getDependency("kafka")) + .hasDependency(getDependency("kafka-streams")) + .hasDependency(RABBIT_BINDER) + .hasDependency(KAFKA_BINDER) + .hasDependency(KAFKA_STREAMS_BINDER) + .hasSpringBootStarterTest() + .hasDependenciesCount(8); + } + + @Test + public void springCloudBusWithRabbit() { + ProjectRequest request = createProjectRequest("cloud-bus", "amqp"); + generateMavenPom(request) + .hasDependency(getDependency("cloud-bus")) + .hasDependency(getDependency("amqp")) + .hasDependency(RABBIT_BINDER) + .hasSpringBootStarterTest() + .hasDependenciesCount(4); + } + + @Test + public void springCloudBusWithKafka() { + ProjectRequest request = createProjectRequest("cloud-bus", "amqp"); + generateMavenPom(request) + .hasDependency(getDependency("cloud-bus")) + .hasDependency(getDependency("amqp")) + .hasDependency(RABBIT_BINDER) + .hasSpringBootStarterTest() + .hasDependenciesCount(4); + } + + @Test + public void springCloudBusWithAllBinders() { + ProjectRequest request = createProjectRequest("cloud-bus", "amqp", "kafka", + "kafka-streams"); + generateMavenPom(request) + .hasDependency(getDependency("cloud-bus")) + .hasDependency(getDependency("amqp")) + .hasDependency(getDependency("kafka")) + .hasDependency(getDependency("kafka-streams")) + .hasDependency(RABBIT_BINDER) + .hasDependency(KAFKA_BINDER) + .hasSpringBootStarterTest() + .hasDependenciesCount(7); + } + + +}