This section goes into more detail about how you can work with Spring Cloud Stream. -It covers topics such as creating and running stream applications.
-diff --git a/Camden.SR6/configprops.groovy b/Camden.SR6/configprops.groovy index eb1682aa..c4ddec55 100644 --- a/Camden.SR6/configprops.groovy +++ b/Camden.SR6/configprops.groovy @@ -8,19 +8,18 @@ @GrabResolver(name='milestone', root='http://repo.spring.io/milestone/') @Grab('org.codehaus.groovy:groovy-json:2.4.3') @Grab('org.springframework.cloud:spring-cloud-stream:1.1.0.RELEASE') -@Grab('org.springframework.cloud:spring-cloud-starter-bus-amqp:1.2.1.RELEASE') -@Grab('org.springframework.cloud:spring-cloud-starter-config:1.2.2.RELEASE') -@Grab('org.springframework.cloud:spring-cloud-config-server:1.2.2.RELEASE') -@Grab('org.springframework.cloud:spring-cloud-netflix-eureka-server:1.2.3.RELEASE') -@Grab('org.springframework.cloud:spring-cloud-starter-eureka:1.2.3.RELEASE') -@Grab('org.springframework.cloud:spring-cloud-starter-aws:1.1.0.RELEASE') +@Grab('org.springframework.cloud:spring-cloud-starter-bus-amqp:1.2.0.RELEASE') +@Grab('org.springframework.cloud:spring-cloud-starter-config:1.2.0.RELEASE') +@Grab('org.springframework.cloud:spring-cloud-netflix-eureka-server:1.2.0.RELEASE') +@Grab('org.springframework.cloud:spring-cloud-starter-eureka:1.2.0.RELEASE') +@Grab('org.springframework.cloud:spring-cloud-starter-aws:1.1.3.RELEASE') @Grab('org.springframework.cloud:spring-cloud-starter-security:1.1.3.RELEASE') -@Grab('org.springframework.cloud:spring-cloud-starter-consul-all:1.1.2.RELEASE') +@Grab('org.springframework.cloud:spring-cloud-starter-consul-all:1.1.0.RELEASE') @Grab('org.springframework.cloud:spring-cloud-starter-zookeeper-all:1.0.3.RELEASE') -@Grab('org.springframework.cloud:spring-cloud-starter-sleuth:1.1.0.RELEASE') +@Grab('org.springframework.cloud:spring-cloud-starter-sleuth:1.0.9.RELEASE') @Grab('org.springframework.cloud:spring-cloud-starter-cloudfoundry:1.0.1.RELEASE') -@Grab('org.springframework.cloud:spring-cloud-cloudfoundry-discovery:1.0.1.RELEASE') -@Grab('org.springframework.cloud:spring-cloud-contract-spec:1.0.2.RELEASE') +@Grab('org.springframework.cloud:spring-cloud-cloudfoundry-discovery:1.0.0.RELEASE') +@Grab('org.springframework.cloud:spring-cloud-contract-stub-runner:1.0.0.RELEASE') import org.springframework.core.io.support.PathMatchingResourcePatternResolver import org.springframework.core.io.Resource diff --git a/Camden.SR6/index.html b/Camden.SR6/index.html index 965fa2b9..fd1632b6 100644 --- a/Camden.SR6/index.html +++ b/Camden.SR6/index.html @@ -579,25 +579,223 @@ body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-b -
Spring Cloud provides tools for developers to quickly build some of the common patterns in distributed systems (e.g. configuration management, service discovery, circuit breakers, intelligent routing, -micro-proxy, control bus). Coordination of +micro-proxy, control bus, one-time tokens, global locks, leadership +election, distributed sessions, cluster state). Coordination of distributed systems leads to boiler plate patterns, and using Spring Cloud developers can quickly stand up services and applications that implement those patterns. They will work well in any distributed @@ -648,6 +847,12 @@ and extensibility mechanism to cover others.
Circuit Breakers
Global locks
+Leadership election and cluster state
+Distributed messaging
This section goes into more detail about how you can work with Spring Cloud Stream. -It covers topics such as creating and running stream applications.
-Spring Cloud Stream is a framework for building message-driven microservice applications. -Spring Cloud Stream builds upon Spring Boot to create standalone, production-grade Spring applications, and uses Spring Integration to provide connectivity to message brokers. -It provides opinionated configuration of middleware from several vendors, introducing the concepts of persistent publish-subscribe semantics, consumer groups, and partitions.
-You can add the @EnableBinding annotation to your application to get immediate connectivity to a message broker, and you can add @StreamListener to a method to cause it to receive events for stream processing.
-The following is a simple sink application which receives external messages.
@SpringBootApplication
-@EnableBinding(Sink.class)
-public class VoteRecordingSinkApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(VoteRecordingSinkApplication.class, args);
- }
-
- @StreamListener(Sink.INPUT)
- public void processVote(Vote vote) {
- votingService.recordVote(vote);
- }
-}
-The @EnableBinding annotation takes one or more interfaces as parameters (in this case, the parameter is a single Sink interface).
-An interface declares input and/or output channels.
-Spring Cloud Stream provides the interfaces Source, Sink, and Processor; you can also define your own interfaces.
The following is the definition of the Sink interface:
public interface Sink {
- String INPUT = "input";
-
- @Input(Sink.INPUT)
- SubscribableChannel input();
-}
-The @Input annotation identifies an input channel, through which received messages enter the application; the @Output annotation identifies an output channel, through which published messages leave the application.
-The @Input and @Output annotations can take a channel name as a parameter; if a name is not provided, the name of the annotated method will be used.
Spring Cloud Stream will create an implementation of the interface for you. -You can use this in the application by autowiring it, as in the following example of a test case.
-@RunWith(SpringJUnit4ClassRunner.class)
-@SpringApplicationConfiguration(classes = VoteRecordingSinkApplication.class)
-@WebAppConfiguration
-@DirtiesContext
-public class StreamApplicationTests {
-
- @Autowired
- private Sink sink;
-
- @Test
- public void contextLoads() {
- assertNotNull(this.sink.input());
- }
-}
-Spring Cloud Stream provides a number of abstractions and primitives that simplify the writing of message-driven microservice applications. -This section gives an overview of the following:
-Spring Cloud Stream’s application model
-The Binder abstraction
-Persistent publish-subscribe support
-Consumer group support
-Partitioning support
-A pluggable Binder API
-A Spring Cloud Stream application consists of a middleware-neutral core. -The application communicates with the outside world through input and output channels injected into it by Spring Cloud Stream. -Channels are connected to external brokers through middleware-specific Binder implementations.
-
-Spring Cloud Stream applications can be run in standalone mode from your IDE for testing. -To run a Spring Cloud Stream application in production, you can create an executable (or "fat") JAR by using the standard Spring Boot tooling provided for Maven or Gradle.
-Spring Cloud Stream provides Binder implementations for Kafka and Rabbit MQ. -Spring Cloud Stream also includes a TestSupportBinder, which leaves a channel unmodified so that tests can interact with channels directly and reliably assert on what is received. -You can use the extensible API to write your own Binder.
-Spring Cloud Stream uses Spring Boot for configuration, and the Binder abstraction makes it possible for a Spring Cloud Stream application to be flexible in how it connects to middleware.
-For example, deployers can dynamically choose, at runtime, the destinations (e.g., the Kafka topics or RabbitMQ exchanges) to which channels connect.
-Such configuration can be provided through external configuration properties and in any form supported by Spring Boot (including application arguments, environment variables, and application.yml or application.properties files).
-In the sink example from the Introducing Spring Cloud Stream section, setting the application property spring.cloud.stream.bindings.input.destination to raw-sensor-data will cause it to read from the raw-sensor-data Kafka topic, or from a queue bound to the raw-sensor-data RabbitMQ exchange.
Spring Cloud Stream automatically detects and uses a binder found on the classpath. -You can easily use different types of middleware with the same code: just include a different binder at build time. -For more complex use cases, you can also package multiple binders with your application and have it choose the binder, and even whether to use different binders for different channels, at runtime.
-Communication between applications follows a publish-subscribe model, where data is broadcast through shared topics. -This can be seen in the following figure, which shows a typical deployment for a set of interacting Spring Cloud Stream applications.
-
-Data reported by sensors to an HTTP endpoint is sent to a common destination named raw-sensor-data.
-From the destination, it is independently processed by a microservice application that computes time-windowed averages and by another microservice application that ingests the raw data into HDFS.
-In order to process the data, both applications declare the topic as their input at runtime.
The publish-subscribe communication model reduces the complexity of both the producer and the consumer, and allows new applications to be added to the topology without disruption of the existing flow. -For example, downstream from the average-calculating application, you can add an application that calculates the highest temperature values for display and monitoring. -You can then add another application that interprets the same flow of averages for fault detection. -Doing all communication through shared topics rather than point-to-point queues reduces coupling between microservices.
-While the concept of publish-subscribe messaging is not new, Spring Cloud Stream takes the extra step of making it an opinionated choice for its application model. -By using native middleware support, Spring Cloud Stream also simplifies use of the publish-subscribe model across different platforms.
-While the publish-subscribe model makes it easy to connect applications through shared topics, the ability to scale up by creating multiple instances of a given application is equally important. -When doing this, different instances of an application are placed in a competing consumer relationship, where only one of the instances is expected to handle a given message.
-Spring Cloud Stream models this behavior through the concept of a consumer group.
-(Spring Cloud Stream consumer groups are similar to and inspired by Kafka consumer groups.)
-Each consumer binding can use the spring.cloud.stream.bindings.<channelName>.group property to specify a group name.
-For the consumers shown in the following figure, this property would be set as spring.cloud.stream.bindings.<channelName>.group=hdfsWrite or spring.cloud.stream.bindings.<channelName>.group=average.
-All groups which subscribe to a given destination receive a copy of published data, but only one member of each group receives a given message from that destination. -By default, when a group is not specified, Spring Cloud Stream assigns the application to an anonymous and independent single-member consumer group that is in a publish-subscribe relationship with all other consumer groups.
-Consistent with the opinionated application model of Spring Cloud Stream, consumer group subscriptions are durable. -That is, a binder implementation ensures that group subscriptions are persistent, and once at least one subscription for a group has been created, the group will receive messages, even if they are sent while all applications in the group are stopped.
-|
- Note
- |
-
-
-
-Anonymous subscriptions are non-durable by nature. -For some binder implementations (e.g., RabbitMQ), it is possible to have non-durable group subscriptions. - |
-
In general, it is preferable to always specify a consumer group when binding an application to a given destination. -When scaling up a Spring Cloud Stream application, you must specify a consumer group for each of its input bindings. -This prevents the application’s instances from receiving duplicate messages (unless that behavior is desired, which is unusual).
-Spring Cloud Stream provides support for partitioning data between multiple instances of a given application. -In a partitioned scenario, the physical communication medium (e.g., the broker topic) is viewed as being structured into multiple partitions. -One or more producer application instances send data to multiple consumer application instances and ensure that data identified by common characteristics are processed by the same consumer instance.
-Spring Cloud Stream provides a common abstraction for implementing partitioned processing use cases in a uniform fashion. -Partitioning can thus be used whether the broker itself is naturally partitioned (e.g., Kafka) or not (e.g., RabbitMQ).
-
-Partitioning is a critical concept in stateful processing, where it is critiical, for either performance or consistency reasons, to ensure that all related data is processed together. -For example, in the time-windowed average calculation example, it is important that all measurements from any given sensor are processed by the same application instance.
-|
- Note
- |
-
-
-
-To set up a partitioned processing scenario, you must configure both the data-producing and the data-consuming ends. - |
-
This section describes Spring Cloud Stream’s programming model. -Spring Cloud Stream provides a number of predefined annotations for declaring bound input and output channels as well as how to listen to channels.
-@EnableBindingYou can turn a Spring application into a Spring Cloud Stream application by applying the @EnableBinding annotation to one of the application’s configuration classes.
-The @EnableBinding annotation itself is meta-annotated with @Configuration and triggers the configuration of Spring Cloud Stream infrastructure:
...
-@Import(...)
-@Configuration
-@EnableIntegration
-public @interface EnableBinding {
- ...
- Class<?>[] value() default {};
-}
-The @EnableBinding annotation can take as parameters one or more interface classes that contain methods which represent bindable components (typically message channels).
|
- Note
- |
-
-
-
-In Spring Cloud Stream 1.0, the only supported bindable components are the Spring Messaging |
-
@Input and @OutputA Spring Cloud Stream application can have an arbitrary number of input and output channels defined in an interface as @Input and @Output methods:
public interface Barista {
-
- @Input
- SubscribableChannel orders();
-
- @Output
- MessageChannel hotDrinks();
-
- @Output
- MessageChannel coldDrinks();
-}
-Using this interface as a parameter to @EnableBinding will trigger the creation of three bound channels named orders, hotDrinks, and coldDrinks, respectively.
@EnableBinding(Barista.class)
-public class CafeConfiguration {
-
- ...
-}
-Using the @Input and @Output annotations, you can specify a customized channel name for the channel, as shown in the following example:
public interface Barista {
- ...
- @Input("inboundOrders")
- SubscribableChannel orders();
-}
-In this example, the created bound channel will be named inboundOrders.
Source, Sink, and ProcessorFor easy addressing of the most common use cases, which involve either an input channel, an output channel, or both, Spring Cloud Stream provides three predefined interfaces out of the box.
-Source can be used for an application which has a single outbound channel.
public interface Source {
-
- String OUTPUT = "output";
-
- @Output(Source.OUTPUT)
- MessageChannel output();
-
-}
-Sink can be used for an application which has a single inbound channel.
public interface Sink {
-
- String INPUT = "input";
-
- @Input(Sink.INPUT)
- SubscribableChannel input();
-
-}
-Processor can be used for an application which has both an inbound channel and an outbound channel.
public interface Processor extends Source, Sink {
-}
-Spring Cloud Stream provides no special handling for any of these interfaces; they are only provided out of the box.
-For each bound interface, Spring Cloud Stream will generate a bean that implements the interface.
-Invoking a @Input-annotated or @Output-annotated method of one of these beans will return the relevant bound channel.
The bean in the following example sends a message on the output channel when its hello method is invoked.
-It invokes output() on the injected Source bean to retrieve the target channel.
@Component
-public class SendingBean {
-
- private Source source;
-
- @Autowired
- public SendingBean(Source source) {
- this.source = source;
- }
-
- public void sayHello(String name) {
- source.output().send(MessageBuilder.withPayload(name).build());
- }
-}
-Bound channels can be also injected directly:
-@Component
-public class SendingBean {
-
- private MessageChannel output;
-
- @Autowired
- public SendingBean(MessageChannel output) {
- this.output = output;
- }
-
- public void sayHello(String name) {
- output.send(MessageBuilder.withPayload(name).build());
- }
-}
-If the name of the channel is customized on the declaring annotation, that name should be used instead of the method name. -Given the following declaration:
-public interface CustomSource {
- ...
- @Output("customOutput")
- MessageChannel output();
-}
-The channel will be injected as shown in the following example:
-@Component
-public class SendingBean {
-
- private MessageChannel output;
-
- @Autowired
- public SendingBean(@Qualifier("customOutput") MessageChannel output) {
- this.output = output;
- }
-
- public void sayHello(String name) {
- this.output.send(MessageBuilder.withPayload(name).build());
- }
-}
-You can write a Spring Cloud Stream application using either Spring Integration annotations or Spring Cloud Stream’s @StreamListener annotation.
-The @StreamListener annotation is modeled after other Spring Messaging annotations (such as @MessageMapping, @JmsListener, @RabbitListener, etc.) but adds content type management and type coercion features.
Because Spring Cloud Stream is based on Spring Integration, Stream completely inherits Integration’s foundation and infrastructure as well as the component itself.
-For example, you can attach the output channel of a Source to a MessageSource:
@EnableBinding(Source.class)
-public class TimerSource {
-
- @Value("${format}")
- private String format;
-
- @Bean
- @InboundChannelAdapter(value = Source.OUTPUT, poller = @Poller(fixedDelay = "${fixedDelay}", maxMessagesPerPoll = "1"))
- public MessageSource<String> timerMessageSource() {
- return () -> new GenericMessage<>(new SimpleDateFormat(format).format(new Date()));
- }
-}
-Or you can use a processor’s channels in a transformer:
-@EnableBinding(Processor.class)
-public class TransformProcessor {
- @Transformer(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT)
- public Object transform(String message) {
- return message.toUpperCase();
- }
-}
-Complementary to its Spring Integration support, Spring Cloud Stream provides its own @StreamListener annotation, modeled after other Spring Messaging annotations (e.g. @MessageMapping, @JmsListener, @RabbitListener, etc.).
-The @StreamListener annotation provides a simpler model for handling inbound messages, especially when dealing with use cases that involve content type management and type coercion.
Spring Cloud Stream provides an extensible MessageConverter mechanism for handling data conversion by bound channels and for, in this case, dispatching to methods annotated with @StreamListener.
-The following is an example of an application which processes external Vote events:
@EnableBinding(Sink.class)
-public class VoteHandler {
-
- @Autowired
- VotingService votingService;
-
- @StreamListener(Sink.INPUT)
- public void handle(Vote vote) {
- votingService.record(vote);
- }
-}
-The distinction between @StreamListener and a Spring Integration @ServiceActivator is seen when considering an inbound Message that has a String payload and a contentType header of application/json.
-In the case of @StreamListener, the MessageConverter mechanism will use the contentType header to parse the String payload into a Vote object.
As with other Spring Messaging methods, method arguments can be annotated with @Payload, @Headers and @Header.
|
- Note
- |
-
-
-
-For methods which return data, you must use the |
-
@EnableBinding(Processor.class)
-public class TransformProcessor {
-
- @Autowired
- VotingService votingService;
-
- @StreamListener(Processor.INPUT)
- @SendTo(Processor.OUTPUT)
- public VoteResult handle(Vote vote) {
- return votingService.record(vote);
- }
-}
-==== Reactive Programming Support
-Spring Cloud Stream also supports the use of reactive APIs where incoming and outgoing data is handled as continuous data flows.
-Support for reactive APIs is available via the spring-cloud-stream-reactive, which needs to be added explicitly to your project.
The programming model with reactive APIs is declarative, where instead of specifying how each individual message should be handled, you can use operators that describe functional transformations from inbound to outbound data flows.
-Spring Cloud Stream supports the following reactive APIs:
-Reactor
-RxJava 1.x
-In the future, it is intended to support a more generic model based on Reactive Streams.
-The reactive programming model is also using the @StreamListener annotation for setting up reactive handlers. The differences are that:
the @StreamListener annotation must not specify an input or output, as they are provided as arguments and return values from the method;
the arguments of the method must be annotated with @Input and @Output indicating which input or output will the incoming and respectively outgoing data flows connect to;
the return value of the method, if any, will be annotated with @Output, indicating the input where data shall be sent.
Reactive programming support requires Java 1.8.
-As of Spring Cloud Stream 1.1.1 and later (starting with release train Brooklyn.SR2), reactive programming support requires the use of Reactor 3.0.4.RELEASE and higher.
-Earlier Reactor versions (including 3.0.1.RELEASE, 3.0.2.RELEASE and 3.0.3.RELEASE) are not supported.
-spring-cloud-stream-reactive will transitively retrieve the proper version, but it is possible for the project structure to manage the version of the io.projectreactor:reactor-core to an earlier release, especially when using Maven.
-This is the case for projects generated via Spring Initializr with Spring Boot 1.x, which will override the Reactor version to 2.0.8.RELEASE.
-In such cases you must ensure that the proper version of the artifact is released.
-This can be simply achieved by adding a direct dependency on io.projectreactor:reactor-core with a version of 3.0.4.RELEASE or later to your project.
The use of term reactive is currently referring to the reactive APIs being used and not to the execution model being reactive (i.e. the bound endpoints are still using a 'push' rather than 'pull' model). While some backpressure support is provided by the use of Reactor, we do intend on the long run to support entirely reactive pipelines by the use of native reactive clients for the connected middleware.
===== Reactor-based handlers
-A Reactor based handler can have the following argument types:
-For arguments annotated with @Input, it supports the Reactor type Flux.
-The parameterization of the inbound Flux follows the same rules as in the case of individual message handling: it can be the entire Message, a POJO which can be the Message payload, or a POJO which is the result of a transformation based on the Message content-type header. Multiple inputs are provided;
For arguments annotated with Output, it supports the type FluxSender which connects a Flux produced by the method with an output. Generally speaking, specifying outputs as arguments is only recommended when the method can have multiple outputs;
A Reactor based handler supports a return type of Flux, case in which it must be annotated with @Output. We recommend using the return value of the method when a single output flux is available.
Here is an example of a simple Reactor-based Processor.
-@EnableBinding(Processor.class)
-@EnableAutoConfiguration
-public static class UppercaseTransformer {
-
- @StreamListener
- @Output(Processor.OUTPUT)
- public Flux<String> receive(@Input(Processor.INPUT) Flux<String> input) {
- return input.map(s -> s.toUpperCase());
- }
-}
-The same processor using output arguments looks like this:
-@EnableBinding(Processor.class)
-@EnableAutoConfiguration
-public static class UppercaseTransformer {
-
- @StreamListener
- public void receive(@Input(Processor.INPUT) Flux<String> input,
- @Output(Processor.OUTPUT) FluxSender output) {
- output.send(input.map(s -> s.toUpperCase()));
- }
-}
-===== RxJava 1.x support
-RxJava 1.x handlers follow the same rules as Reactor-based one, but will use Observable and ObservableSender arguments and return types.
So the first example above will become:
-@EnableBinding(Processor.class)
-@EnableAutoConfiguration
-public static class UppercaseTransformer {
-
- @StreamListener
- @Output(Processor.OUTPUT)
- public Observable<String> receive(@Input(Processor.INPUT) Observable<String> input) {
- return input.map(s -> s.toUpperCase());
- }
-}
-The second example above will become:
-@EnableBinding(Processor.class)
-@EnableAutoConfiguration
-public static class UppercaseTransformer {
-
- @StreamListener
- public void receive(@Input(Processor.INPUT) Observable<String> input,
- @Output(Processor.OUTPUT) ObservableSender output) {
- output.send(input.map(s -> s.toUpperCase()));
- }
-}
-==== Aggregation
-Spring Cloud Stream provides support for aggregating multiple applications together, connecting their input and output channels directly and avoiding the additional cost of exchanging messages via a broker. -As of version 1.0 of Spring Cloud Stream, aggregation is supported only for the following types of applications:
-sources - applications with a single output channel named output, typically having a single binding of the type org.springframework.cloud.stream.messaging.Source
sinks - applications with a single input channel named input, typically having a single binding of the type org.springframework.cloud.stream.messaging.Sink
processors - applications with a single input channel named input and a single output channel named output, typically having a single binding of the type org.springframework.cloud.stream.messaging.Processor.
They can be aggregated together by creating a sequence of interconnected applications, in which the output channel of an element in the sequence is connected to the input channel of the next element, if it exists. -A sequence can start with either a source or a processor, it can contain an arbitrary number of processors and must end with either a processor or a sink.
-Depending on the nature of the starting and ending element, the sequence may have one or more bindable channels, as follows:
-if the sequence starts with a source and ends with a sink, all communication between the applications is direct and no channels will be bound
-if the sequence starts with a processor, then its input channel will become the input channel of the aggregate and will be bound accordingly
if the sequence ends with a processor, then its output channel will become the output channel of the aggregate and will be bound accordingly
Aggregation is performed using the AggregateApplicationBuilder utility class, as in the following example.
-Let’s consider a project in which we have source, processor and a sink, which may be defined in the project, or may be contained in one of the project’s dependencies.
Each component (source, sink or processor) in an aggregate application must be provided in a separate package if the configuration classes use @SpringBootApplication.
-This is required to avoid cross-talk between applications, due to the classpath scanning performed by @SpringBootApplication on the configuration classes inside the same package.
-In the example below, it can be seen that the Source, Processor and Sink application classes are grouped in separate packages.
-A possible alternative is to provide the source, sink or processor configuration in a separate @Configuration class, avoid the use of @SpringBootApplication/@ComponentScan and use those for aggregation.
package com.app.mysink;
-
-@SpringBootApplication
-@EnableBinding(Sink.class)
-public class SinkApplication {
-
- private static Logger logger = LoggerFactory.getLogger(SinkApplication.class);
-
- @ServiceActivator(inputChannel=Sink.INPUT)
- public void loggerSink(Object payload) {
- logger.info("Received: " + payload);
- }
-}
-package com.app.myprocessor;
-
-@SpringBootApplication
-@EnableBinding(Processor.class)
-public class ProcessorApplication {
-
- @Transformer
- public String loggerSink(String payload) {
- return payload.toUpperCase();
- }
-}
-package com.app.mysource;
-
-@SpringBootApplication
-@EnableBinding(Source.class)
-public class SourceApplication {
-
- @Bean
- @InboundChannelAdapter(value = Source.OUTPUT)
- public String timerMessageSource() {
- return new SimpleDateFormat().format(new Date());
- }
-}
-Each configuration can be used for running a separate component, but in this case they can be aggregated together as follows:
-package com.app;
-
-@SpringBootApplication
-public class SampleAggregateApplication {
-
- public static void main(String[] args) {
- new AggregateApplicationBuilder()
- .from(SourceApplication.class).args("--fixedDelay=5000")
- .via(ProcessorApplication.class)
- .to(SinkApplication.class).args("--debug=true").run(args);
- }
-}
-The starting component of the sequence is provided as argument to the from() method.
-The ending component of the sequence is provided as argument to the to() method.
-Intermediate processors are provided as argument to the via() method.
-Multiple processors of the same type can be chained together (e.g. for pipelining transformations with different configurations).
-For each component, the builder can provide runtime arguments for Spring Boot configuration.
===== Configuring aggregate application
-Spring Cloud Stream supports passing properties for the individual applications inside the aggregate application using 'namespace' as prefix.
-The namespace can be set for applications as follows:
-@SpringBootApplication
-public class SampleAggregateApplication {
-
- public static void main(String[] args) {
- new AggregateApplicationBuilder()
- .from(SourceApplication.class).namespace("source").args("--fixedDelay=5000")
- .via(ProcessorApplication.class).namespace("processor1")
- .to(SinkApplication.class).namespace("sink").args("--debug=true").run(args);
- }
-}
-Once the 'namespace' is set for the individual applications, the application properties with the namespace as prefix can be passed to the aggregate application using any supported property source (commandline, environment properties etc.,)
For instance, to override the default fixedDelay and debug properties of 'source' and 'sink' applications:
java -jar target/MyAggregateApplication-0.0.1-SNAPSHOT.jar --source.fixedDelay=10000 --sink.debug=false
-== Binders
-Spring Cloud Stream provides a Binder abstraction for use in connecting to physical destinations at the external middleware. -This section provides information about the main concepts behind the Binder SPI, its main components, and implementation-specific details.
-=== Producers and Consumers
-
-A producer is any component that sends messages to a channel.
-The channel can be bound to an external message broker via a Binder implementation for that broker.
-When invoking the bindProducer() method, the first parameter is the name of the destination within the broker, the second parameter is the local channel instance to which the producer will send messages, and the third parameter contains properties (such as a partition key expression) to be used within the adapter that is created for that channel.
A consumer is any component that receives messages from a channel.
-As with a producer, the consumer’s channel can be bound to an external message broker.
-When invoking the bindConsumer() method, the first parameter is the destination name, and a second parameter provides the name of a logical group of consumers.
-Each group that is represented by consumer bindings for a given destination receives a copy of each message that a producer sends to that destination (i.e., publish-subscribe semantics).
-If there are multiple consumer instances bound using the same group name, then messages will be load-balanced across those consumer instances so that each message sent by a producer is consumed by only a single consumer instance within each group (i.e., queueing semantics).
=== Binder SPI
-The Binder SPI consists of a number of interfaces, out-of-the box utility classes and discovery strategies that provide a pluggable mechanism for connecting to external middleware.
-The key point of the SPI is the Binder interface which is a strategy for connecting inputs and outputs to external middleware.
public interface Binder<T, C extends ConsumerProperties, P extends ProducerProperties> {
- Binding<T> bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties);
-
- Binding<T> bindProducer(String name, T outboundBindTarget, P producerProperties);
-}
-The interface is parameterized, offering a number of extension points:
-input and output bind targets - as of version 1.0, only MessageChannel is supported, but this is intended to be used as an extension point in the future;
extended consumer and producer properties - allowing specific Binder implementations to add supplemental properties which can be supported in a type-safe manner.
-A typical binder implementation consists of the following
-a class that implements the Binder interface;
a Spring @Configuration class that creates a bean of the type above along with the middleware connection infrastructure;
a META-INF/spring.binders file found on the classpath containing one or more binder definitions, e.g.
kafka:\
-org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfiguration
-=== Binder Detection
-Spring Cloud Stream relies on implementations of the Binder SPI to perform the task of connecting channels to message brokers. -Each Binder implementation typically connects to one type of messaging system.
-==== Classpath Detection
-By default, Spring Cloud Stream relies on Spring Boot’s auto-configuration to configure the binding process. -If a single Binder implementation is found on the classpath, Spring Cloud Stream will use it automatically. -For example, a Spring Cloud Stream project that aims to bind only to RabbitMQ can simply add the following dependency:
-<dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
-</dependency>
-For the specific maven coordinates of other binder dependencies, please refer to the documentation of that binder implementation.
-=== Multiple Binders on the Classpath
-When multiple binders are present on the classpath, the application must indicate which binder is to be used for each channel binding.
-Each binder configuration contains a META-INF/spring.binders, which is a simple properties file:
rabbit:\
-org.springframework.cloud.stream.binder.rabbit.config.RabbitServiceAutoConfiguration
-Similar files exist for the other provided binder implementations (e.g., Kafka), and custom binder implementations are expected to provide them, as well.
-The key represents an identifying name for the binder implementation, whereas the value is a comma-separated list of configuration classes that each contain one and only one bean definition of type org.springframework.cloud.stream.binder.Binder.
Binder selection can either be performed globally, using the spring.cloud.stream.defaultBinder property (e.g., spring.cloud.stream.defaultBinder=rabbit) or individually, by configuring the binder on each channel binding.
-For instance, a processor application (that has channels with the names input and output for read/write respectively) which reads from Kafka and writes to RabbitMQ can specify the following configuration:
spring.cloud.stream.bindings.input.binder=kafka -spring.cloud.stream.bindings.output.binder=rabbit-
=== Connecting to Multiple Systems
-By default, binders share the application’s Spring Boot auto-configuration, so that one instance of each binder found on the classpath will be created. -If your application should connect to more than one broker of the same type, you can specify multiple binder configurations, each with different environment settings.
-Turning on explicit binder configuration will disable the default binder configuration process altogether.
-If you do this, all binders in use must be included in the configuration.
-Frameworks that intend to use Spring Cloud Stream transparently may create binder configurations that can be referenced by name, but will not affect the default binder configuration.
-In order to do so, a binder configuration may have its defaultCandidate flag set to false, e.g. spring.cloud.stream.binders.<configurationName>.defaultCandidate=false.
-This denotes a configuration that will exist independently of the default binder configuration process.
For example, this is the typical configuration for a processor application which connects to two RabbitMQ broker instances:
-spring:
- cloud:
- stream:
- bindings:
- input:
- destination: foo
- binder: rabbit1
- output:
- destination: bar
- binder: rabbit2
- binders:
- rabbit1:
- type: rabbit
- environment:
- spring:
- rabbitmq:
- host: <host1>
- rabbit2:
- type: rabbit
- environment:
- spring:
- rabbitmq:
- host: <host2>
-=== Binder configuration properties
-The following properties are available when creating custom binder configurations.
-They must be prefixed with spring.cloud.stream.binders.<configurationName>.
The binder type.
-It typically references one of the binders found on the classpath, in particular a key in a META-INF/spring.binders file.
By default, it has the same value as the configuration name.
-Whether the configuration will inherit the environment of the application itself.
-Default true.
Root for a set of properties that can be used to customize the environment of the binder. -When this is configured, the context in which the binder is being created is not a child of the application context. -This allows for complete separation between the binder components and the application components.
-Default empty.
Whether the binder configuration is a candidate for being considered a default binder, or can be used only when explicitly referenced. -This allows adding binder configurations without interfering with the default processing.
-Default true.
== Configuration Options
-Spring Cloud Stream supports general configuration options as well as configuration for bindings and binders. -Some binders allow additional binding properties to support middleware-specific features.
-Configuration options can be provided to Spring Cloud Stream applications via any mechanism supported by Spring Boot. -This includes application arguments, environment variables, and YAML or .properties files.
-=== Spring Cloud Stream Properties
-The number of deployed instances of an application. -Must be set for partitioning and if using Kafka.
-Default: 1.
The instance index of the application: a number from 0 to instanceCount-1.
-Used for partitioning and with Kafka.
-Automatically set in Cloud Foundry to match the application’s instance index.
A list of destinations that can be bound dynamically (for example, in a dynamic routing scenario). -If set, only listed destinations can be bound.
-Default: empty (allowing any destination to be bound).
-The default binder to use, if multiple binders are configured. -See Multiple Binders on the Classpath.
-Default: empty.
- This property is only applicable when the cloud profile is active and Spring Cloud Connectors are provided with the application.
-If the property is false (the default), the binder will detect a suitable bound service (e.g. a RabbitMQ service bound in Cloud Foundry for the RabbitMQ binder) and will use it for creating connections (usually via Spring Cloud Connectors).
-When set to true, this property instructs binders to completely ignore the bound services and rely on Spring Boot properties (e.g. relying on the spring.rabbitmq.* properties provided in the environment for the RabbitMQ binder).
-The typical usage of this property is to be nested in a customized environment when connecting to multiple systems.
Default: false.
-=== Binding Properties
-Binding properties are supplied using the format spring.cloud.stream.bindings.<channelName>.<property>=<value>.
-The <channelName> represents the name of the channel being configured (e.g., output for a Source).
To avoid repetition, Spring Cloud Stream supports setting values for all channels, in the format spring.cloud.stream.default.<property>=<value>.
In what follows, we indicate where we have omitted the spring.cloud.stream.bindings.<channelName>. prefix and focus just on the property name, with the understanding that the prefix will be included at runtime.
==== Properties for Use of Spring Cloud Stream
-The following binding properties are available for both input and output bindings and must be prefixed with spring.cloud.stream.bindings.<channelName>., e.g. spring.cloud.stream.bindings.input.destination=ticktock.
Default values can be set by using the prefix spring.cloud.stream.default, e.g. spring.cloud.stream.default.contentType=application/json.
The target destination of a channel on the bound middleware (e.g., the RabbitMQ exchange or Kafka topic). -If the channel is bound as a consumer, it could be bound to multiple destinations and the destination names can be specified as comma separated String values. -If not set, the channel name is used instead. -The default value of this property cannot be overridden.
-The consumer group of the channel. -Applies only to inbound bindings. -See Consumer Groups.
-Default: null (indicating an anonymous consumer).
-The content type of the channel.
-Default: null (so that no type coercion is performed).
-The binder used by this binding. -See [multiple-binders] for details.
-Default: null (the default binder will be used, if one exists).
-==== Consumer properties
-The following binding properties are available for input bindings only and must be prefixed with spring.cloud.stream.bindings.<channelName>.consumer., e.g. spring.cloud.stream.bindings.input.consumer.concurrency=3.
Default values can be set by using the prefix spring.cloud.stream.default.consumer, e.g. spring.cloud.stream.default.consumer.headerMode=raw.
The concurrency of the inbound consumer.
-Default: 1.
Whether the consumer receives data from a partitioned producer.
-Default: false.
When set to raw, disables header parsing on input.
-Effective only for messaging middleware that does not support message headers natively and requires header embedding.
-Useful when inbound data is coming from outside Spring Cloud Stream applications.
Default: embeddedHeaders.
If processing fails, the number of attempts to process the message (including the first). -Set to 1 to disable retry.
-Default: 3.
The backoff initial interval on retry.
-Default: 1000.
The maximum backoff interval.
-Default: 10000.
The backoff multiplier.
-Default: 2.0.
When set to a value greater than equal to zero, allows customizing the instance index of this consumer (if different from spring.cloud.stream.instanceIndex).
-When set to a negative value, it will default to spring.cloud.stream.instanceIndex.
Default: -1.
When set to a value greater than equal to zero, allows customizing the instance count of this consumer (if different from spring.cloud.stream.instanceCount).
-When set to a negative value, it will default to spring.cloud.stream.instanceCount.
Default: -1.
==== Producer Properties
-The following binding properties are available for output bindings only and must be prefixed with spring.cloud.stream.bindings.<channelName>.producer., e.g. spring.cloud.stream.bindings.input.producer.partitionKeyExpression=payload.id.
Default values can be set by using the prefix spring.cloud.stream.default.producer, e.g. spring.cloud.stream.default.producer.partitionKeyExpression=payload.id.
A SpEL expression that determines how to partition outbound data.
-If set, or if partitionKeyExtractorClass is set, outbound data on this channel will be partitioned, and partitionCount must be set to a value greater than 1 to be effective.
-The two options are mutually exclusive.
-See Partitioning Support.
Default: null.
- A PartitionKeyExtractorStrategy implementation.
-If set, or if partitionKeyExpression is set, outbound data on this channel will be partitioned, and partitionCount must be set to a value greater than 1 to be effective.
-The two options are mutually exclusive.
-See Partitioning Support.
Default: null.
- A PartitionSelectorStrategy implementation.
-Mutually exclusive with partitionSelectorExpression.
-If neither is set, the partition will be selected as the hashCode(key) % partitionCount, where key is computed via either partitionKeyExpression or partitionKeyExtractorClass.
Default: null.
- A SpEL expression for customizing partition selection.
-Mutually exclusive with partitionSelectorClass.
-If neither is set, the partition will be selected as the hashCode(key) % partitionCount, where key is computed via either partitionKeyExpression or partitionKeyExtractorClass.
Default: null.
-The number of target partitions for the data, if partitioning is enabled. -Must be - set to a value greater than 1 if the producer is partitioned. -On Kafka, interpreted as a - hint; the larger of this and the partition count of the target topic is used instead.
-Default: 1.
A comma-separated list of groups to which the producer must ensure message delivery even if they start after it has been created (e.g., by pre-creating durable queues in RabbitMQ).
- When set to raw, disables header embedding on output.
-Effective only for messaging middleware that does not support message headers natively and requires header embedding.
-Useful when producing data for non-Spring Cloud Stream applications.
Default: embeddedHeaders.
When set to true, the outbound message is serialized directly by client library, which must be configured correspondingly (e.g. setting an appropriate Kafka producer value serializer).
-When this configuration is being used, the outbound message marshalling is not based on the contentType of the binding.
-When native encoding is used, it is the responsibility of the consumer to use appropriate decoder (ex: Kafka consumer value de-serializer) to deserialize the inbound message.
-Also, when native encoding/decoding is used the headerMode property is ignored and headers will not be embedded into the message.
Default: false.
=== Using dynamically bound destinations
-Besides the channels defined via @EnableBinding, Spring Cloud Stream allows applications to send messages to dynamically bound destinations.
-This is useful, for example, when the target destination needs to be determined at runtime.
-Applications can do so by using the BinderAwareChannelResolver bean, registered automatically by the @EnableBinding annotation.
The property 'spring.cloud.stream.dynamicDestinations' can be used for restricting the dynamic destination names to a set known beforehand (whitelisting). -If the property is not set, any destination can be bound dynamicaly.
-The BinderAwareChannelResolver can be used directly as in the following example, in which a REST controller uses a path variable to decide the target channel.
@EnableBinding
-@Controller
-public class SourceWithDynamicDestination {
-
- @Autowired
- private BinderAwareChannelResolver resolver;
-
- @RequestMapping(path = "/{target}", method = POST, consumes = "*/*")
- @ResponseStatus(HttpStatus.ACCEPTED)
- public void handleRequest(@RequestBody String body, @PathVariable("target") target,
- @RequestHeader(HttpHeaders.CONTENT_TYPE) Object contentType) {
- sendMessage(body, target, contentType);
- }
-
- private void sendMessage(String body, String target, Object contentType) {
- resolver.resolveDestination(target).send(MessageBuilder.createMessage(body,
- new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, contentType))));
- }
-}
-After starting the application on the default port 8080, when sending the following data:
-curl -H "Content-Type: application/json" -X POST -d "customer-1" http://localhost:8080/customers - -curl -H "Content-Type: application/json" -X POST -d "order-1" http://localhost:8080/orders-
The destinations 'customers' and 'orders' are created in the broker (for example: exchange in case of Rabbit or topic in case of Kafka) with the names 'customers' and 'orders', and the data is published to the appropriate destinations.
-The BinderAwareChannelResolver is a general purpose Spring Integration DestinationResolver and can be injected in other components.
-For example, in a router using a SpEL expression based on the target field of an incoming JSON message.
@EnableBinding
-@Controller
-public class SourceWithDynamicDestination {
-
- @Autowired
- private BinderAwareChannelResolver resolver;
-
-
- @RequestMapping(path = "/", method = POST, consumes = "application/json")
- @ResponseStatus(HttpStatus.ACCEPTED)
- public void handleRequest(@RequestBody String body, @RequestHeader(HttpHeaders.CONTENT_TYPE) Object contentType) {
- sendMessage(body, contentType);
- }
-
- private void sendMessage(Object body, Object contentType) {
- routerChannel().send(MessageBuilder.createMessage(body,
- new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, contentType))));
- }
-
- @Bean(name = "routerChannel")
- public MessageChannel routerChannel() {
- return new DirectChannel();
- }
-
- @Bean
- @ServiceActivator(inputChannel = "routerChannel")
- public ExpressionEvaluatingRouter router() {
- ExpressionEvaluatingRouter router =
- new ExpressionEvaluatingRouter(new SpelExpressionParser().parseExpression("payload.target"));
- router.setDefaultOutputChannelName("default-output");
- router.setChannelResolver(resolver);
- return router;
- }
-}
-== Content Type and Transformation
-To allow you to propagate information about the content type of produced messages, Spring Cloud Stream attaches, by default, a contentType header to outbound messages.
-For middleware that does not directly support headers, Spring Cloud Stream provides its own mechanism of automatically wrapping outbound messages in an envelope of its own.
-For middleware that does support headers, Spring Cloud Stream applications may receive messages with a given content type from non-Spring Cloud Stream applications.
Spring Cloud Stream can handle messages based on this information in two ways:
-Through its contentType settings on inbound and outbound channels
Through its argument mapping performed for methods annotated with @StreamListener
Spring Cloud Stream allows you to declaratively configure type conversion for inputs and outputs using the spring.cloud.stream.bindings.<channelName>.content-type property of a binding.
-Note that general type conversion may also be accomplished easily by using a transformer inside your application.
-Currently, Spring Cloud Stream natively supports the following type conversions commonly used in streams:
JSON to/from POJO
-JSON to/from org.springframework.tuple.Tuple
-Object to/from byte[] : Either the raw bytes serialized for remote transport, bytes emitted by an application, or converted to bytes using Java serialization(requires the object to be Serializable)
-String to/from byte[]
-Object to plain text (invokes the object’s toString() method)
-Where JSON represents either a byte array or String payload containing JSON. -Currently, Objects may be converted from a JSON byte array or String. -Converting to JSON always produces a String.
-=== MIME types
-content-type values are parsed as media types, e.g., application/json or text/plain;charset=UTF-8.
-MIME types are especially useful for indicating how to convert to String or byte[] content.
-Spring Cloud Stream also uses MIME type format to represent Java types, using the general type application/x-java-object with a type parameter.
-For example, application/x-java-object;type=java.util.Map or application/x-java-object;type=com.bar.Foo can be set as the content-type property of an input binding.
-In addition, Spring Cloud Stream provides custom MIME types, notably, application/x-spring-tuple to specify a Tuple.
=== MIME types and Java types
-The type conversions Spring Cloud Stream provides out of the box are summarized in the following table:
-| Source Payload | -Target Payload | -content-type header | -content-type | -Comments | -
|---|---|---|---|---|
POJO |
-JSON String |
-ignored |
-application/json |
-- |
Tuple |
-JSON String |
-ignored |
-application/json |
-JSON is tailored for Tuple |
-
POJO |
-String (toString()) |
-ignored |
-text/plain, java.lang.String |
-- |
POJO |
-byte[] (java.io serialized) |
-ignored |
-application/x-java-serialized-object |
-- |
JSON byte[] or String |
-POJO |
-application/json (or none) |
-application/x-java-object |
-- |
byte[] or String |
-Serializable |
-application/x-java-serialized-object |
-application/x-java-object |
-- |
JSON byte[] or String |
-Tuple |
-application/json (or none) |
-application/x-spring-tuple |
-- |
byte[] |
-String |
-any |
-text/plain, java.lang.String |
-will apply any Charset specified in the content-type header |
-
String |
-byte[] |
-any |
-application/octet-stream |
-will apply any Charset specified in the content-type header |
-
Conversion applies to payloads that require type conversion. -For example, if a module produces an XML string with outputType=application/json, the payload will not be converted from XML to JSON. -This is because the payload at the module’s output channel is already a String so no conversion will be applied at runtime.
-While conversion is supported for both input and output channels, it is especially recommended to be used for the conversion of outbound messages.
-For the conversion of inbound messages, especially when the target is a POJO, the @StreamListener support will perform the conversion automatically.
=== Customizing message conversion
-Besides the conversions that it supports out of the box, Spring Cloud Stream also supports registering your own message conversion implementations.
-This allows you to send and receive data in a variety of custom formats, including binary, and associate them with specific contentTypes.
-Spring Cloud Stream registers all the beans of type org.springframework.messaging.converter.MessageConverter as custom message converters along with the out of the box message converters.
If your message converter needs to work with a specific content-type and target class (for both input and output), then the message converter needs to extend org.springframework.messaging.converter.AbstractMessageConverter.
-For conversion when using @StreamListener, a message converter that implements org.springframework.messaging.converter.MessageConverter would suffice.
Here is an example of creating a message converter bean (with the content-type application/bar) inside a Spring Cloud Stream application:
@EnableBinding(Sink.class)
-@SpringBootApplication
-public static class SinkApplication {
-
- ...
-
- @Bean
- public MessageConverter customMessageConverter() {
- return new MyCustomMessageConverter();
- }
-public class MyCustomMessageConverter extends AbstractMessageConverter {
-
- public MyCustomMessageConverter() {
- super(new MimeType("application", "bar"));
- }
-
- @Override
- protected boolean supports(Class<?> clazz) {
- return (Bar.class == clazz);
- }
-
- @Override
- protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
- Object payload = message.getPayload();
- return (payload instanceof Bar ? payload : new Bar((byte[]) payload));
- }
-}
-=== Schema-based message converters
-Spring Cloud Stream provides support for schema-based message converters through its spring-cloud-stream-schema module.
-Currently, the only serialization format supported out of the box is Apache Avro, with more formats to be added in future versions.
==== Apache Avro Message Converters
-The spring-cloud-stream-schema module contains two types of message converters that can be used for Apache Avro serialization:
converters using the class information of the serialized/deserialized objects, or a schema with a location known at startup;
-converters using a schema registry - they locate the schemas at runtime, as well as dynamically registering new schemas as domain objects evolve.
-===== Converters with schema support
-The AvroSchemaMessageConverter supports serializing and deserializing messages either using a predefined schema or by using the schema information available in the class (either reflectively, or contained in the SpecificRecord).
-If the target type of the conversion is a GenericRecord, then a schema must be set.
For using it, you can simply add it to the application context, optionally specifying one ore more MimeTypes to associate it with.
-The default MimeType is application/avro.
Here is an example of configuring it in a sink application registering the Apache Avro MessageConverter, without a predefined schema:
@EnableBinding(Sink.class)
-@SpringBootApplication
-public static class SinkApplication {
-
- ...
-
- @Bean
- public MessageConverter userMessageConverter() {
- return new AvroSchemaMessageConverter(MimeType.valueOf("avro/bytes"));
- }
-}
-Conversely, here is an application that registers a converter with a predefined schema, to be found on the classpath:
-@EnableBinding(Sink.class)
-@SpringBootApplication
-public static class SinkApplication {
-
- ...
-
- @Bean
- public MessageConverter userMessageConverter() {
- AvroSchemaMessageConverter converter = new AvroSchemaMessageConverter(MimeType.valueOf("avro/bytes"));
- converter.setSchemaLocation(new ClassPathResource("schemas/User.avro"));
- return converter;
- }
-}
-In order to understand the schema registry client converter, we will describe the schema registry support first.
-=== Schema Registry Support
-Most serialization models, especially the ones that aim for portability across different platforms and languages, rely on a schema that describes how the data is serialized in the binary payload. -In order to serialize the data and then to interpret it, both the sending and receiving sides must have access to a schema that describes the binary format. -In certain cases, the schema can be inferred from the payload type on serialization, or from the target type on deserialization, but in a lot of cases applications benefit from having access to an explicit schema that describes the binary data format. -A schema registry allows you to store schema information in a textual format (typically JSON) and makes that information accessible to various applications that need it to receive and send data in binary format. -A schema is referenceable as a tuple consisting of:
-a subject that is the logical name of the schema;
-the schema version;
-the schema format which describes the binary format of the data.
-==== Schema Registry Server
-Spring Cloud Stream provides a schema registry server implementation.
-In order to use it, you can simply add the spring-cloud-stream-schema-server artifact to your project and use the @EnableSchemaRegistryServer annotation, adding the schema registry server REST controller to your application.
-This annotation is intended to be used with Spring Boot web applications, and the listening port of the server is controlled by the server.port setting.
-The spring.cloud.stream.schema.server.path setting can be used to control the root path of the schema server (especially when it is embedded in other applications).
-The spring.cloud.stream.schema.server.allowSchemaDeletion boolean setting enables the deletion of schema. By default this is disabled.
The schema registry server uses a relational database to store the schemas. - By default, it uses an embedded database. -You can customize the schema storage using the Spring Boot SQL database and JDBC configuration options.
-A Spring Boot application enabling the schema registry looks as follows:
-@SpringBootApplication
-@EnableSchemaRegistryServer
-public class SchemaRegistryServerApplication {
- public static void main(String[] args) {
- SpringApplication.run(SchemaRegistryServerApplication.class, args);
- }
-}
-===== Schema Registry Server API
-The Schema Registry Server API consists of the following operations:
-====== POST /
Register a new schema.
-Accepts JSON payload with the following fields:
-subject the schema subject;
format the schema format;
definition the schema definition.
Response is a schema object in JSON format, with the following fields:
-id the schema id;
subject the schema subject;
format the schema format;
version the schema version;
definition the schema definition.
====== GET /{subject}/{format}/{version}
Retrieve an existing schema by its subject, format and version.
-Response is a schema object in JSON format, with the following fields:
-id the schema id;
subject the schema subject;
format the schema format;
version the schema version;
definition the schema definition.
====== GET /schemas/{id}
Retrieve an existing schema by its id.
-Response is a schema object in JSON format, with the following fields:
-id the schema id;
subject the schema subject;
format the schema format;
version the schema version;
definition the schema definition.
====== DELETE /{subject}/{format}/{version}
Delete an existing schema by its subject, format and version.
-====== DELETE /schemas/{id}
Delete an existing schema by its id.
-====== DELETE /{subject}
Delete existing schemas by their subject.
-This note applies to users of Spring Cloud Stream 1.1.0.RELEASE only.
-Spring Cloud Stream 1.1.0.RELEASE used the table name schema for storing Schema objects, which is a keyword in a number of database implementations.
-To avoid any conflicts in the future, starting with 1.1.1.RELEASE we have opted for the name SCHEMA_REPOSITORY for the storage table.
-Any Spring Cloud Stream 1.1.0.RELEASE users that are upgrading are advised to migrate their existing schemas to the new table before upgrading.
==== Schema Registry Client
-The client-side abstraction for interacting with schema registry servers is the SchemaRegistryClient interface, with the following structure:
public interface SchemaRegistryClient {
-
- SchemaRegistrationResponse register(String subject, String format, String schema);
-
- String fetch(SchemaReference schemaReference);
-
- String fetch(Integer id);
-
-}
-Spring Cloud Stream provides out of the box implementations for interacting with its own schema server, as well as for interacting with the Confluent Schema Registry.
-A client for the Spring Cloud Stream schema registry can be configured using the @EnableSchemaRegistryClient as follows:
@EnableBinding(Sink.class)
- @SpringBootApplication
- @EnableSchemaRegistryClient
- public static class AvroSinkApplication {
- ...
- }
-==== Avro Schema Registry Client Message Converters
-For Spring Boot applications that have a SchemaRegistryClient bean registered with the application context, Spring Cloud Stream will auto-configure an Apache Avro message converter that uses the schema registry client for schema management.
-This eases schema evolution, as applications that receive messages can get easy access to a writer schema that can be reconciled with their own reader schema.
For outbound messages, the MessageConverter will be activated if the content type of the channel is set to application/*+avro, e.g.:
spring.cloud.stream.bindings.output.contentType=application/*+avro
-During the outbound conversion, the message converter will try to infer the schemas of the outbound messages based on their type and register them to a subject based on the payload type using the SchemaRegistryClient.
-If an identical schema is already found, then a reference to it will be retrieved.
-If not, the schema will be registered and a new version number will be provided.
-The message will be sent with a contentType header using the scheme application/[prefix].[subject].v[version]+avro, where prefix is configurable and subject is deduced from the payload type.
For example, a message of the type User may be sent as a binary payload with a content type of application/vnd.user.v2+avro, where user is the subject and 2 is the version number.
When receiving messages, the converter will infer the schema reference from the header of the incoming message and will try to retrieve it. The schema will be used as the writer schema in the deserialization process.
-=== @StreamListener and Message Conversion
The @StreamListener annotation provides a convenient way for converting incoming messages without the need to specify the content type of an input channel.
-During the dispatching process to methods annotated with @StreamListener, a conversion will be applied automatically if the argument requires it.
For example, let’s consider a message with the String content {"greeting":"Hello, world"} and a content-type header of application/json is received on the input channel.
-Let us consider the following application that receives it:
public class GreetingMessage {
-
- String greeting;
-
- public String getGreeting() {
- return greeting;
- }
-
- public void setGreeting(String greeting) {
- this.greeting = greeting;
- }
-}
-
-@EnableBinding(Sink.class)
-@EnableAutoConfiguration
-public static class GreetingSink {
-
- @StreamListener(Sink.INPUT)
- public void receive(Greeting greeting) {
- // handle Greeting
- }
- }
-The argument of the method will be populated automatically with the POJO containing the unmarshalled form of the JSON String.
-== Inter-Application Communication
-=== Connecting Multiple Application Instances
-While Spring Cloud Stream makes it easy for individual Spring Boot applications to connect to messaging systems, the typical scenario for Spring Cloud Stream is the creation of multi-application pipelines, where microservice applications send data to each other. -You can achieve this scenario by correlating the input and output destinations of adjacent applications.
-Supposing that a design calls for the Time Source application to send data to the Log Sink application, you can use a common destination named ticktock for bindings within both applications.
Time Source (that has the channel name output) will set the following property:
spring.cloud.stream.bindings.output.destination=ticktock-
Log Sink (that has the channel name input) will set the following property:
spring.cloud.stream.bindings.input.destination=ticktock-
=== Instance Index and Instance Count
-When scaling up Spring Cloud Stream applications, each instance can receive information about how many other instances of the same application exist and what its own instance index is.
-Spring Cloud Stream does this through the spring.cloud.stream.instanceCount and spring.cloud.stream.instanceIndex properties.
-For example, if there are three instances of a HDFS sink application, all three instances will have spring.cloud.stream.instanceCount set to 3, and the individual applications will have spring.cloud.stream.instanceIndex set to 0, 1, and 2, respectively.
When Spring Cloud Stream applications are deployed via Spring Cloud Data Flow, these properties are configured automatically; when Spring Cloud Stream applications are launched independently, these properties must be set correctly.
-By default, spring.cloud.stream.instanceCount is 1, and spring.cloud.stream.instanceIndex is 0.
In a scaled-up scenario, correct configuration of these two properties is important for addressing partitioning behavior (see below) in general, and the two properties are always required by certain binders (e.g., the Kafka binder) in order to ensure that data are split correctly across multiple consumer instances.
-=== Partitioning
-==== Configuring Output Bindings for Partitioning
-An output binding is configured to send partitioned data by setting one and only one of its partitionKeyExpression or partitionKeyExtractorClass properties, as well as its partitionCount property.
-For example, the following is a valid and typical configuration:
spring.cloud.stream.bindings.output.producer.partitionKeyExpression=payload.id -spring.cloud.stream.bindings.output.producer.partitionCount=5-
Based on the above example configuration, data will be sent to the target partition using the following logic.
-A partition key’s value is calculated for each message sent to a partitioned output channel based on the partitionKeyExpression.
-The partitionKeyExpression is a SpEL expression which is evaluated against the outbound message for extracting the partitioning key.
If a SpEL expression is not sufficient for your needs, you can instead calculate the partition key value by setting the property partitionKeyExtractorClass to a class which implements the org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy interface.
-While the SpEL expression should usually suffice, more complex cases may use the custom implementation strategy.
-In that case, the property 'partitionKeyExtractorClass' can be set as follows:
spring.cloud.stream.bindings.output.producer.partitionKeyExtractorClass=com.example.MyKeyExtractor -spring.cloud.stream.bindings.output.producer.partitionCount=5-
Once the message key is calculated, the partition selection process will determine the target partition as a value between 0 and partitionCount - 1.
-The default calculation, applicable in most scenarios, is based on the formula key.hashCode() % partitionCount.
-This can be customized on the binding, either by setting a SpEL expression to be evaluated against the 'key' (via the partitionSelectorExpression property) or by setting a org.springframework.cloud.stream.binder.PartitionSelectorStrategy implementation (via the partitionSelectorClass property).
The binding level properties for 'partitionSelectorExpression' and 'partitionSelectorClass' can be specified similar to the way 'partitionKeyExpression' and 'partitionKeyExtractorClass' properties are specified in the above examples. -Additional properties can be configured for more advanced scenarios, as described in the following section.
-===== Spring-managed custom PartitionKeyExtractorClass implementations
In the example above, a custom strategy such as MyKeyExtractor is instantiated by the Spring Cloud Stream directly.
-In some cases, it is necessary for such a custom strategy implementation to be created as a Spring bean, for being able to be managed by Spring, so that it can perform dependency injection, property binding, etc.
-This can be done by configuring it as a @Bean in the application context and using the fully qualified class name as the bean’s name, as in the following example.
@Bean(name="com.example.MyKeyExtractor")
-public MyKeyExtractor extractor() {
- return new MyKeyExtractor();
-}
-As a Spring bean, the custom strategy benefits from the full lifecycle of a Spring bean. -For example, if the implementation need access to the application context directly, it can make implement 'ApplicationContextAware'.
-===== Configuring Input Bindings for Partitioning
-An input binding (with the channel name input) is configured to receive partitioned data by setting its partitioned property, as well as the instanceIndex and instanceCount properties on the application itself, as in the following example:
spring.cloud.stream.bindings.input.consumer.partitioned=true -spring.cloud.stream.instanceIndex=3 -spring.cloud.stream.instanceCount=5-
The instanceCount value represents the total number of application instances between which the data need to be partitioned, and the instanceIndex must be a unique value across the multiple instances, between 0 and instanceCount - 1.
-The instance index helps each application instance to identify the unique partition (or, in the case of Kafka, the partition set) from which it receives data.
-It is important to set both values correctly in order to ensure that all of the data is consumed and that the application instances receive mutually exclusive datasets.
While a scenario which using multiple instances for partitioned data processing may be complex to set up in a standalone case, Spring Cloud Dataflow can simplify the process significantly by populating both the input and output values correctly as well as relying on the runtime infrastructure to provide information about the instance index and instance count.
-== Testing
-Spring Cloud Stream provides support for testing your microservice applications without connecting to a messaging system.
-You can do that by using the TestSupportBinder.
-This is useful especially for unit testing your microservices.
The TestSupportBinder allows users to interact with the bound channels and inspect what messages are sent and received by the application
For outbound message channels, the TestSupportBinder registers a single subscriber and retains the messages emitted by the application in a MessageCollector.
-They can be retrieved during tests and have assertions made against them.
The user can also send messages to inbound message channels, so that the consumer application can consume the messages. -The following example shows how to test both input and output channels on a processor.
-@RunWith(SpringJUnit4ClassRunner.class)
-@SpringApplicationConfiguration(classes = ExampleTest.MyProcessor.class)
-@IntegrationTest({"server.port=-1"})
-@DirtiesContext
-public class ExampleTest {
-
- @Autowired
- private Processor processor;
-
- @Autowired
- private BinderFactory<MessageChannel> binderFactory;
-
- @Autowired
- private MessageCollector messageCollector;
-
- @Test
- @SuppressWarnings("unchecked")
- public void testWiring() {
- Message<String> message = new GenericMessage<>("hello");
- processor.input().send(message);
- Message<String> received = (Message<String>) messageCollector.forChannel(processor.output()).poll();
- assertThat(received.getPayload(), equalTo("hello world"));
- }
-
-
- @SpringBootApplication
- @EnableBinding(Processor.class)
- public static class MyProcessor {
-
- @Autowired
- private Processor channels;
-
- @Transformer(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT)
- public String transform(String in) {
- return in + " world";
- }
- }
-}
-In the example above, we are creating an application that has an input and an output channel, bound through the Processor interface.
-The bound interface is injected into the test so we can have access to both channels.
-We are sending a message on the input channel and we are using the MessageCollector provided by Spring Cloud Stream’s test support to capture the message has been sent to the output channel as a result.
-Once we have received the message, we can validate that the component functions correctly.
== Health Indicator
-Spring Cloud Stream provides a health indicator for binders.
-It is registered under the name of binders and can be enabled or disabled by setting the management.health.binders.enabled property.
== Samples
-For Spring Cloud Stream samples, please refer to the spring-cloud-stream-samples repository on GitHub.
-== Getting Started
-To get started with creating Spring Cloud Stream applications, visit the Spring Initializr and create a new Maven project named "GreetingSource".
-Select Spring Boot {supported-spring-boot-version} in the dropdown.
-In the Search for dependencies text box type Stream Rabbit or Stream Kafka depending on what binder you want to use.
Next, create a new class, GreetingSource, in the same package as the GreetingSourceApplication class.
-Give it the following code:
import org.springframework.cloud.stream.annotation.EnableBinding;
-import org.springframework.cloud.stream.messaging.Source;
-import org.springframework.integration.annotation.InboundChannelAdapter;
-
-@EnableBinding(Source.class)
-public class GreetingSource {
-
- @InboundChannelAdapter(Source.OUTPUT)
- public String greet() {
- return "hello world " + System.currentTimeMillis();
- }
-}
-The @EnableBinding annotation is what triggers the creation of Spring Integration infrastructure components.
-Specifically, it will create a Kafka connection factory, a Kafka outbound channel adapter, and the message channel defined inside the Source interface:
public interface Source {
-
- String OUTPUT = "output";
-
- @Output(Source.OUTPUT)
- MessageChannel output();
-
-}
-The auto-configuration also creates a default poller, so that the greet() method will be invoked once per second.
-The standard Spring Integration @InboundChannelAdapter annotation sends a message to the source’s output channel, using the return value as the payload of the message.
To test-drive this setup, run a Kafka message broker. -An easy way to do this is to use a Docker image:
-# On OS X
-$ docker run -p 2181:2181 -p 9092:9092 --env ADVERTISED_HOST=`docker-machine ip \`docker-machine active\`` --env ADVERTISED_PORT=9092 spotify/kafka
-
-# On Linux
-$ docker run -p 2181:2181 -p 9092:9092 --env ADVERTISED_HOST=localhost --env ADVERTISED_PORT=9092 spotify/kafka
-Build the application:
-./mvnw clean package-
The consumer application is coded in a similar manner.
-Go back to Initializr and create another project, named LoggingSink.
-Then create a new class, LoggingSink, in the same package as the class LoggingSinkApplication and with the following code:
import org.springframework.cloud.stream.annotation.EnableBinding;
-import org.springframework.cloud.stream.annotation.StreamListener;
-import org.springframework.cloud.stream.messaging.Sink;
-
-@EnableBinding(Sink.class)
-public class LoggingSink {
-
- @StreamListener(Sink.INPUT)
- public void log(String message) {
- System.out.println(message);
- }
-}
-Build the application:
-./mvnw clean package-
To connect the GreetingSource application to the LoggingSink application, each application must share the same destination name. -Starting up both applications as shown below, you will see the consumer application printing "hello world" and a timestamp to the console:
-cd GreetingSource
-java -jar target/GreetingSource-0.0.1-SNAPSHOT.jar --spring.cloud.stream.bindings.output.destination=mydest
-
-cd LoggingSink
-java -jar target/LoggingSink-0.0.1-SNAPSHOT.jar --server.port=8090 --spring.cloud.stream.bindings.input.destination=mydest
-(The different server port prevents collisions of the HTTP port used to service the Spring Boot Actuator endpoints in the two applications.)
-The output of the LoggingSink application will look something like the following:
-[ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8090 (http)
-[ main] com.example.LoggingSinkApplication : Started LoggingSinkApplication in 6.828 seconds (JVM running for 7.371)
-hello world 1458595076731
-hello world 1458595077732
-hello world 1458595078733
-hello world 1458595079734
-hello world 1458595080735
-= Binder Implementations
-== Apache Kafka Binder
-== Usage
-For using the Apache Kafka binder, you just need to add it to your Spring Cloud Stream application, using the following Maven coordinates:
-<dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-stream-binder-kafka</artifactId>
-</dependency>
-Alternatively, you can also use the Spring Cloud Stream Kafka Starter.
-<dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-stream-kafka</artifactId>
-</dependency>
-== Apache Kafka Binder Overview
-A simplified diagram of how the Apache Kafka binder operates can be seen below.
-
-The Apache Kafka Binder implementation maps each destination to an Apache Kafka topic. -The consumer group maps directly to the same Apache Kafka concept. -Partitioning also maps directly to Apache Kafka partitions as well.
-== Configuration Options
-This section contains the configuration options used by the Apache Kafka binder.
-For common configuration options and properties pertaining to binder, refer to the core docs.
-=== Kafka Binder Properties
-A list of brokers to which the Kafka binder will connect.
-Default: localhost.
brokers allows hosts specified with or without port information (e.g., host1,host2:port2).
-This sets the default port when no port is configured in the broker list.
Default: 9092.
A list of ZooKeeper nodes to which the Kafka binder can connect.
-Default: localhost.
zkNodes allows hosts specified with or without port information (e.g., host1,host2:port2).
-This sets the default port when no port is configured in the node list.
Default: 2181.
Key/Value map of client properties (both producers and consumer) passed to all clients created by the binder. -Due to the fact that these properties will be used by both producers and consumers, usage should be restricted to common properties, especially security settings.
-Default: Empty map.
-The list of custom headers that will be transported by the binder.
-Default: empty.
- The frequency, in milliseconds, with which offsets are saved.
-Ignored if 0.
Default: 10000.
The frequency, in number of updates, which which consumed offsets are persisted.
-Ignored if 0.
-Mutually exclusive with offsetUpdateTimeWindow.
Default: 0.
The number of required acks on the broker.
-Default: 1.
Effective only if autoCreateTopics or autoAddPartitions is set.
-The global minimum number of partitions that the binder will configure on topics on which it produces/consumes data.
-It can be superseded by the partitionCount setting of the producer or by the value of instanceCount * concurrency settings of the producer (if either is larger).
Default: 1.
The replication factor of auto-created topics if autoCreateTopics is active.
Default: 1.
If set to true, the binder will create new topics automatically.
-If set to false, the binder will rely on the topics being already configured.
-In the latter case, if the topics do not exist, the binder will fail to start.
-Of note, this setting is independent of the auto.topic.create.enable setting of the broker and it does not influence it: if the server is set to auto-create topics, they may be created as part of the metadata retrieval request, with default broker settings.
Default: true.
If set to true, the binder will create add new partitions if required.
-If set to false, the binder will rely on the partition size of the topic being already configured.
-If the partition count of the target topic is smaller than the expected value, the binder will fail to start.
Default: false.
Size (in bytes) of the socket buffer to be used by the Kafka consumers.
-Default: 2097152.
=== Kafka Consumer Properties
-The following properties are available for Kafka consumers only and
-must be prefixed with spring.cloud.stream.kafka.bindings.<channelName>.consumer..
When true, topic partitions will be automatically rebalanced between the members of a consumer group.
-When false, each consumer will be assigned a fixed set of partitions based on spring.cloud.stream.instanceCount and spring.cloud.stream.instanceIndex.
-This requires both spring.cloud.stream.instanceCount and spring.cloud.stream.instanceIndex properties to be set appropriately on each launched instance.
-The property spring.cloud.stream.instanceCount must typically be greater than 1 in this case.
Default: true.
Whether to autocommit offsets when a message has been processed.
-If set to false, a header with the key kafka_acknowledgment of the type org.springframework.kafka.support.Acknowledgment header will be present in the inbound message.
-Applications may use this header for acknowledging messages.
-See the examples section for details.
-When this property is set to false, Kafka binder will set the ack mode to org.springframework.kafka.listener.AbstractMessageListenerContainer.AckMode.MANUAL.
Default: true.
Effective only if autoCommitOffset is set to true.
-If set to false it suppresses auto-commits for messages that result in errors, and will commit only for successful messages, allows a stream to automatically replay from the last successfully processed message, in case of persistent failures.
-If set to true, it will always auto-commit (if auto-commit is enabled).
-If not set (default), it effectively has the same value as enableDlq, auto-committing erroneous messages if they are sent to a DLQ, and not committing them otherwise.
Default: not set.
-The interval between connection recovery attempts, in milliseconds.
-Default: 5000.
Whether to reset offsets on the consumer to the value provided by startOffset.
Default: false.
The starting offset for new groups, or when resetOffsets is true.
-Allowed values: earliest, latest.
-If the consumer group is set explicitly for the consumer 'binding' (via spring.cloud.stream.bindings.<channelName>.group), then 'startOffset' is set to earliest; otherwise it is set to latest for the anonymous consumer group.
Default: null (equivalent to earliest).
When set to true, it will send enable DLQ behavior for the consumer.
-Messages that result in errors will be forwarded to a topic named error.<destination>.<group>.
-This provides an alternative option to the more common Kafka replay scenario for the case when the number of errors is relatively small and replaying the entire original topic may be too cumbersome.
Default: false.
Map with a key/value pair containing generic Kafka consumer properties.
-Default: Empty map.
-=== Kafka Producer Properties
-The following properties are available for Kafka producers only and
-must be prefixed with spring.cloud.stream.kafka.bindings.<channelName>.producer..
Upper limit, in bytes, of how much data the Kafka producer will attempt to batch before sending.
-Default: 16384.
Whether the producer is synchronous.
-Default: false.
How long the producer will wait before sending in order to allow more messages to accumulate in the same batch. -(Normally the producer does not wait at all, and simply sends all the messages that accumulated while the previous send was in progress.) A non-zero value may increase throughput at the expense of latency.
-Default: 0.
Map with a key/value pair containing generic Kafka producer properties.
-Default: Empty map.
-The Kafka binder will use the partitionCount setting of the producer as a hint to create a topic with the given partition count (in conjunction with the minPartitionCount, the maximum of the two being the value being used).
-Exercise caution when configuring both minPartitionCount for a binder and partitionCount for an application, as the larger value will be used.
-If a topic already exists with a smaller partition count and autoAddPartitions is disabled (the default), then the binder will fail to start.
-If a topic already exists with a smaller partition count and autoAddPartitions is enabled, new partitions will be added.
-If a topic already exists with a larger number of partitions than the maximum of (minPartitionCount and partitionCount), the existing partition count will be used.
=== Usage examples
-In this section, we illustrate the use of the above properties for specific scenarios.
-==== Example: Setting autoCommitOffset false and relying on manual acking.
This example illustrates how one may manually acknowledge offsets in a consumer application.
-This example requires that spring.cloud.stream.kafka.bindings.input.consumer.autoCommitOffset is set to false.
-Use the corresponding input channel name for your example.
@SpringBootApplication
-@EnableBinding(Sink.class)
-public class ManuallyAcknowdledgingConsumer {
-
- public static void main(String[] args) {
- SpringApplication.run(ManuallyAcknowdledgingConsumer.class, args);
- }
-
- @StreamListener(Sink.INPUT)
- public void process(Message<?> message) {
- Acknowledgment acknowledgment = message.getHeaders().get(KafkaHeaders.ACKNOWLEDGMENT, Acknowledgment.class);
- if (acknowledgment != null) {
- System.out.println("Acknowledgment provided");
- acknowledgment.acknowledge();
- }
- }
-}
-==== Example: security configuration
-Apache Kafka 0.9 supports secure connections between client and brokers.
-To take advantage of this feature, follow the guidelines in the Apache Kafka Documentation as well as the Kafka 0.9 security guidelines from the Confluent documentation.
-Use the spring.cloud.stream.kafka.binder.configuration option to set security properties for all clients created by the binder.
For example, for setting security.protocol to SASL_SSL, set:
spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_SSL
-All the other security properties can be set in a similar manner.
-When using Kerberos, follow the instructions in the reference documentation for creating and referencing the JAAS configuration.
-Spring Cloud Stream supports passing JAAS configuration information to the application using a JAAS configuration file and using Spring Boot properties.
-===== Using JAAS configuration files
-The JAAS, and (optionally) krb5 file locations can be set for Spring Cloud Stream applications by using system properties. -Here is an example of launching a Spring Cloud Stream application with SASL and Kerberos using a JAAS configuration file:
- java -Djava.security.auth.login.config=/path.to/kafka_client_jaas.conf -jar log.jar \
- --spring.cloud.stream.kafka.binder.brokers=secure.server:9092 \
- --spring.cloud.stream.kafka.binder.zkNodes=secure.zookeeper:2181 \
- --spring.cloud.stream.bindings.input.destination=stream.ticktock \
- --spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_PLAINTEXT
-===== Using Spring Boot properties
-As an alternative to having a JAAS configuration file, Spring Cloud Stream provides a mechanism for setting up the JAAS configuration for Spring Cloud Stream applications using Spring Boot properties.
-The following properties can be used for configuring the login context of the Kafka client.
-The login module name. Not necessary to be set in normal cases.
-Default: com.sun.security.auth.module.Krb5LoginModule.
The control flag of the login module.
-Default: required.
Map with a key/value pair containing the login module options.
-Default: Empty map.
-Here is an example of launching a Spring Cloud Stream application with SASL and Kerberos using Spring Boot configuration properties:
- java --spring.cloud.stream.kafka.binder.brokers=secure.server:9092 \
- --spring.cloud.stream.kafka.binder.zkNodes=secure.zookeeper:2181 \
- --spring.cloud.stream.bindings.input.destination=stream.ticktock \
- --spring.cloud.stream.kafka.binder.autoCreateTopics=false \
- --spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_PLAINTEXT \
- --spring.cloud.stream.kafka.binder.jaas.options.useKeyTab=true \
- --spring.cloud.stream.kafka.binder.jaas.options.storeKey=true \
- --spring.cloud.stream.kafka.binder.jaas.options.keyTab=/etc/security/keytabs/kafka_client.keytab \
- --spring.cloud.stream.kafka.binder.jaas.options.principal=kafka-client-1@EXAMPLE.COM
-This represents the equivalent of the following JAAS file:
-KafkaClient {
- com.sun.security.auth.module.Krb5LoginModule required
- useKeyTab=true
- storeKey=true
- keyTab="/etc/security/keytabs/kafka_client.keytab"
- principal="kafka-client-1@EXAMPLE.COM";
-};
-If the topics required already exist on the broker, or will be created by an administrator, autocreation can be turned off and only client JAAS properties need to be sent. As an alternative to setting spring.cloud.stream.kafka.binder.autoCreateTopics you can simply remove the broker dependency from the application. See [exclude-admin-utils] for details.
Do not mix JAAS configuration files and Spring Boot properties in the same application.
-If the -Djava.security.auth.login.config system property is already present, Spring Cloud Stream will ignore the Spring Boot properties.
Exercise caution when using the autoCreateTopics and autoAddPartitions if using Kerberos.
-Usually applications may use principals that do not have administrative rights in Kafka and Zookeeper, and relying on Spring Cloud Stream to create/modify topics may fail.
-In secure environments, we strongly recommend creating topics and managing ACLs administratively using Kafka tooling.
==== Using the binder with Apache Kafka 0.10
-The binder also supports connecting to Kafka 0.10 brokers.
-In order to support this, when you create the project that contains your application, include spring-cloud-starter-stream-kafka as you normally would do for 0.9 based applications.
-Then add these dependencies at the top of the <dependencies> section in the pom.xml file to override the Apache Kafka, Spring Kafka, and Spring Integration Kafka with 0.10-compatible versions as in the following example:
<dependency>
- <groupId>org.springframework.kafka</groupId>
- <artifactId>spring-kafka</artifactId>
- <version>1.1.1.RELEASE</version>
-</dependency>
-<dependency>
- <groupId>org.springframework.integration</groupId>
- <artifactId>spring-integration-kafka</artifactId>
- <version>2.1.0.RELEASE</version>
-</dependency>
-<dependency>
- <groupId>org.apache.kafka</groupId>
- <artifactId>kafka_2.11</artifactId>
- <version>0.10.0.0</version>
- <exclusions>
- <exclusion>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-log4j12</artifactId>
- </exclusion>
- </exclusions>
-</dependency>
-The versions above are provided only for the sake of the example. -For best results, we recommend using the most recent 0.10-compatible versions of the projects.
-==== Excluding Kafka broker jar from the classpath of the binder based application
-The Apache Kafka Binder uses the administrative utilities which are part of the Apache Kafka server library to create and reconfigure topics. -If the inclusion of the Apache Kafka server library and its dependencies is not necessary at runtime because the application will rely on the topics being configured administratively, the Kafka binder allows for Apache Kafka server dependency to be excluded from the application.
-If you use Kafka 10 dependencies as advised above, all you have to do is not to include the kafka broker dependency.
-If you use Kafka 0.9, then ensure that you exclude the kafka broker jar from the spring-cloud-starter-stream-kafka dependency as following.
<dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-stream-kafka</artifactId>
- <exclusions>
- <exclusion>
- <groupId>org.apache.kafka</groupId>
- <artifactId>kafka_2.11</artifactId>
- </exclusion>
- </exclusions>
-</dependency>
-If you exclude the Apache Kafka server dependency and the topic is not present on the server, then the Apache Kafka broker will create the topic if auto topic creation is enabled on the server. -Please keep in mind that if you are relying on this, then the Kafka server will use the default number of partitions and replication factors. -On the other hand, if auto topic creation is disabled on the server, then care must be taken before running the application to create the topic with the desired number of partitions.
-If you want to have full control over how partitions are allocated, then leave the default settings as they are, i.e. do not exclude the kafka broker jar and ensure that spring.cloud.stream.kafka.binder.autoCreateTopics is set to true, which is the default.
== RabbitMQ Binder
-== Usage
-For using the RabbitMQ binder, you just need to add it to your Spring Cloud Stream application, using the following Maven coordinates:
-<dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
-</dependency>
-Alternatively, you can also use the Spring Cloud Stream RabbitMQ Starter.
-<dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
-</dependency>
-== RabbitMQ Binder Overview
-A simplified diagram of how the RabbitMQ binder operates can be seen below.
-
-The RabbitMQ Binder implementation maps each destination to a TopicExchange.
-For each consumer group, a Queue will be bound to that TopicExchange.
-Each consumer instance have a corresponding RabbitMQ Consumer instance for its group’s Queue.
-For partitioned producers/consumers the queues are suffixed with the partition index and use the partition index as routing key.
Using the autoBindDlq option, you can optionally configure the binder to create and configure dead-letter queues (DLQs) (and a dead-letter exchange DLX).
-The dead letter queue has the name of the destination, appended with .dlq.
-If retry is enabled (maxAttempts > 1) failed messages will be delivered to the DLQ.
-If retry is disabled (maxAttempts = 1), you should set requeueRejected to false (default) so that a failed message will be routed to the DLQ, instead of being requeued.
-In addition, republishToDlq causes the binder to publish a failed message to the DLQ (instead of rejecting it); this enables additional information to be added to the message in headers, such as the stack trace in the x-exception-stacktrace header.
-This option does not need retry enabled; you can republish a failed message after just one attempt.
|
- Important
- |
-
-Setting requeueRejected to true will cause the message to be requeued and redelivered continually, which is likely not what you want unless the failure issue is transient.
-In general, it’s better to enable retry within the binder by setting maxAttempts to greater than one, or set republishToDlq to true.
- |
-
See [rabbit-binder-properties] for more information about these properties.
-The framework does not provide any standard mechanism to consume dead-letter messages (or to re-route them back to the primary queue). -Some options are described in [rabbit-dlq-processing].
-== Configuration Options
-This section contains settings specific to the RabbitMQ Binder and bound channels.
-For general binding configuration options and properties, -please refer to the Spring Cloud Stream core documentation.
-=== RabbitMQ Binder Properties
-By default, the RabbitMQ binder uses Spring Boot’s ConnectionFactory, and it therefore supports all Spring Boot configuration options for RabbitMQ.
-(For reference, consult the Spring Boot documentation.)
-RabbitMQ configuration options use the spring.rabbitmq prefix.
In addition to Spring Boot options, the RabbitMQ binder supports the following properties:
- A comma-separated list of RabbitMQ management plugin URLs.
-Only used when nodes contains more than one entry.
-Each entry in this list must have a corresponding entry in spring.rabbitmq.addresses.
Default: empty.
- A comma-separated list of RabbitMQ node names.
-When more than one entry, used to locate the server address where a queue is located.
-Each entry in this list must have a corresponding entry in spring.rabbitmq.addresses.
Default: empty.
- Compression level for compressed bindings.
-See java.util.zip.Deflater.
Default: 1 (BEST_LEVEL).
=== RabbitMQ Consumer Properties
-The following properties are available for Rabbit consumers only and
-must be prefixed with spring.cloud.stream.rabbit.bindings.<channelName>.consumer..
The acknowledge mode.
-Default: AUTO.
Whether to automatically declare the DLQ and bind it to the binder DLX.
-Default: false.
The routing key with which to bind the queue to the exchange (if bindQueue is true).
-for partitioned destinations -<instanceIndex> will be appended.
Default: #.
Whether to bind the queue to the destination exchange; set to false if you have set up your own infrastructure and have previously created/bound the queue.
Default: true.
Whether to declare the exchange for the destination.
-Default: true.
Whether to declare the exchange as a Delayed Message Exchange - requires the delayed message exchange plugin on the broker.
-The x-delayed-type argument is set to the exchangeType.
Default: false.
Whether subscription should be durable.
-Only effective if group is also set.
Default: true.
If declareExchange is true, whether the exchange should be auto-delete (removed after the last queue is removed).
Default: true.
If declareExchange is true, whether the exchange should be durable (survives broker restart).
Default: true.
The exchange type; direct, fanout or topic for non-partitioned destinations; direct or topic for partitioned destinations.
Default: topic.
Default: 1.
Prefetch count.
-Default: 1.
A prefix to be added to the name of the destination and queues.
Default: "".
-The interval between connection recovery attempts, in milliseconds.
-Default: 5000.
Whether delivery failures should be requeued when retry is disabled or republishToDlq is false.
-Default: false.
The request headers to be transported.
-Default: [STANDARD_REQUEST_HEADERS,'*'].
The reply headers to be transported.
-Default: [STANDARD_REPLY_HEADERS,'*'].
By default, messages which fail after retries are exhausted are rejected.
-If a dead-letter queue (DLQ) is configured, RabbitMQ will route the failed message (unchanged) to the DLQ.
-If set to true, the binder will republish failed messages to the DLQ with additional headers, including the exception message and stack trace from the cause of the final failure.
Default: false
-Whether to use transacted channels.
-Default: false.
The number of deliveries between acks.
-Default: 1.
=== Rabbit Producer Properties
-The following properties are available for Rabbit producers only and
-must be prefixed with spring.cloud.stream.rabbit.bindings.<channelName>.producer..
Whether to automatically declare the DLQ and bind it to the binder DLX.
-Default: false.
Whether to enable message batching by producers.
-Default: false.
The number of messages to buffer when batching is enabled.
-Default: 100.
Default: 10000.
Default: 5000.
The routing key with which to bind the queue to the exchange (if bindQueue is true).
-Only applies to non-partitioned destinations.
-Only applies if requiredGroups are provided and then only to those groups.
Default: #.
Whether to bind the queue to the destination exchange; set to false if you have set up your own infrastructure and have previously created/bound the queue.
-Only applies if requiredGroups are provided and then only to those groups.
Default: true.
Whether data should be compressed when sent.
-Default: false.
Whether to declare the exchange for the destination.
-Default: true.
A SpEL expression to evaluate the delay to apply to the message (x-delay header) - has no effect if the exchange is not a delayed message exchange.
Default: No x-delay header is set.
Whether to declare the exchange as a Delayed Message Exchange - requires the delayed message exchange plugin on the broker.
-The x-delayed-type argument is set to the exchangeType.
Default: false.
Delivery mode.
-Default: PERSISTENT.
If declareExchange is true, whether the exchange should be auto-delete (removed after the last queue is removed).
Default: true.
If declareExchange is true, whether the exchange should be durable (survives broker restart).
Default: true.
The exchange type; direct, fanout or topic for non-partitioned destinations; direct or topic for partitioned destinations.
Default: topic.
A prefix to be added to the name of the destination exchange.
Default: "".
-The request headers to be transported.
-Default: [STANDARD_REQUEST_HEADERS,'*'].
The reply headers to be transported.
-Default: [STANDARD_REPLY_HEADERS,'*'].
A SpEL expression to determine the routing key to use when publishing messages.
-Default: destination or destination-<partition> for partitioned destinations.
Whether to use transacted channels.
-Default: false.
In the case of RabbitMQ, content type headers can be set by external applications. -Spring Cloud Stream supports them as part of an extended internal protocol used for any type of transport (including transports, such as Kafka, that do not normally support headers).
-== Dead-Letter Queue Processing
-Because it can’t be anticipated how users would want to dispose of dead-lettered messages, the framework does not provide any standard mechanism to handle them.
-If the reason for the dead-lettering is transient, you may wish to route the messages back to the original queue.
-However, if the problem is a permanent issue, that could cause an infinite loop.
-The following spring-boot application is an example of how to route those messages back to the original queue, but moves them to a third "parking lot" queue after three attempts.
-The second example utilizes the RabbitMQ Delayed Message Exchange to introduce a delay to the requeued message.
-In this example, the delay increases for each attempt.
-These examples use a @RabbitListener to receive messages from the DLQ, you could also use RabbitTemplate.receive() in a batch process.
The examples assume the original destination is so8400in and the consumer group is so8400.
=== Non-Partitioned Destinations
-The first two examples are when the destination is not partitioned.
-@SpringBootApplication
-public class ReRouteDlqApplication {
-
- private static final String ORIGINAL_QUEUE = "so8400in.so8400";
-
- private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
-
- private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
-
- private static final String X_RETRIES_HEADER = "x-retries";
-
- public static void main(String[] args) throws Exception {
- ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
- System.out.println("Hit enter to terminate");
- System.in.read();
- context.close();
- }
-
- @Autowired
- private RabbitTemplate rabbitTemplate;
-
- @RabbitListener(queues = DLQ)
- public void rePublish(Message failedMessage) {
- Integer retriesHeader = (Integer) failedMessage.getMessageProperties().getHeaders().get(X_RETRIES_HEADER);
- if (retriesHeader == null) {
- retriesHeader = Integer.valueOf(0);
- }
- if (retriesHeader < 3) {
- failedMessage.getMessageProperties().getHeaders().put(X_RETRIES_HEADER, retriesHeader + 1);
- this.rabbitTemplate.send(ORIGINAL_QUEUE, failedMessage);
- }
- else {
- this.rabbitTemplate.send(PARKING_LOT, failedMessage);
- }
- }
-
- @Bean
- public Queue parkingLot() {
- return new Queue(PARKING_LOT);
- }
-
-}
-@SpringBootApplication
-public class ReRouteDlqApplication {
-
- private static final String ORIGINAL_QUEUE = "so8400in.so8400";
-
- private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
-
- private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
-
- private static final String X_RETRIES_HEADER = "x-retries";
-
- private static final String DELAY_EXCHANGE = "dlqReRouter";
-
- public static void main(String[] args) throws Exception {
- ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
- System.out.println("Hit enter to terminate");
- System.in.read();
- context.close();
- }
-
- @Autowired
- private RabbitTemplate rabbitTemplate;
-
- @RabbitListener(queues = DLQ)
- public void rePublish(Message failedMessage) {
- Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
- Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
- if (retriesHeader == null) {
- retriesHeader = Integer.valueOf(0);
- }
- if (retriesHeader < 3) {
- headers.put(X_RETRIES_HEADER, retriesHeader + 1);
- headers.put("x-delay", 5000 * retriesHeader);
- this.rabbitTemplate.send(DELAY_EXCHANGE, ORIGINAL_QUEUE, failedMessage);
- }
- else {
- this.rabbitTemplate.send(PARKING_LOT, failedMessage);
- }
- }
-
- @Bean
- public DirectExchange delayExchange() {
- DirectExchange exchange = new DirectExchange(DELAY_EXCHANGE);
- exchange.setDelayed(true);
- return exchange;
- }
-
- @Bean
- public Binding bindOriginalToDelay() {
- return BindingBuilder.bind(new Queue(ORIGINAL_QUEUE)).to(delayExchange()).with(ORIGINAL_QUEUE);
- }
-
- @Bean
- public Queue parkingLot() {
- return new Queue(PARKING_LOT);
- }
-
-}
-=== Partitioned Destinations
-With partitioned destinations, there is one DLQ for all partitions and we determine the original queue from the headers.
-==== republishToDlq=false
-When republishToDlq is false, RabbitMQ publishes the message to the DLX/DLQ with an x-death header containing information about the original destination.
@SpringBootApplication
-public class ReRouteDlqApplication {
-
- private static final String ORIGINAL_QUEUE = "so8400in.so8400";
-
- private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
-
- private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
-
- private static final String X_DEATH_HEADER = "x-death";
-
- private static final String X_RETRIES_HEADER = "x-retries";
-
- public static void main(String[] args) throws Exception {
- ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
- System.out.println("Hit enter to terminate");
- System.in.read();
- context.close();
- }
-
- @Autowired
- private RabbitTemplate rabbitTemplate;
-
- @SuppressWarnings("unchecked")
- @RabbitListener(queues = DLQ)
- public void rePublish(Message failedMessage) {
- Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
- Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
- if (retriesHeader == null) {
- retriesHeader = Integer.valueOf(0);
- }
- if (retriesHeader < 3) {
- headers.put(X_RETRIES_HEADER, retriesHeader + 1);
- List<Map<String, ?>> xDeath = (List<Map<String, ?>>) headers.get(X_DEATH_HEADER);
- String exchange = (String) xDeath.get(0).get("exchange");
- List<String> routingKeys = (List<String>) xDeath.get(0).get("routing-keys");
- this.rabbitTemplate.send(exchange, routingKeys.get(0), failedMessage);
- }
- else {
- this.rabbitTemplate.send(PARKING_LOT, failedMessage);
- }
- }
-
- @Bean
- public Queue parkingLot() {
- return new Queue(PARKING_LOT);
- }
-
-}
-==== republishToDlq=true
-When republishToDlq is true, the republishing recoverer adds the original exchange and routing key to headers.
@SpringBootApplication
-public class ReRouteDlqApplication {
-
- private static final String ORIGINAL_QUEUE = "so8400in.so8400";
-
- private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
-
- private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
-
- private static final String X_RETRIES_HEADER = "x-retries";
-
- private static final String X_ORIGINAL_EXCHANGE_HEADER = RepublishMessageRecoverer.X_ORIGINAL_EXCHANGE;
-
- private static final String X_ORIGINAL_ROUTING_KEY_HEADER = RepublishMessageRecoverer.X_ORIGINAL_ROUTING_KEY;
-
- public static void main(String[] args) throws Exception {
- ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
- System.out.println("Hit enter to terminate");
- System.in.read();
- context.close();
- }
-
- @Autowired
- private RabbitTemplate rabbitTemplate;
-
- @RabbitListener(queues = DLQ)
- public void rePublish(Message failedMessage) {
- Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
- Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
- if (retriesHeader == null) {
- retriesHeader = Integer.valueOf(0);
- }
- if (retriesHeader < 3) {
- headers.put(X_RETRIES_HEADER, retriesHeader + 1);
- String exchange = (String) headers.get(X_ORIGINAL_EXCHANGE_HEADER);
- String originalRoutingKey = (String) headers.get(X_ORIGINAL_ROUTING_KEY_HEADER);
- this.rabbitTemplate.send(exchange, originalRoutingKey, failedMessage);
- }
- else {
- this.rabbitTemplate.send(PARKING_LOT, failedMessage);
- }
- }
-
- @Bean
- public Queue parkingLot() {
- return new Queue(PARKING_LOT);
- }
-
-}
-= Spring Cloud Bus -:github: https://github.com/spring-cloud/spring-cloud-config -:githubmaster: https://github.com/spring-cloud/spring-cloud-config/tree/master -:docslink: https://github.com/spring-cloud/spring-cloud-config/tree/master/docs/src/main/asciidoc +Sabby Anandan; Marius Bogoevici; Eric Bottard; Mark Fisher; Ilayaperumal Gopinathan; Gunnar Hillert; Mark Pollack; Patrick Peralta; Glenn Renfro; Thomas Risberg; Dave Syer; David Turanski; Janne Valkealahti; Benjamin Klein +:doctype: book :toc: -:nofooter:
+:toclevels: 4 +:source-highlighter: prettify +:numbered: +:icons: font +:hide-uri-scheme: +:spring-cloud-stream-repo: snapshot +:github-tag: master +:spring-cloud-stream-docs-version: current +:spring-cloud-stream-docs: http://docs.spring.io/spring-cloud-stream/docs/{spring-cloud-stream-docs-version}/reference +:spring-cloud-stream-docs-current: http://docs.spring.io/spring-cloud-stream/docs/current-SNAPSHOT/reference/html/ +:github-repo: spring-cloud/spring-cloud-stream +:github-raw: http://raw.github.com/spring-cloud/spring-cloud-netflix/master +:github-code: http://github.com/spring-cloud/spring-cloud-netflix/tree/master +:github-wiki: http://github.com/spring-cloud/spring-cloud-netflix/wiki +:github-master-code: http://github.com/spring-cloud/spring-cloud-netflix/tree/master +:sc-ext: javaUnresolved directive in loud-stream-docs/src/main/asciidoc/spring-cloud-stream-aggregate.adoc - include::../../../kafka/spring-cloud-stream-binder-kafka-docs/src/main/asciidoc/overview.adoc[leveloffset=+1]
+Unresolved directive in loud-stream-docs/src/main/asciidoc/spring-cloud-stream-aggregate.adoc - include::../../../rabbit/spring-cloud-stream-binder-rabbit-docs/src/main/asciidoc/overview.adoc[leveloffset=+1] +Unresolved directive in loud-stream-docs/src/main/asciidoc/spring-cloud-stream-aggregate.adoc - include::../../../rabbit/spring-cloud-stream-binder-rabbit-docs/src/main/asciidoc/dlq.adoc[leveloffset=+1]
+To build the source you will need to install JDK 1.7.
+The build uses the Maven wrapper so you don’t have to install a specific +version of Maven. To enable the tests for Redis, Rabbit, and Kafka bindings you +should have those servers running before building. See below for more +information on running the servers.
+The main build command is
+$ ./mvnw clean install+
You can also add '-DskipTests' if you like, to avoid running the tests.
+|
+ Note
+ |
+
+You can also install Maven (>=3.3.3) yourself and run the mvn command
+in place of ./mvnw in the examples below. If you do that you also
+might need to add -P spring if your local Maven settings do not
+contain repository declarations for spring pre-release artifacts.
+ |
+
|
+ Note
+ |
+
+Be aware that you might need to increase the amount of memory
+available to Maven by setting a MAVEN_OPTS environment variable with
+a value like -Xmx512m -XX:MaxPermSize=128m. We try to cover this in
+the .mvn configuration, so if you find you have to do it to make a
+build succeed, please raise a ticket to get the settings added to
+source control.
+ |
+
The projects that require middleware generally include a
+docker-compose.yml, so consider using
+Docker Compose to run the middeware servers
+in Docker containers. See the README in the
+scripts demo
+repository for specific instructions about the common cases of mongo,
+rabbit and redis.
There is a "full" profile that will generate documentation.
+If you don’t have an IDE preference we would recommend that you use +Spring Tools Suite or +Eclipse when working with the code. We use the +m2eclipe eclipse plugin for maven support. Other IDEs and tools +should also work without issue.
+We recommend the m2eclipe eclipse plugin when working with +eclipse. If you don’t already have m2eclipse installed it is available from the "eclipse +marketplace".
+Unfortunately m2e does not yet support Maven 3.3, so once the projects
+are imported into Eclipse you will also need to tell m2eclipse to use
+the .settings.xml file for the projects. If you do not do this you
+may see many different errors related to the POMs in the
+projects. Open your Eclipse preferences, expand the Maven
+preferences, and select User Settings. In the User Settings field
+click Browse and navigate to the Spring Cloud project you imported
+selecting the .settings.xml file in that project. Click Apply and
+then OK to save the preference changes.
|
+ Note
+ |
+
+Alternatively you can copy the repository settings from .settings.xml into your own ~/.m2/settings.xml.
+ |
+
If you prefer not to use m2eclipse you can generate eclipse project metadata using the +following command:
+$ ./mvnw eclipse:eclipse+
The generated eclipse projects can be imported by selecting import existing projects
+from the file menu.
+[[contributing]
+== Contributing
Spring Cloud is released under the non-restrictive Apache 2.0 license, +and follows a very standard Github development process, using Github +tracker for issues and merging pull requests into master. If you want +to contribute even something trivial please do not hesitate, but +follow the guidelines below.
+Before we accept a non-trivial patch or pull request we will need you to sign the +contributor’s agreement. +Signing the contributor’s agreement does not grant anyone commit rights to the main +repository, but it does mean that we can accept your contributions, and you will get an +author credit if we do. Active contributors might be asked to join the core team, and +given the ability to merge pull requests.
+None of these is essential for a pull request, but they will all help. They can also be +added after the original pull request but before a merge.
+Use the Spring Framework code format conventions. If you use Eclipse
+you can import formatter settings using the
+eclipse-code-formatter.xml file from the
+Spring
+Cloud Build project. If using IntelliJ, you can use the
+Eclipse Code Formatter
+Plugin to import the same file.
Make sure all new .java files to have a simple Javadoc class comment with at least an
+@author tag identifying you, and preferably at least a paragraph on what the class is
+for.
Add the ASF license header comment to all new .java files (copy from existing files
+in the project)
Add yourself as an @author to the .java files that you modify substantially (more
+than cosmetic changes).
Add some Javadocs and, if you change the namespace, some XSD doc elements.
+A few unit tests would help a lot as well — someone has to do it.
+If no-one else is using your branch, please rebase it against the current master (or +other target branch in the main project).
+When writing a commit message please follow these conventions,
+if you are fixing an existing issue please add Fixes gh-XXXX at the end of the commit
+message (where XXXX is the issue number).
Spring Cloud Bus links nodes of a distributed system with a lightweight message broker. This can then be used to broadcast state changes (e.g. configuration changes) or other management instructions. A key idea is that the Bus is like a distributed Actuator for a Spring Boot application that is scaled out, but it can also be used as a communication channel between apps. The only implementation currently is with an AMQP broker as the transport, but the same basic feature set (and some more depending on the transport) is on the roadmap for other transports.
== Quick Start
Spring Cloud Bus works by adding Spring Boot autconfiguration if it detects itself on the classpath. All you need to do to enable the bus is to add spring-cloud-starter-bus-amqp or spring-cloud-starter-bus-kafka to your dependency management and Spring Cloud takes care of the rest. Make sure the broker (RabbitMQ or Kafka) is available and configured: running on localhost you shouldn’t have to do anything, but if you are running remotely use Spring Cloud Connectors, or Spring Boot conventions to define the broker credentials, e.g. for Rabbit
== Addressing an Instance
The HTTP endpoints accept a "destination" parameter, e.g. "/bus/refresh?destination=customers:9000", where the destination is an ApplicationContext ID. If the ID is owned by an instance on the Bus then it will process the message and all other instances will ignore it. Spring Boot sets the ID for you in the ContextIdApplicationContextInitializer to a combination of the spring.application.name, active profiles and server.port by default.
== Addressing all instances of a service
The "destination" parameter is used in a Spring PathMatcher (with the path separator as a colon :) to determine if an instance will process the message. Using the example from above, "/bus/refresh?destination=customers:**" will target all instances of the "customers" service regardless of the profiles and ports set as the ApplicationContext ID.
== Application Context ID must be unique
The bus tries to eliminate processing an event twice, once from the original ApplicationEvent and once from the queue. To do this, it checks the sending application context id againts the current application context id. If multiple instances of a service have the same application context id, events will not be processed. Running on a local machine, each service will be on a different port and that will be part of the application context id. Cloud Foundry supplies an index to differentiate. To ensure that the application context id is the unique, set spring.application.index to something unique for each instance of a service. For example, in lattice, set spring.application.index=${INSTANCE_INDEX} in application.properties (or bootstrap.properties if using configserver).
== Customizing the Message Broker
Spring Cloud Bus uses Spring Cloud Stream to @@ -9787,9 +6572,11 @@ the the externall middleware). Normally the defaults will suffice.
To lean more about how to customize the message broker settings consult the Spring Cloud Stream documentation.
== Tracing Bus Events
Bus events (subclasses of RemoteApplicationEvent) can be traced by
setting spring.cloud.bus.trace.enabled=true. If you do this then the
@@ -9856,9 +6643,11 @@ queries on the data. Or forward it to a specialized tracing service.
== Broadcasting Your Own Events
The Bus can carry any event of type RemoteApplicationEvent, but the
default transport is JSON and the deserializer needs to know which
@@ -9871,9 +6660,8 @@ or rely on the default strategy which is to use the simple name of the class.
Note that both the producer and the consumer will need access to the class
definition.
=== Registering events in custom packages
-If you cannot or don’t want to use a subpackage of org.springframework.cloud.bus.event
for your custom events, you must specify which packages to scan for events of
@@ -9934,10 +6722,11 @@ in that the com.acme package will be registered by explicitly speci
packages on @RemoteApplicationEventScan. Note, you can specify multiple base
packages to scan.
Spring Cloud Sleuth
Adrian Cole, Spencer Gibb, Marcin Grzejszczak, Dave Syer
@@ -9948,9 +6737,10 @@ packages to scan.Spring Cloud Sleuth implements a distributed tracing solution for Spring Cloud.
=== Terminology
=== Purpose
In the following sections the example from the image above will be taken into consideration.
==== Distributed tracing with Zipkin
-Altogether there are 7 spans . If you go to traces in Zipkin you will see this number in the second trace:
service3 and 2 from service2
Logically we see the information of Total Spans: 4 because we have 1 span related to the incoming request
to service1 and 3 spans related to RPC calls.
==== Visualizing errors
Zipkin allows you to visualize errors in your trace. When an exception was thrown and wasn’t caught then we’re setting proper tags on the span which Zipkin can properly colorize. You could see in the list of traces one @@ -10135,9 +6924,9 @@ setting proper tags on the span which Zipkin can properly colorize. You could se
As you can see you can easily see the reason for an error and the whole stacktrace related to it.
==== Live examples
@@ -10158,9 +6947,9 @@ setting proper tags on the span which Zipkin can properly colorize. You could se
==== Log correlation
When grepping the logs of those four applications by trace id equal to e.g. 2485ec27856c56f4 one would get the following:
===== JSON Logback with Logstash
-Often you do not want to store your logs in a text file but in a JSON file that Logstash can immediately pick. To do that you have to do the following (for readability
we’re passing the dependencies in the groupId:artifactId:version notation.
logback-spring.xml then you have to
=== Adding to the project
==== Only Sleuth (log correlation)
If you want to profit only from Spring Cloud Sleuth without the Zipkin integration just add
the spring-cloud-starter-sleuth module to your project.
==== Sleuth with Zipkin via HTTP
If you want both Sleuth and Zipkin just add the spring-cloud-starter-zipkin dependency.
==== Sleuth with Zipkin via Spring Cloud Stream
If you want both Sleuth and Zipkin just add the spring-cloud-sleuth-stream dependency.
==== Spring Cloud Sleuth Stream Zipkin Collector
If you want to start a Spring Cloud Sleuth Stream Zipkin collector just add the spring-cloud-sleuth-zipkin-stream
dependency
== Additional resources
Marcin Grzejszczak talking about Spring Cloud Sleuth and Zipkin
== Features
== Sampling
In distributed tracing the data volumes can be very high so sampling can be important (you usually don’t need to export all spans to get a @@ -10853,9 +7648,11 @@ public Sampler defaultSampler() { }
== Instrumentation
Spring Cloud Sleuth instruments all your Spring application automatically, so you shouldn’t have to do anything to activate @@ -10901,9 +7698,11 @@ This approach may change in the future towards being lazy on this matter.
== Span lifecycle
You can do the following operations on the Span by means of org.springframework.cloud.sleuth.Tracer interface:
Tracer for you. In order to use it a
You can manually create spans by using the Tracer interface.
Sometimes you don’t want to create a new span but you want to continue one. Example of such a situation might be (of course it all depends on the use-case):
@@ -11060,9 +7858,9 @@ Always clean after you create a span! Don’t forget to detach a span if somThere is a possibility that you want to start a new span and provide an explicit parent of that span. Let’s assume that the parent of a span is in one thread and you want to start a new span in another thread. The @@ -11103,9 +7901,12 @@ After having created such a span remember to close it. Otherwise you will see a
== Naming spans
Picking a span name is not a trivial task. Span name should depict an operation name. The name should be low cardinality (e.g. not include identifiers).
@@ -11130,9 +7931,8 @@ artificial like:Fortunately, for the asynchronous processing you can provide explicit naming.
=== @SpanName annotation
-You can do name the span explicitly via the @SpanName annotation.
The span will be named calculateTax.
=== toString() method
It’s pretty rare to create separate classes for Runnable or Callable. Typically one creates an anonymous
instance of those classes. You can’t annotate such classes thus to override that, if there is no @SpanName annotation present,
@@ -11191,9 +7991,12 @@ future.get();
will lead in creating a span named calculateTax.
== Customizations
Thanks to the SpanInjector and SpanExtractor you can customize the way spans
are created and propagated.
=== Spring Integration
-For Spring Integration these are the beans responsible for creation of a Span from a Message
and filling in the MessageBuilder with tracing information.
You can override them by providing your own implementation and by adding a @Primary annotation
to your bean definition.
=== HTTP
For HTTP these are the beans responsible for creation of a Span from a HttpServletRequest.
You can override them by providing your own implementation and by adding a @Primary annotation
to your bean definition.
=== Example
Let’s assume that instead of the standard Zipkin compatible tracing HTTP header names you have
@@ -11359,9 +8161,9 @@ HttpResponseInjectingTraceFilter responseInjectingTraceFilter(Tracer tracer) { }=== Custom SA tag in Zipkin
Sometimes you want to create a manual Span that will wrap a call to an external service which is not instrumented.
What you can do is to create a span with the peer.service tag that will contain a value of the service that you want to call.
@@ -11397,9 +8199,9 @@ Remember not to add both peer.service tag and the SA t
=== Custom service name
By default Sleuth assumes that when you send a span to Zipkin, you want the span’s service name
to be equal to spring.application.name value. That’s not always the case though. There
@@ -11412,9 +8214,9 @@ Remember not to add both peer.service tag and the SA t
spring.zipkin.service.name: foo
=== Host locator
In order to define the host that is corresponding to a particular span we need to resolve the host name and port. The default approach is to take it from server properties. If those for some reason are not set @@ -11430,9 +8232,12 @@ Stream based span reporting).
spring.zipkin.locator.discovery.enabled: true
== Span Data as Messages
You can accumulate and send span data over
Spring Cloud Stream by
@@ -11443,9 +8248,8 @@ adding a Channel Binder implementation
automatically turn your app into a producer of messages with payload
type Spans.
=== Zipkin Consumer
-There is a special convenience annotation for setting up a message consumer
for the Span data and pushing it into a Zipkin SpanStore. This application
=== Custom Consumer
A custom consumer can also easily be implemented using
spring-cloud-sleuth-stream and binding to the SleuthSink. Example:
== Metrics
Currently Spring Cloud Sleuth registers very simple metrics related to spans. It’s using the Spring Boot’s metrics support @@ -11565,12 +8372,13 @@ to calculate the number of accepted and dropped spans. Each time a span gets sent to Zipkin the number of accepted spans will increase. If there’s an error then the number of dropped spans will get increased.
== Integrations
=== Runnable and Callable
If you’re wrapping your logic in Runnable or Callable it’s enough to wrap those classes in their Sleuth representative.
That way you will ensure that a new Span is created and closed for each execution.
=== Hystrix
-==== Custom Concurrency Strategy
We’re registering a custom HystrixConcurrencyStrategy
that wraps all Callable instances into their Sleuth representative -
the TraceCallable. The strategy either starts or continues a span depending on the fact whether tracing was already going
on before the Hystrix command was called. To disable the custom Hystrix Concurrency Strategy set the spring.sleuth.hystrix.strategy.enabled to false.
==== Manual Command setting
Assuming that you have the following HystrixCommand:
=== RxJava
We’re registering a custom RxJavaSchedulersHook
that wraps all Action0 instances into their Sleuth representative -
@@ -11678,24 +8486,23 @@ on before the Action was scheduled. To disable the custom RxJavaSchedulersHook s
You can define a list of regular expressions for thread names, for which you don’t want a Span to be created. Just provide a comma separated list
of regular expressions in the spring.sleuth.rxjava.schedulers.ignoredthreads property.
=== HTTP integration
Features from this section can be disabled by providing the spring.sleuth.web.enabled property with value equal to false.
==== HTTP Filter
-Via the TraceFilter all sampled incoming requests result in creation of a Span. That Span’s name is http: + the path to which
the request was sent. E.g. if the request was sent to /foo/bar then the name will be http:/foo/bar. You can configure which URIs you would
like to skip via the spring.sleuth.web.skipPattern property. If you have ManagementServerProperties on classpath then
its value of contextPath gets appended to the provided skip pattern.
==== HandlerInterceptor
Since we want the span names to be precise we’re using a TraceHandlerInterceptor that either wraps an
existing HandlerInterceptor or is added directly to the list of existing HandlerInterceptors. The
@@ -11704,18 +8511,18 @@ of regular expressions in the spring.sleuth.rxjava.schedulers.ignoredthrea
span created on the server side so that the trace is presented properly in the UI. Seeing that most likely
signifies that there is a missing instrumentation. In that case please file an issue in Spring Cloud Sleuth.
==== Async Servlet support
If your controller returns a Callable or a WebAsyncTask Spring Cloud Sleuth will continue the existing span instead of creating a new one.
=== HTTP client integration
==== Synchronous Rest Template
We’re injecting a RestTemplate interceptor that ensures that all the tracing information is passed to the requests. Each time a
call is made a new Span is created. It gets closed upon receiving the response. In order to block the synchronous RestTemplate features
@@ -11734,9 +8541,9 @@ If you create a RestTemplate instance with a new keywo
==== Asynchronous Rest Template
==== Executor, ExecutorService and ScheduledExecutorService
We’re providing LazyTraceExecutor, TraceableExecutorService and TraceableScheduledExecutorService. Those implementations
are creating Spans each time a new task is submitted, invoked or scheduled.
=== Messaging
Spring Cloud Sleuth integrates with Spring Integration. It creates spans for publish and
subscribe events. To disable Spring Integration instrumentation, set spring.sleuth.integration.enabled to false.
=== Zuul
We’re registering Zuul filters to propagate the tracing information (the request header is enriched with tracing data).
To disable Zuul support set the spring.sleuth.zuul.enabled property to false.
== Running examples
You can find the running examples deployed in the Pivotal Web Services. Check them out in the following links:
spring.sleuth.zuul.enabled property
= Spring Cloud Consul
Camden.SR6
== Install Consul -Please see the installation documentation for instructions on how to install Consul.
== Consul Agent
Please see the installation documentation for instructions on how to install Consul.
+A Consul Agent client must be available to all Spring Cloud Consul applications. By default, the Agent client is expected to be at localhost:8500. See the Agent documentation for specifics on how to start an Agent client and how to connect to a cluster of Consul Agent Servers. For development, after you have installed consul, you may start a Consul Agent using the following command:
This will start an agent in server mode on port 8500, with the ui available at http://localhost:8500
== Service Discovery with Consul
Service Discovery is one of the key tenets of a microservice based architecture. Trying to hand configure each client or some form of convention can be very difficult to do and can be very brittle. Consul provides Service Discovery services via an HTTP API and DNS. Spring Cloud Consul leverages the HTTP API for service registration and discovery. This does not prevent non-Spring Cloud applications from leveraging the DNS interface. Consul Agents servers are run in a cluster that communicates via a gossip protocol and uses the Raft consensus protocol.
=== How to activate
-To activate Consul Service Discovery use the starter with group org.springframework.cloud and artifact id spring-cloud-starter-consul-discovery. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.
=== Registering with Consul
When a client registers with Consul, it provides meta-data about itself such as host and port, id, name and tags. An HTTP Check is created by default that Consul hits the /health endpoint every 10 seconds. If the health check fails, the service instance is marked as critical.
@EnableDiscoveryClient make the app into both a Consul "service" (i.e. it registers itself) and a "client" (i.e. it can query Consul to locate other services).
=== HTTP Health Check
The health check for a Consul instance defaults to "/health", which is the default locations of a useful endpoint in a Spring Boot Actuator application. You need to change these, even for an Actuator application if you use a non-default context path or servlet path (e.g. server.servletPath=/foo) or management endpoint path (e.g. management.contextPath=/admin). The interval that Consul uses to check the health endpoint may also be configured. "10s" and "1m" represent 10 seconds and 1 minute respectively. Example:
==== Metadata and Consul tags
-Consul does not yet support metadata on services. Spring Cloud’s ServiceInstance has a Map<String, String> metadata field. Spring Cloud Consul uses Consul tags to approximate metadata until Consul officially supports metadata. Tags with the form key=value will be split and used as a Map key and value respectively. Tags without the equal = sign, will be used as both the key and value.
The above configuration will result in a map with foo→bar and baz→baz.
==== Making the Consul Instance ID Unique
By default a consul instance is registered with an ID that is equal to its Spring Application Context ID. By default, the Spring Application Context ID is ${spring.application.name}:comma,separated,profiles:${server.port}. For most cases, this will allow multiple instances of one service to run on one machine. If further uniqueness is required, Using Spring Cloud you can override this by providing a unique identifier in spring.cloud.consul.discovery.instanceId. For example:
With this metadata, and multiple service instances deployed on localhost, the random value will kick in there to make the instance unique. In Cloudfoundry the vcap.application.instance_id will be populated automatically in a Spring Boot application, so the random value will not be needed.
=== Using the DiscoveryClient
-Spring Cloud has support for Feign (a REST client builder) and also Spring RestTemplate using the logical service names instead of physical URLs.
Spring Cloud has support for Feign (a REST client builder) and also Spring RestTemplate using the logical service names instead of physical URLs.
You can also use the org.springframework.cloud.client.discovery.DiscoveryClient which provides a simple API for discovery clients that is not specific to Netflix, e.g.
== Distributed Configuration with Consul
Consul provides a Key/Value Store for storing configuration and other metadata. Spring Cloud Consul Config is an alternative to the Config Server and Client. Configuration is loaded into the Spring Environment during the special "bootstrap" phase. Configuration is stored in the /config folder by default. Multiple PropertySource instances are created based on the application’s name and the active profiles that mimicks the Spring Cloud Config order of resolving properties. For example, an application with the name "testApp" and with the "dev" profile will have the following property sources created:
Configuration is currently read on startup of the application. Sending a HTTP POST to /refresh will cause the configuration to be reloaded. Watching the key value store (which Consul supports) is not currently possible, but will be a future addition to this project.
=== How to activate
-To get started with Consul Configuration use the starter with group org.springframework.cloud and artifact id spring-cloud-starter-consul-config. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.
This will enable auto-configuration that will setup Spring Cloud Consul Config.
=== Customizing
Consul Config may be customized using the following properties:
=== Config Watch
The Consul Config Watch takes advantage of the ability of consul to watch a key prefix. The Config Watch makes a blocking Consul HTTP API call to determine if any relevant configuration data has changed for the current application. If there is new configuration data a Refresh Event is published. This is equivalent to calling the /refresh actuator endpoint.
To disable the Config Watch set spring.cloud.consul.config.watch.enabled=false.
=== YAML or Properties with Config
It may be more convenient to store a blob of properties in YAML or Properties format as opposed to individual key/value pairs. Set the spring.cloud.consul.config.format property to YAML or PROPERTIES. For example to use YAML:
You can change the data key using spring.cloud.consul.config.data-key.
=== git2consul with Config
-git2consul is a Consul community project that loads files from a git repository to individual keys into Consul. By default the names of the keys are names of the files. YAML and Properties files are supported with file extensions of .yml and .properties respectively. Set the spring.cloud.consul.config.format property to FILES. For example:
git2consul is a Consul community project that loads files from a git repository to individual keys into Consul. By default the names of the keys are names of the files. YAML and Properties files are supported with file extensions of .yml and .properties respectively. Set the spring.cloud.consul.config.format property to FILES. For example:
The value of each key needs to be a properly formatted YAML or Properties file.
=== Fail Fast
It may be convenient in certain circumstances (like local development or certain test scenarios) to not fail if consul isn’t available for configuration. Setting spring.cloud.consul.config.failFast=false in bootstrap.yml will cause the configuration module to log a warning rather than throw an exception. This will allow the application to continue startup normally.
== Consul Retry
If you expect that the consul agent may occasionally be unavailable when
your app starts, you can ask it to keep trying after a failure. You need to add
@@ -12275,27 +9104,33 @@ Retry has a RetryInterceptorBuilder that makes it easy to create on
== Spring Cloud Bus with Consul
=== How to activate
To get started with the Consul Bus use the starter with group org.springframework.cloud and artifact id spring-cloud-starter-consul-bus. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.
See the Spring Cloud Bus documentation for the available actuator endpoints and howto send custom messages.
== Circuit Breaker with Hystrix
Applications can use the Hystrix Circuit Breaker provided by the Spring Cloud Netflix project by including this starter in the projects pom.xml: spring-cloud-starter-hystrix. Hystrix doesn’t depend on the Netflix Discovery Client. The @EnableHystrix annotation should be placed on a configuration class (usually the main class). Then methods can be annotated with @HystrixCommand to be protected by a circuit breaker. See the documentation for more details.
== Hystrix metrics aggregation with Turbine and Consul
Turbine (provided by the Spring Cloud Netflix project), aggregates multiple instances Hystrix metrics streams, so the dashboard can display an aggregate view. Turbine uses the DiscoveryClient interface to lookup relevant instances. To use Turbine with Spring Cloud Consul, configure the Turbine application in a manner similar to the following examples:
= Spring Cloud Zookeeper
This project provides Zookeeper integrations for Spring Boot apps through autoconfiguration +
== Install Zookeeper -Please see the installation documentation for instructions on how to install Zookeeper.
== Service Discovery with Zookeeper
+Please see the installation documentation for instructions on how to install Zookeeper.
Service Discovery is one of the key tenets of a microservice based architecture. Trying to hand configure each client or some form of convention can be very difficult to do and can be very brittle. Curator(A java library for Zookeeper) provides Service Discovery services via Service Discovery Extension. Spring Cloud Zookeeper leverages this extension for service registration and discovery.
=== How to activate
-Including a dependency on org.springframework.cloud:spring-cloud-starter-zookeeper-discovery will enable auto-configuration that will setup Spring Cloud Zookeeper Discovery.
=== Registering with Zookeeper
When a client registers with Zookeeper, it provides meta-data about itself such as host and port, id and name.
@EnableDiscoveryClient makes the app into both a Zookeeper "service" (i.e. it registers itself) and a "client" (i.e. it can query Zookeeper to locate other services).
=== Using the DiscoveryClient
-Spring Cloud has support for Feign (a REST client builder) and also Spring RestTemplate using the logical service names instead of physical URLs.
Spring Cloud has support for Feign (a REST client builder) and also Spring RestTemplate using the logical service names instead of physical URLs.
You can also use the org.springframework.cloud.client.discovery.DiscoveryClient which provides a simple API for discovery clients that is not specific to Netflix, e.g.
== Zookeeper Dependencies
=== Using the Zookeeper Dependencies
Spring Cloud Zookeeper gives you a possibility to provide dependencies of your application as properties. As dependencies you can understand other applications that are registered
in Zookeeper and which you would like to call via Feign (a REST client builder)
@@ -12463,9 +9307,9 @@ and also
You can also benefit from the Zookeeper Dependency Watchers functionality that lets you control and monitor what is the state of your dependencies and decide what to do with that.
=== How to activate Zookeeper Dependencies
=== Setting up Zookeeper Dependencies
Let’s take a closer look at an example of dependencies representation:
Let’s now go through each part of the dependency one by one. The root property name is spring.cloud.zookeeper.dependencies.
==== Aliases
-Below the root property you have to represent each dependency has by an alias due to the constraints of Ribbon (the application id has to be placed in the URL
thus you can’t pass any complex path like /foo/bar/name). The alias will be the name that you will use instead of serviceId for DiscoveryClient, Feign or RestTemplate.
==== Path
Represented by path yaml property.
Path is the path under which the dependency is registered under Zookeeper. Like presented before Ribbon operates on URLs thus this path is not compliant with its requirement. That is why Spring Cloud Zookeeper maps the alias to the proper path.
==== Load balancer type
Represented by loadBalancerType yaml property.
==== Content-Type template and version
Represented by contentTypeTemplate and version yaml property.
application/vnd.newsletter.v1+json
==== Default headers
Represented by headers map in yaml
headers section:
Results in adding the Accept and Cache-Control headers with appropriate list of values in your HTTP request.
==== Obligatory dependencies
Represented by required property in yaml
You can read more about Spring Cloud Zookeeper Presence Checker in the following sections.
==== Stubs
You can provide a colon separated path to the JAR containing stubs of the dependency. Example
since stubs is the default classifier.
=== Configuring Spring Cloud Zookeeper Dependencies
There is a bunch of properties that you can set to enable / disable parts of Zookeeper Dependencies functionalities.
== Spring Cloud Zookeeper Dependency Watcher
The Dependency Watcher mechanism allows you to register listeners to your dependencies. The functionality is in fact an implementation of the Observator pattern. When a dependency changes
its state (UP or DOWN) then some custom logic can be applied.
=== How to activate
-Spring Cloud Zookeeper Dependencies functionality needs to be enabled to profit from Dependency Watcher mechanism.
=== Registering a listener
In order to register a listener you have to implement an interface org.springframework.cloud.zookeeper.discovery.watcher.DependencyWatcherListener and register it as a bean.
The interface gives you one method:
If you want to register a listener for a particular dependency then the dependencyName would be the discriminator for your concrete implementation. newState will provide you with information
whether your dependency has changed to CONNECTED or DISCONNECTED.
=== Presence Checker
Bound with Dependency Watcher is the functionality called Presence Checker. It allows you to provide custom behaviour upon booting of your application to react accordingly to the state of your dependencies.
@@ -12761,9 +9607,12 @@ of your dependencies.The functionality can be overriden since the DefaultDependencyPresenceOnStartupVerifier is registered only when there is no bean of DependencyPresenceOnStartupVerifier.
== Distributed Configuration with Zookeeper
Zookeeper provides a hierarchical namespace that allows clients to store arbitrary data, such as configuration data. Spring Cloud Zookeeper Config is an alternative to the Config Server and Client. Configuration is loaded into the Spring Environment during the special "bootstrap" phase. Configuration is stored in the /config namespace by default. Multiple PropertySource instances are created based on the application’s name and the active profiles that mimicks the Spring Cloud Config order of resolving properties. For example, an application with the name "testApp" and with the "dev" profile will have the following property sources created:
Configuration is currently read on startup of the application. Sending a HTTP POST to /refresh will cause the configuration to be reloaded. Watching the configuration namespace (which Zookeeper supports) is not currently implemented, but will be a future addition to this project.
=== How to activate
-Including a dependency on org.springframework.cloud:spring-cloud-starter-zookeeper-config will enable auto-configuration that will setup Spring Cloud Zookeeper Config.
=== Customizing
Zookeeper Config may be customized using the following properties:
Unresolved directive in spring-cloud.adoc - include::/Users/ryanjbaxter/git-repos/spring-cloud/scripts/docs/../cli/docs/src/main/asciidoc/spring-cloud-cli.adoc[]
+Unresolved directive in spring-cloud.adoc - include::/Users/ryanjbaxter/git-repos/ryanjbaxter/spring-cloud/scripts/docs/../cli/docs/src/main/asciidoc/spring-cloud-cli.adoc[]
= Spring Cloud Security -:github: https://github.com/spring-cloud/spring-cloud-security -:githubmaster: https://github.com/spring-cloud/spring-cloud-config/tree/master -:docslink: https://github.com/spring-cloud/spring-cloud-config/tree/master/src/main/asciidoc
Spring Cloud Security offers a set of primitives for building secure applications and services with minimum fuss. A declarative model which @@ -12849,17 +9697,18 @@ exchange.
== Quickstart
=== OAuth2 Single Sign On
Here’s a Spring Cloud "Hello World" app with HTTP Basic authentication and a single user account:
@@ -12955,9 +9804,9 @@ to the classpath (e.g. see the=== OAuth2 Protected Resource
You want to protect an API resource with an OAuth2 token? Here’s a simple example (paired with the client above):
@@ -12991,12 +9840,14 @@ class Application { preferTokenInfo: false== More Detail
=== Single Sign On
=== Token Relay
A Token Relay is where an OAuth2 consumer acts as a Client and forwards the incoming token to outgoing resource requests. The consumer can be a pure Client (like an SSO application) or a Resource Server.
==== Client Token Relay
-If your app is a user facing OAuth2 client (i.e. has declared
@EnableOAuth2Sso or @EnableOAuth2Client) then it has an
@@ -13033,9 +9883,9 @@ always forward the access token downstream, also refreshing the access
token automatically if it expires. (These are features of Spring
Security and Spring Boot.)
==== Client Token Relay in Zuul Proxy
If your app also has a Spring @@ -13068,13 +9918,13 @@ correct header.
traditional app), and that in turn triggers some autoconfiguration for aZuulFilter, which itself is activated because Zuul is on the
classpath (via @EnableZuulProxy). The
-filter
+filter
just extracts an access token from the currently authenticated user,
and puts it in a request header for the downstream requests.
==== Resource Server Token Relay
If your app has @EnableOAuth2Resource you might want to relay the
incoming token downstream to other services. If you use a
@@ -13146,9 +9996,13 @@ client that sent you the token), then you only need to create your own
OAuth2ClientContext if it is available, so they should also do a
token relay anywhere where a RestTemplate would.
== Configuring Authentication Downstream of a Zuul Proxy
You can control the authorization behaviour downstream of an
@EnableZuulProxy through the proxy.auth.* settings. Example:
See - + ProxyAuthenticationProperties for full details.
= Spring Cloud for Cloud Foundry -:nofooter:
Spring Cloud for Cloudfoundry makes it easy to run
Spring Cloud apps in
@@ -13208,9 +10063,11 @@ can use the The first time you use it the discovery client might be slow owing to
the fact that it has to get an access token from Cloud Foundry.DiscoveryClient directly or via a LoadBalancerCli
== Discovery
Here’s a Spring Cloud app with Cloud Foundry discovery:
== Single Sign On
=== Using Files to Specify the Stub Bodies
WireMock can read response bodies from files on the classpath or file
system. In that case you will see in the JSON DSL that the response
@@ -13414,9 +10297,9 @@ effect on the stubs loaded explicitly from the stubs attribute.
=== Alternative: Using JUnit Rules
For a more conventional WireMock experience, using JUnit @Rules to
start and stop the server, just use the WireMockSpring convenience
@@ -13424,23 +10307,68 @@ class to obtain an Options instance:
Unresolved directive in rc/main/asciidoc/spring-cloud-wiremock.adoc - include::{doc_samples}/src/test/java/com/example/WiremockForDocsClassRuleTests.java[tags=wiremock_test1]
-Unresolved directive in rc/main/asciidoc/spring-cloud-wiremock.adoc - include::{doc_samples}/src/test/java/com/example/WiremockForDocsClassRuleTests.java[tags=wiremock_test2]
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+public class WiremockForDocsClassRuleTests {
+
+ // Start WireMock on some dynamic port
+ // for some reason `dynamicPort()` is not working properly
+ @ClassRule
+ public static WireMockClassRule wiremock = new WireMockClassRule(
+ WireMockSpring.options().dynamicPort());
+ // A service that calls out over HTTP to localhost:${wiremock.port}
+ @Autowired
+ private Service service;
+
+ // Using the WireMock APIs in the normal way:
+ @Test
+ public void contextLoads() throws Exception {
+ // Stubbing WireMock
+ wiremock.stubFor(get(urlEqualTo("/resource"))
+ .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!")));
+ // We're asserting if WireMock responded properly
+ assertThat(this.service.go()).isEqualTo("Hello World!");
+ }
+
+}
The use @ClassRule means that the server will shut down after all the methods in this class.
== WireMock and Spring MVC Mocks
Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into a
Spring MockRestServiceServer. Here’s an example:
Unresolved directive in rc/main/asciidoc/spring-cloud-wiremock.adoc - include::{doc_samples}/src/test/java/com/example/WiremockForDocsMockServerApplicationTests.java[tags=wiremock_test]
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.NONE)
+public class WiremockForDocsMockServerApplicationTests {
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ @Autowired
+ private Service service;
+
+ @Test
+ public void contextLoads() throws Exception {
+ // will read stubs classpath
+ MockRestServiceServer server = WireMockRestServiceServer.with(this.restTemplate)
+ .baseUrl("http://example.org").stubs("classpath:/stubs/resource.json")
+ .build();
+ // We're asserting if WireMock responded properly
+ assertThat(this.service.go()).isEqualTo("Hello World");
+ server.verify();
+ }
+}
== Generating Stubs using RestDocs
Spring RestDocs can be
used to generate documentation (e.g. in asciidoctor format) for an
@@ -13609,9 +10539,11 @@ available on the classpath, you can create a stub using WireMock in a
number of different ways, including as described above using
@AutoConfigureWireMock(stubs="classpath:resource.json").
== Generating Contracts using RestDocs
Another thing that can be generated with Spring RestDocs is the Spring Cloud Contract DSL file and documentation. If you combine that with Spring Cloud @@ -13636,7 +10568,20 @@ only contracts and not generate the stubs. That’s why we suggest to do bot
Unresolved directive in rc/main/asciidoc/spring-cloud-wiremock.adoc - include::{wiremock_tests}/src/test/java/org/springframework/cloud/contract/wiremock/restdocs/ContractDslSnippetTests.java[tags=contract_snippet]
+ this.mockMvc.perform(post("/foo")
+ .accept(MediaType.APPLICATION_PDF)
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content("{\"foo\": 23 }"))
+ .andExpect(status().isOk())
+ .andExpect(content().string("bar"))
+ // first WireMock
+ .andDo(WireMockRestDocs.verify()
+ .jsonPath("$[?(@.foo >= 20)]")
+ .contentType(MediaType.valueOf("application/json"))
+ .stub("shouldGrantABeerIfOldEnough"))
+ // then Contract DSL documentation
+ .andDo(document("index", SpringCloudContractRestDocs.dslContract()));
the generated document (example for Asciidoc) will contain a formatted contract
(the location of this file would be index/dsl-contract.adoc).
== Spring Cloud Contract Verifier
=== Introduction
==== Client Side
During the tests you want to have a WireMock instance / Messaging route up and running that simulates the service Y. You would like to feed that instance with a proper stub definition. That stub definition would need @@ -13914,9 +10859,9 @@ to be valid and should also be reusable on the server side.
Summing it up: On this side, in the stub definition, you can use patterns for request stubbing and you need exact values for responses.
==== Server Side
Being a service Y since you are developing your stub, you need to be sure that it’s actually resembling your concrete implementation. You can’t have a situation where your stub acts in one way and your application on @@ -13930,9 +10875,9 @@ that your application behaves in the same way as you define in your stub.
Summing it up: On this side, in the stub definition, you need exact values as request and can use patterns/methods for response verification.
==== Step by step guide to CDC
Let’s take an example of Fraud Detection and Loan Issuance process. The business scenario is such that we want to issue loans to people but don’t want them to steal the money from us. The current implementation of our system grants loans to everybody.
===== Technical note
-If using the SNAPSHOT / Milestone / Release Candidate versions please add the following section to your
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=repos,indent=0]
+<repositories>
+ <repository>
+ <id>spring-snapshots</id>
+ <name>Spring Snapshots</name>
+ <url>https://repo.spring.io/snapshot</url>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>spring-milestones</id>
+ <name>Spring Milestones</name>
+ <url>https://repo.spring.io/milestone</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>spring-releases</id>
+ <name>Spring Releases</name>
+ <url>https://repo.spring.io/release</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+</repositories>
+<pluginRepositories>
+ <pluginRepository>
+ <id>spring-snapshots</id>
+ <name>Spring Snapshots</name>
+ <url>https://repo.spring.io/snapshot</url>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </pluginRepository>
+ <pluginRepository>
+ <id>spring-milestones</id>
+ <name>Spring Milestones</name>
+ <url>https://repo.spring.io/milestone</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+ <pluginRepository>
+ <id>spring-releases</id>
+ <name>Spring Releases</name>
+ <url>https://repo.spring.io/release</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+</pluginRepositories>
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/build.gradle[tags=deps_repos,indent=0]
+repositories {
+ mavenCentral()
+ mavenLocal()
+ maven { url "http://repo.spring.io/snapshot" }
+ maven { url "http://repo.spring.io/milestone" }
+ maven { url "http://repo.spring.io/release" }
+}
===== Consumer side (Loan Issuance)
As a developer of the Loan Issuance service (a consumer of the Fraud Detection server):
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=client_tdd,indent=0]
+@Test
+public void shouldBeRejectedDueToAbnormalLoanAmount() {
+ // given:
+ LoanApplication application = new LoanApplication(new Client("1234567890"),
+ 99999);
+ // when:
+ LoanApplicationResult loanApplication = service.loanApplication(application);
+ // then:
+ assertThat(loanApplication.getLoanApplicationStatus())
+ .isEqualTo(LoanApplicationStatus.LOAN_APPLICATION_REJECTED);
+ assertThat(loanApplication.getRejectionReason()).isEqualTo("Amount too high");
+}
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/src/main/java/com/example/loan/LoanApplicationService.java[tags=client_call_server,indent=0]
+ResponseEntity<FraudServiceResponse> response =
+ restTemplate.exchange("http://localhost:" + port + "/fraudcheck", HttpMethod.PUT,
+ new HttpEntity<>(request, httpHeaders),
+ FraudServiceResponse.class);
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.groovy[]
+package contracts
+
+org.springframework.cloud.contract.spec.Contract.make {
+ request { // (1)
+ method 'PUT' // (2)
+ url '/fraudcheck' // (3)
+ body([ // (4)
+ clientId: $(regex('[0-9]{10}')),
+ loanAmount: 99999
+ ])
+ headers { // (5)
+ contentType('application/vnd.fraud.v1+json')
+ }
+ }
+ response { // (6)
+ status 200 // (7)
+ body([ // (8)
+ fraudCheckStatus: "FRAUD",
+ rejectionReason: "Amount too high"
+ ])
+ headers { // (9)
+ contentType('application/vnd.fraud.v1+json')
+ }
+ }
+}
+
+/*
+Since we don't want to force on the user to hardcode values of fields that are dynamic
+(timestamps, database ids etc.), one can parametrize those entries. If you wrap your field's
+ value in a `$(...)` or `value(...)` and provide a dynamic value of a field then
+ the concrete value will be generated for you. If you want to be really explicit about
+ which side gets which value you can do that by using the `value(consumer(...), producer(...))` notation.
+ That way what's present in the `consumer` section will end up in the produced stub. What's
+ there in the `producer` will end up in the autogenerated test. If you provide only the
+ regular expression side without the concrete value then Spring Cloud Contract will generate one for you.
+
+From the Consumer perspective, when shooting a request in the integration test:
+
+(1) - If the consumer sends a request
+(2) - With the "PUT" method
+(3) - to the URL "/fraudcheck"
+(4) - with the JSON body that
+ * has a field `clientId` that matches a regular expression `[0-9]{10}`
+ * has a field `loanAmount` that is equal to `99999`
+(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
+(6) - then the response will be sent with
+(7) - status equal `200`
+(8) - and JSON body equal to
+ { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
+(9) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
+
+From the Producer perspective, in the autogenerated producer-side test:
+
+(1) - A request will be sent to the producer
+(2) - With the "PUT" method
+(3) - to the URL "/fraudcheck"
+(4) - with the JSON body that
+ * has a field `clientId` that will have a generated value that matches a regular expression `[0-9]{10}`
+ * has a field `loanAmount` that is equal to `99999`
+(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
+(6) - then the test will assert if the response has been sent with
+(7) - status equal `200`
+(8) - and JSON body equal to
+ { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
+(9) - with header `Content-Type` matching `application/vnd.fraud.v1+json.*`
+ */
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_bom,indent=0]
+<dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-dependencies</artifactId>
+ <version>${spring-cloud-dependencies.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+</dependencyManagement>
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_maven_plugin,indent=0]
+<plugin>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-contract-maven-plugin</artifactId>
+ <version>${spring-cloud-contract.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <packageWithBaseClasses>com.example.fraud</packageWithBaseClasses>
+ </configuration>
+</plugin>
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=contract_bom,indent=0]
+<dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-dependencies</artifactId>
+ <version>${spring-cloud-dependencies.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+</dependencyManagement>
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/pom.xml[tags=stub_runner,indent=0]
+<dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
+ <scope>test</scope>
+</dependency>
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=autoconfigure_stubrunner,indent=0]
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment=WebEnvironment.NONE)
+@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, workOffline = true)
+@DirtiesContext
+public class LoanApplicationServiceTests {
Once we’re satisfied with the results and the test passes publish a PR to the server side. Currently the consumer side work is done.
===== Producer side (Fraud Detection server)
As a developer of the Fraud Detection server (a server to the Loan Issuance service):
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=server_api,indent=0]
-Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=initial_impl,indent=0]
+@RequestMapping(
+ value = "/fraudcheck",
+ method = PUT,
+ consumes = FRAUD_SERVICE_JSON_VERSION_1,
+ produces = FRAUD_SERVICE_JSON_VERSION_1)
+public FraudCheckResult fraudCheck(@RequestBody FraudCheck fraudCheck) {
+return new FraudCheckResult(FraudCheckStatus.OK, NO_REASON);
}
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=verifier_test_dependencies,indent=0]
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-contract-verifier</artifactId>
+ <scope>test</scope>
+</dependency>
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/pom.xml[tags=contract_maven_plugin,indent=0]
+<plugin>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-contract-maven-plugin</artifactId>
+ <version>${spring-cloud-contract.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <packageWithBaseClasses>com.example.fraud</packageWithBaseClasses>
+ </configuration>
+</plugin>
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/test/java/com/example/fraud/FraudBase.java[]
+package com.example.fraud;
+
+import com.example.fraud.FraudDetectionController;
+import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc;
+
+import org.junit.Before;
+
+public class FraudBase {
+
+ @Before
+ public void setup() {
+ RestAssuredMockMvc.standaloneSetup(new FraudDetectionController());
+ }
+
+ public void assertThatRejectionReasonIsNull(Object rejectionReason) {
+ assert rejectionReason == null;
+ }
+}
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=server_api,indent=0]
-Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=new_impl,indent=0]
-Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/dsl/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=initial_impl,indent=0]
+@RequestMapping(
+ value = "/fraudcheck",
+ method = PUT,
+ consumes = FRAUD_SERVICE_JSON_VERSION_1,
+ produces = FRAUD_SERVICE_JSON_VERSION_1)
+public FraudCheckResult fraudCheck(@RequestBody FraudCheck fraudCheck) {
+if (amountGreaterThanThreshold(fraudCheck)) {
+ return new FraudCheckResult(FraudCheckStatus.FRAUD, AMOUNT_TOO_HIGH);
+}
+return new FraudCheckResult(FraudCheckStatus.OK, NO_REASON);
}
Then we assume that your CI would run sth like ./mvnw clean deploy which would publish both the application and the stub artifcats.
===== Consumer side (Loan Issuance) final step
As a developer of the Loan Issuance service (a consumer of the Fraud Detection server):
And that’s it!
==== Dependencies
The best way to add the dependencies is to just use the proper starter dependency.
For stub-runner use spring-cloud-starter-stub-runner and when you’re using a plugin just add
spring-cloud-starter-contract-verifier.
==== Additional links
Below you can find some resources related to Spring Cloud Contract Verifier and Stub Runner. Note that some can be outdated since the Spring Cloud Contract Verifier project is under constant development.
===== Readings
-==== Samples
Here you can find some samples.
=== FAQ
==== Why use Spring Cloud Contract Verifier and not X ?
For the time being Spring Cloud Contract Verifier is a JVM based tool. So it could be your first pick when you’re already creating software for the JVM. This project has a lot of really interesting features but especially quite a few of them definitely make @@ -14495,9 +11653,9 @@ Spring Cloud Contract Verifier stand out on the "market" of Consumer Driven Cont
==== What is this value(consumer(), producer()) ?
One of the biggest challenges related to stubs is their reusability. Only if they can be vastly used, will they serve their purpose. What typically makes that difficult are the hard-coded values of request / response elements. For example dates or ids. @@ -14675,12 +11833,11 @@ properly structure the request / response bodies.
==== How to do Stubs versioning?
-===== API Versioning
Let’s try to answer a question what versioning really means. If you’re referring to the API version then there are different approaches.
@@ -14703,9 +11860,9 @@ business value should be picked.Let’s assume that you do version your API. In that case you should provide as many contracts as many versions you support. You can create a subfolder for every version or append it to th contract name - whatever suits you more.
===== JAR versioning
If by versioning you mean the version of the JAR that contains the stubs then there are essentially two main approaches.
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:2.1.1:stubs:8080"})
===== Dev or prod stubs
You can manipulate the classifier to run the tests against current development version of the stubs of other services
or the ones that were deployed to production. If you alter your build to deploy the stubs with the prod-stubs classifier
@@ -14772,18 +11929,18 @@ version. Example for 2.1.1.
You can pass those values also via properties from your deployment pipeline.
==== Common repo with contracts
Another way of storing contracts other than having them with the producer is keeping them in a common place. It can be related to security issues where the consumers can’t clone the producer’s code. Also if you keep contracts in a single place then you, as a producer, will know how many consumers you have and which consumer will you break with your local changes.
===== Repo structure
-Let’s assume that we have a producer with coordinates com.example:server and 3 consumers: client1,
client2, client3. Then in the repository with common contracts you would have the following setup
@@ -14820,7 +11977,114 @@ one to one to the contents of the repo.
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/contracts/com/example/server/pom.xml[indent=0]
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>com.example</groupId>
+ <artifactId>server</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+
+ <name>Server Stubs</name>
+ <description>POM used to install locally stubs for consumer side</description>
+
+ <parent>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-parent</artifactId>
+ <version>1.4.2.BUILD-SNAPSHOT</version>
+ <relativePath />
+ </parent>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <java.version>1.8</java.version>
+ <spring-cloud-contract.version>1.0.5.BUILD-SNAPSHOT</spring-cloud-contract.version>
+ <spring-cloud-dependencies.version>Camden.BUILD-SNAPSHOT</spring-cloud-dependencies.version>
+ <excludeBuildFolders>true</excludeBuildFolders>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-dependencies</artifactId>
+ <version>${spring-cloud-dependencies.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-contract-maven-plugin</artifactId>
+ <version>${spring-cloud-contract.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <!-- By default it would search under src/test/resources/ -->
+ <contractsDirectory>${project.basedir}</contractsDirectory>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <repositories>
+ <repository>
+ <id>spring-snapshots</id>
+ <name>Spring Snapshots</name>
+ <url>https://repo.spring.io/snapshot</url>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>spring-milestones</id>
+ <name>Spring Milestones</name>
+ <url>https://repo.spring.io/milestone</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>spring-releases</id>
+ <name>Spring Releases</name>
+ <url>https://repo.spring.io/release</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
+ <pluginRepositories>
+ <pluginRepository>
+ <id>spring-snapshots</id>
+ <name>Spring Snapshots</name>
+ <url>https://repo.spring.io/snapshot</url>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </pluginRepository>
+ <pluginRepository>
+ <id>spring-milestones</id>
+ <name>Spring Milestones</name>
+ <url>https://repo.spring.io/milestone</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+ <pluginRepository>
+ <id>spring-releases</id>
+ <name>Spring Releases</name>
+ <url>https://repo.spring.io/release</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+ </pluginRepositories>
+
+</project>
mvn clean install -D
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/contracts/pom.xml[indent=0]
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>com.example.standalone</groupId>
+ <artifactId>contracts</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+
+ <name>Contracts</name>
+ <description>Contains all the Spring Cloud Contracts, well, contracts. JAR used by the producers to generate tests and stubs</description>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>contracts</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <attach>true</attach>
+ <descriptor>${basedir}/src/assembly/contracts.xml</descriptor>
+ <!-- If you want an explicit classifier remove the following line -->
+ <appendAssemblyId>false</appendAssemblyId>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
mvn clean install -D
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/samples/standalone/contracts/src/assembly/contracts.xml[indent=0]
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+ <id>project</id>
+ <formats>
+ <format>jar</format>
+ </formats>
+ <includeBaseDirectory>false</includeBaseDirectory>
+ <fileSets>
+ <fileSet>
+ <directory>${project.basedir}</directory>
+ <outputDirectory>/</outputDirectory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ <excludes>
+ <exclude>**/${project.build.directory}/**</exclude>
+ <exclude>mvnw</exclude>
+ <exclude>mvnw.cmd</exclude>
+ <exclude>.mvn/**</exclude>
+ <exclude>src/**</exclude>
+ </excludes>
+ </fileSet>
+ </fileSets>
+</assembly>
===== Workflow
The workflow would look similar to the one presented in the Step by step guide to CDC. The only difference
is that the producer doesn’t own the contracts anymore. So the consumer and the producer have to work on
common contracts in a common repository.
====== Consumer
-When the consumer wants to work on the contracts offline, instead of cloning the producer code, the
consumer team clones the common repository, goes to the required producer’s folder (e.g. com/example/server)
@@ -14872,16 +12197,26 @@ You need to have Maven installed
====== Producer
As a producer it’s enough to alter the Spring Cloud Contract Verifier to provide the URL and the dependency of the JAR containing the contracts:
Unresolved directive in rc/main/asciidoc/verifier/introduction.adoc - include::{introduction_url}/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/basic-remote-contracts/pom-with-repo.xml[tags=remote_config,indent=0]
+<plugin>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-contract-maven-plugin</artifactId>
+ <configuration>
+ <contractsRepositoryUrl>http://link/to/your/nexus/or/artifactory/or/sth</contractsRepositoryUrl>
+ <contractDependency>
+ <groupId>com.example.standalone</groupId>
+ <artifactId>contracts</artifactId>
+ </contractDependency>
+ </configuration>
+</plugin>
The rest of the flow looks the same.
==== Can I have multiple base classes for tests?
Yes! Check out the Different base classes for contracts sections of either Gradle or Maven plugins.
=== Spring Cloud Contract Verifier HTTP
==== Gradle Project
-===== Prerequisites
In order to use Spring Cloud Contract Verifier with WireMock you have to use Gradle or Maven plugin.
spock-core and spock-spring modules. Check
====== Add gradle plugin with dependencies
-buildscript {
@@ -14959,21 +12294,28 @@ dependencies {
}
====== Snapshot versions for Gradle
Add the additional snapshot repository to your build.gradle to use snapshot versions which are automatically uploaded after every successful build:
Unresolved directive in rc/main/asciidoc/verifier/rest.adoc - include::{standalone_samples_path}/http-server/build.gradle[tags=repos,indent=0]
+buildscript {
+ repositories {
+ mavenCentral()
+ mavenLocal()
+ maven { url "http://repo.spring.io/snapshot" }
+ maven { url "http://repo.spring.io/milestone" }
+ maven { url "http://repo.spring.io/release" }
+ }
}
====== Add stubs
By default Spring Cloud Contract Verifier is looking for stubs in src/test/resources/contracts directory.
===== Run plugin
Plugin registers itself to be invoked before check task. You have nothing to do as long as you want it to be part of your build process. If you just want to generate tests please invoke generateContractTests task.
===== Default setup
Default Gradle Plugin setup creates the following Gradle part of the build (it’s a pseudocode)
===== Configure plugin
To change default configuration just add contracts snippet to your Gradle config
====== Configuration options
-====== Single base class for all tests
When using Spring Cloud Contract Verifier in default MockMvc you need to create a base specification for all generated acceptance tests. In this class you need to point to endpoint which should be verified.
Unresolved directive in rc/main/asciidoc/verifier/rest.adoc - include::{plugins_path}/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/bootSimple/src/test/groovy/org/springframework/cloud/contract/verifier/twitter/places/BaseMockMvcSpec.groovy[tags=base_class,indent=0]
+abstract class BaseMockMvcSpec extends Specification {
+
+ def setup() {
+ RestAssuredMockMvc.standaloneSetup(new PairIdController())
+ }
+
+ void isProperCorrelationId(Integer correlationId) {
+ assert correlationId == 123456
+ }
+
+ void isEmpty(String value) {
+ assert value == null
+ }
+
+}
In case of using Explicit mode, you can use base class to initialize the whole tested app similarly as in regular integration tests. In case of JAXRSCLIENT mode this base class
should also contain protected WebTarget webTarget field, right now the only option to test JAX-RS API is to start a web server.
====== Different base classes for contracts
If your base classes differ between contracts you can tell the Spring Cloud Contract plugin which class should get extended by the autogenerated tests. You have two options:
@@ -15177,7 +12533,7 @@ if they exist and form a class with aBase suffix. Takes precedence
Unresolved directive in rc/main/asciidoc/verifier/rest.adoc - include::{plugins_path}/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy[tags=package_with_base_classes,indent=0]
+packageWithBaseClasses = 'com.example.base'
Unresolved directive in rc/main/asciidoc/verifier/rest.adoc - include::{plugins_path}/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy[tags=base_class_mappings,indent=0]
+baseClassForTests = "com.example.FooBase"
+baseClassMappings {
+ baseClassMapping('.*/com/.*', 'com.example.ComBase')
+ baseClassMapping('.*/bar/.*':'com.example.BarBase')
+}
packageWithBaseClasses as fallback). That way the tests generated from src/test/resources/contract/com/ contracts
will be extending the com.example.ComBase whereas the rest of tests will extend com.example.FooBase.
===== Invoking generated tests
To ensure that provider side is complaint with defined contracts, you need to invoke:
com.example.ComBase whereas the rest of tests
./gradlew generateContractTests test
===== Spring Cloud Contract Verifier on consumer side
In consumer service you need to configure Spring Cloud Contract Verifier plugin in exactly the same way as in case of provider. If you don’t want to use Stub Runner then you need to copy contracts stored in
src/test/resources/contracts and generate WireMock json stubs using:
Underneath LoanApplication makes a call to FraudDetection service. This request is handled by WireMock server configured using stubs generated by Spring Cloud Contract Verifier.
==== Using in your Maven project
===== Add maven plugin
Add the Spring Cloud Contract BOM
Unresolved directive in rc/main/asciidoc/verifier/rest.adoc - include::{standalone_samples_path}/http-server/pom.xml[tags=contract_bom,indent=0]
+<dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-dependencies</artifactId>
+ <version>${spring-cloud-dependencies.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+</dependencyManagement>
Unresolved directive in rc/main/asciidoc/verifier/rest.adoc - include::{standalone_samples_path}/http-server/pom.xml[tags=contract_maven_plugin,indent=0]
+<plugin>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-contract-maven-plugin</artifactId>
+ <version>${spring-cloud-contract.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <packageWithBaseClasses>com.example.fraud</packageWithBaseClasses>
+ </configuration>
+</plugin>
You can read more in the Spring Cloud Contract Maven Plugin Docs
====== Snapshot versions for Maven
-For Snapshot / Milestone versions you have to add the following section to your pom.xml
Unresolved directive in rc/main/asciidoc/verifier/rest.adoc - include::{standalone_samples_path}/http-server/pom.xml[tags=repos,indent=0]
+<repositories>
+ <repository>
+ <id>spring-snapshots</id>
+ <name>Spring Snapshots</name>
+ <url>https://repo.spring.io/snapshot</url>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>spring-milestones</id>
+ <name>Spring Milestones</name>
+ <url>https://repo.spring.io/milestone</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>spring-releases</id>
+ <name>Spring Releases</name>
+ <url>https://repo.spring.io/release</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+</repositories>
+<pluginRepositories>
+ <pluginRepository>
+ <id>spring-snapshots</id>
+ <name>Spring Snapshots</name>
+ <url>https://repo.spring.io/snapshot</url>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </pluginRepository>
+ <pluginRepository>
+ <id>spring-milestones</id>
+ <name>Spring Milestones</name>
+ <url>https://repo.spring.io/milestone</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+ <pluginRepository>
+ <id>spring-releases</id>
+ <name>Spring Releases</name>
+ <url>https://repo.spring.io/release</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+</pluginRepositories>
===== Add stubs
By default Spring Cloud Contract Verifier is looking for stubs in src/test/resources/contracts directory.
Directory containing stub definitions is treated as a class name, and each stub definition is treated as a single test.
@@ -15315,15 +12745,15 @@ src/test/resources/contracts/myservice/shouldReturnUser.groovy
- shouldCreateUser()
- shouldReturnUser()
===== Run plugin
Plugin goal generateTests is assigned to be invoked in phase generate-test-sources. You have nothing to do as long as you want it to be part of your build process. If you just want to generate tests please invoke generateTests goal.
===== Configure plugin
To change default configuration just add configuration section to plugin definition or execution definition.
====== Important configuration options
-com.example.base
For complete information take a look at Plugin Documentation
-
-====== Single base class for all tests
+
+Single base class for all tests
When using Spring Cloud Contract Verifier in default MockMvc you need to create a base specification for all generated acceptance tests.
In this class you need to point to endpoint which should be verified.
@@ -15433,9 +12862,9 @@ class MvcSpec extends Specification {
In case of using Explicit mode, you can use base class to initialize the whole tested app similarly as in regular integration tests. In case of JAXRSCLIENT mode this base class should also contain protected WebTarget webTarget field, right now the only option to test JAX-RS API is to start a web server.
-
-====== Different base classes for contracts
+
+Different base classes for contracts
If your base classes differ between contracts you can tell the Spring Cloud Contract plugin which class should get
extended by the autogenerated tests. You have two options:
@@ -15460,7 +12889,13 @@ if they exist and form a class with a Base suffix. Takes precedence
-Unresolved directive in rc/main/asciidoc/verifier/rest.adoc - include::{plugins_path}/spring-cloud-contract-maven-plugin/src/test/projects/basic-generated-baseclass/pom.xml[tags=convention,indent=0]
+<plugin>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-contract-maven-plugin</artifactId>
+ <configuration>
+ <packageWithBaseClasses>hello</packageWithBaseClasses>
+ </configuration>
+</plugin>
@@ -15473,7 +12908,19 @@ Let’s take a look at the following example:
-Unresolved directive in rc/main/asciidoc/verifier/rest.adoc - include::{plugins_path}/spring-cloud-contract-maven-plugin/src/test/projects/basic-baseclass-from-mappings/pom.xml[tags=mapping,indent=0]
+<plugin>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-contract-maven-plugin</artifactId>
+ <configuration>
+ <baseClassForTests>com.example.FooBase</baseClassForTests>
+ <baseClassMappings>
+ <baseClassMapping>
+ <contractPackageRegex>.*com.*</contractPackageRegex>
+ <baseClassFQN>com.example.TestBase</baseClassFQN>
+ </baseClassMapping>
+ </baseClassMappings>
+ </configuration>
+</plugin>
@@ -15486,9 +12933,10 @@ Let’s take a look at the following example:
the packageWithBaseClasses as fallback). That way the tests generated from src/test/resources/contract/com/ contracts
will be extending the com.example.ComBase whereas the rest of tests will extend com.example.FooBase.
-
-===== Invoking generated tests
+
+
+Invoking generated tests
Spring Cloud Contract Maven Plugin generates verification code into directory /generated-test-sources/contractVerifier and attach this directory to testCompile goal.
@@ -15530,12 +12978,11 @@ will be extending the com.example.ComBase whereas the rest of tests
To ensure that provider side is complaint with defined contracts, you need to invoke mvn generateTest test
-
-===== FAQ with Maven Plugin
-
-
-====== Maven Plugin and STS
+
+FAQ with Maven Plugin
+
+Maven Plugin and STS
In case you see the following exception while using STS
@@ -15597,9 +13044,10 @@ will be extending the com.example.ComBase whereas the rest of tests
</build>
-
-===== Spring Cloud Contract Verifier on consumer side
+
+
+Spring Cloud Contract Verifier on consumer side
You can actually use the Spring Cloud Contract Verifier also for the consumer side!
You can use the plugin so that it only converts the contracts and generates the stubs.
@@ -15662,9 +13110,10 @@ public class LoanApplicationServiceTests {
Underneath LoanApplication makes a call to the FraudDetection service. This request is handled by
a WireMock server configured using stubs generated by Spring Cloud Contract Verifier.
-
-==== Scenarios
+
+
+Scenarios
It’s possible to handle scenarios with Spring Cloud Contract Verifier. All you need to do is to stick to proper naming convention while creating your contracts. The convention requires to include order number followed by the underscore.
@@ -15699,9 +13148,9 @@ a WireMock server configured using stubs generated by Spring Cloud Contract Veri
Spring Cloud Contract Verifier will also generate tests with guaranteed order of execution.
-
-==== Stubs and transitive dependencies
+
+Stubs and transitive dependencies
The Maven and Gradle plugin that we’re created are adding the tasks that create the stubs jar for you. What can be problematic
is that when reusing the stubs you can by mistake import all of that stub dependencies! When building a Maven artifact
@@ -15743,16 +13192,16 @@ all of the depenencies are optional, they will not get downloaded.
As a consumer, if you add the stub dependency to your classpath you can explicitly exclude the unwanted dependencies.
-
-=== Spring Cloud Contract Verifier Messaging
+
+
+Spring Cloud Contract Verifier Messaging
Spring Cloud Contract Verifier allows you to verify your application that uses messaging as means of communication.
All of our integrations are working with Spring but you can also create one yourself and use it.
-
-==== Integrations
-
+
+Integrations
You can use one of the four integration configurations:
@@ -15789,9 +13238,9 @@ generated tests. Otherwise messaging part of Spring Cloud Contract Verifier will
-
-==== Manual Integration Testing
+
+Manual Integration Testing
The main interface used by the tests is the org.springframework.cloud.contract.verifier.messaging.MessageVerifier.
It defines how to send and receive messages. You can create your own implementation to achieve the
@@ -15828,9 +13277,9 @@ you only need the one annotation.
-
-==== Publisher side test generation
+
+Publisher side test generation
Having the input or outputMessage sections in your DSL will result in creation of tests on the publisher’s side. By default
JUnit tests will be created, however there is also a possibility to create Spock tests.
@@ -15852,15 +13301,26 @@ inside the application (e.g. scheduler)
-
-===== Scenario 1 (no input message)
-
+
+Scenario 1 (no input message)
For the given contract:
-Unresolved directive in rc/main/asciidoc/verifier/messaging.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_method_dsl]
+def contractDsl = Contract.make {
+ label 'some_label'
+ input {
+ triggeredBy('bookReturnedTriggered()')
+ }
+ outputMessage {
+ sentTo('activemq:output')
+ body('''{ "bookName" : "foo" }''')
+ headers {
+ header('BOOK-NAME', 'foo')
+ }
+ }
+}
@@ -15868,7 +13328,18 @@ inside the application (e.g. scheduler)
-Unresolved directive in rc/main/asciidoc/verifier/messaging.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_method_junit_test]
+'''
+ // when:
+ bookReturnedTriggered();
+
+ // then:
+ ContractVerifierMessage response = contractVerifierMessaging.receive("activemq:output");
+ assertThat(response).isNotNull();
+ assertThat(response.getHeader("BOOK-NAME")).isEqualTo("foo");
+ // and:
+ DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload()));
+ assertThatJson(parsedJson).field("bookName").isEqualTo("foo");
+'''
@@ -15876,18 +13347,50 @@ inside the application (e.g. scheduler)
-Unresolved directive in rc/main/asciidoc/verifier/messaging.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_method_test]
+'''
+ when:
+ bookReturnedTriggered()
+
+ then:
+ ContractVerifierMessage response = contractVerifierMessaging.receive('activemq:output')
+ assert response != null
+ response.getHeader('BOOK-NAME') == 'foo'
+ and:
+ DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.payload))
+ assertThatJson(parsedJson).field("bookName").isEqualTo("foo")
+
+'''
-
-===== Scenario 2 (output triggered by input)
+
+Scenario 2 (output triggered by input)
For the given contract:
-Unresolved directive in rc/main/asciidoc/verifier/messaging.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_message_dsl]
+def contractDsl = Contract.make {
+ label 'some_label'
+ input {
+ messageFrom('jms:input')
+ messageBody([
+ bookName: 'foo'
+ ])
+ messageHeaders {
+ header('sample', 'header')
+ }
+ }
+ outputMessage {
+ sentTo('jms:output')
+ body([
+ bookName: 'foo'
+ ])
+ headers {
+ header('BOOK-NAME', 'foo')
+ }
+ }
+}
@@ -15895,7 +13398,24 @@ inside the application (e.g. scheduler)
-Unresolved directive in rc/main/asciidoc/verifier/messaging.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_message_junit]
+'''
+// given:
+ ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
+ "{\\"bookName\\":\\"foo\\"}"
+, headers()
+ .header("sample", "header"));
+
+// when:
+ contractVerifierMessaging.send(inputMessage, "jms:input");
+
+// then:
+ ContractVerifierMessage response = contractVerifierMessaging.receive("jms:output");
+ assertThat(response).isNotNull();
+ assertThat(response.getHeader("BOOK-NAME")).isEqualTo("foo");
+// and:
+ DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload()));
+ assertThatJson(parsedJson).field("bookName").isEqualTo("foo");
+'''
@@ -15903,18 +13423,47 @@ inside the application (e.g. scheduler)
-Unresolved directive in rc/main/asciidoc/verifier/messaging.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_message_spock]
+"""\
+given:
+ ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
+ '''{"bookName":"foo"}''',
+ ['sample': 'header']
+ )
+
+when:
+ contractVerifierMessaging.send(inputMessage, 'jms:input')
+
+then:
+ ContractVerifierMessage response = contractVerifierMessaging.receive('jms:output')
+ assert response !- null
+ response.getHeader('BOOK-NAME') == 'foo'
+and:
+ DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.payload))
+ assertThatJson(parsedJson).field("bookName").isEqualTo("foo")
+"""
-
-===== Scenario 3 (no output message)
+
+Scenario 3 (no output message)
For the given contract:
-Unresolved directive in rc/main/asciidoc/verifier/messaging.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_dsl]
+def contractDsl = Contract.make {
+ label 'some_label'
+ input {
+ messageFrom('jms:delete')
+ messageBody([
+ bookName: 'foo'
+ ])
+ messageHeaders {
+ header('sample', 'header')
+ }
+ assertThat('bookWasDeleted()')
+ }
+}
@@ -15922,7 +13471,19 @@ inside the application (e.g. scheduler)
-Unresolved directive in rc/main/asciidoc/verifier/messaging.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_junit]
+'''
+// given:
+ ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
+ "{\\"bookName\\":\\"foo\\"}"
+, headers()
+ .header("sample", "header"));
+
+// when:
+ contractVerifierMessaging.send(inputMessage, "jms:delete");
+
+// then:
+ bookWasDeleted();
+'''
@@ -15930,12 +13491,26 @@ inside the application (e.g. scheduler)
-Unresolved directive in rc/main/asciidoc/verifier/messaging.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=trigger_no_output_spock]
+'''
+given:
+ ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
+ \'\'\'{"bookName":"foo"}\'\'\',
+ ['sample': 'header']
+ )
+
+when:
+ contractVerifierMessaging.send(inputMessage, 'jms:delete')
+
+then:
+ noExceptionThrown()
+ bookWasDeleted()
+'''
-
-==== Consumer Stub Side generation
+
+
+Consumer Stub Side generation
Unlike the HTTP part - in Messaging we need to publish the Groovy DSL inside the JAR with a stub. Then it’s parsed on the consumer side
and proper stubbed routes are created.
@@ -15946,18 +13521,64 @@ and proper stubbed routes are created.
Maven
-Unresolved directive in rc/main/asciidoc/verifier/messaging.adoc - include::{standalone_messaging_samples_path}/stream-sink/pom.xml[tags=jars,indent=0]
+<dependencies>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-stream-test-support</artifactId>
+ <scope>test</scope>
+ </dependency>
+</dependencies>
+
+<dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-dependencies</artifactId>
+ <version>Camden.BUILD-SNAPSHOT</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+</dependencyManagement>
Gradle
-Unresolved directive in rc/main/asciidoc/verifier/messaging.adoc - include::{plugins_path}/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/build.gradle[tags=jar_setup,indent=0]
+ext {
+ contractsDir = file("mappings")
+ stubsOutputDirRoot = file("${project.buildDir}/production/${project.name}-stubs/")
+}
+
+// Automatically added by plugin:
+// copyContracts - copies contracts to the output folder from which JAR will be created
+// verifierStubsJar - JAR with a provided stub suffix
+// the presented publication is also added by the plugin but you can modify it as you wish
+
+publishing {
+ publications {
+ stubs(MavenPublication) {
+ artifactId "${project.name}-stubs"
+ artifact verifierStubsJar
+ }
+ }
+}
-
-=== Spring Cloud Contract Stub Runner
+
+
+Spring Cloud Contract Stub Runner
One of the issues that you could have encountered while using Spring Cloud Contract Verifier was to pass the generated WireMock JSON stubs from the server side to the client side (or various clients).
The same takes place in terms of client side generation for messaging.
@@ -15969,27 +13590,84 @@ and proper stubbed routes are created.
That’s why we’ll introduce Spring Cloud Contract Stub Runner that can download and run the stubs
automatically for you.
-
-==== Snapshot versions
-
+
+Snapshot versions
Add the additional snapshot repository to your build.gradle to use snapshot versions which are automatically uploaded after every successful build:
Maven
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner.adoc - include::{standalone_samples_path}/http-server/pom.xml[tags=repos,indent=0]
+<repositories>
+ <repository>
+ <id>spring-snapshots</id>
+ <name>Spring Snapshots</name>
+ <url>https://repo.spring.io/snapshot</url>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>spring-milestones</id>
+ <name>Spring Milestones</name>
+ <url>https://repo.spring.io/milestone</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>spring-releases</id>
+ <name>Spring Releases</name>
+ <url>https://repo.spring.io/release</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+</repositories>
+<pluginRepositories>
+ <pluginRepository>
+ <id>spring-snapshots</id>
+ <name>Spring Snapshots</name>
+ <url>https://repo.spring.io/snapshot</url>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </pluginRepository>
+ <pluginRepository>
+ <id>spring-milestones</id>
+ <name>Spring Milestones</name>
+ <url>https://repo.spring.io/milestone</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+ <pluginRepository>
+ <id>spring-releases</id>
+ <name>Spring Releases</name>
+ <url>https://repo.spring.io/release</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+</pluginRepositories>
Gradle
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner.adoc - include::{standalone_samples_path}/http-server/build.gradle[tags=repos,indent=0]
+buildscript {
+ repositories {
+ mavenCentral()
+ mavenLocal()
+ maven { url "http://repo.spring.io/snapshot" }
+ maven { url "http://repo.spring.io/milestone" }
+ maven { url "http://repo.spring.io/release" }
+ }
-
-==== Publishing stubs as JARs
+
+Publishing stubs as JARs
The easiest approach would be to centralize the way stubs are kept. For example you can keep them as JARs in a Maven repository.
@@ -16009,30 +13687,866 @@ For both Maven and Gradle the setup comes out of the box. But you can customize
Maven
<!-- First disable the default jar setup in the properties section-->
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner.adoc - include::{samples_url}/producer_with_restdocs/pom.xml[tags=skip_jar,indent=0]
+<!-- we don't want the verifier to do a jar for us -->
+<spring.cloud.contract.verifier.skip>true</spring.cloud.contract.verifier.skip>
<!-- Next add the assembly plugin to your build -->
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner.adoc - include::{samples_url}/producer_with_restdocs/pom.xml[tags=assembly,indent=0]
+<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>stub</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <inherited>false</inherited>
+ <configuration>
+ <attach>true</attach>
+ <descriptor>$/Users/ryanjbaxter/git-repos/ryanjbaxter/spring-cloud/scripts/docs/../src/assembly/stub.xml</descriptor>
+ </configuration>
+ </execution>
+ </executions>
+</plugin>
<!-- Finally setup your assembly. Below you can find the contents of src/main/assembly/stub.xml -->
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner.adoc - include::{samples_url}/producer_with_restdocs/src/assembly/stub.xml[indent=0]
+<assembly
+ xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+ <id>stubs</id>
+ <formats>
+ <format>jar</format>
+ </formats>
+ <includeBaseDirectory>false</includeBaseDirectory>
+ <fileSets>
+ <fileSet>
+ <directory>src/main/java</directory>
+ <outputDirectory>/</outputDirectory>
+ <includes>
+ <include>**com/example/model/*.*</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>${project.build.directory}/classes</directory>
+ <outputDirectory>/</outputDirectory>
+ <includes>
+ <include>**com/example/model/*.*</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>${project.build.directory}/snippets/stubs</directory>
+ <outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings</outputDirectory>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>$/Users/ryanjbaxter/git-repos/ryanjbaxter/spring-cloud/scripts/docs/../src/test/resources/contracts</directory>
+ <outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/contracts</outputDirectory>
+ <includes>
+ <include>**/*.groovy</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+</assembly>
Gradle
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner.adoc - include::{plugins_path}/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/build.gradle[tags=jar_setup,indent=0]
+ext {
+ contractsDir = file("mappings")
+ stubsOutputDirRoot = file("${project.buildDir}/production/${project.name}-stubs/")
+}
+
+// Automatically added by plugin:
+// copyContracts - copies contracts to the output folder from which JAR will be created
+// verifierStubsJar - JAR with a provided stub suffix
+// the presented publication is also added by the plugin but you can modify it as you wish
+
+publishing {
+ publications {
+ stubs(MavenPublication) {
+ artifactId "${project.name}-stubs"
+ artifact verifierStubsJar
+ }
+ }
+}
+
+
+
+
+Modules
+
+
+
+
+Stub Runner Core
+
+Runs stubs for service collaborators. Treating stubs as contracts of services allows to use stub-runner as an implementation of
+Consumer Driven Contracts.
+
+
+Stub Runner allows you to automatically download the stubs of the provided dependencies, start WireMock servers for them and feed them with proper stub definitions.
+For messaging, special stub routes are defined.
+
+
+Running stubs
+
+Limitations
+
+
+
+
+Important
+
+
+There might be a problem with StubRunner shutting down ports between tests. You might
+ have a situation in which you get port conflicts. As long as you use the same context across tests
+ everything works fine. But when the context are different (e.g. different stubs or different profiles)
+ then you have to either use @DirtiesContext to shut down the stub servers, or else run them on
+ different ports per test.
+
+
+
+
+
+
+Running using main app
+
+You can set the following options to the main class:
+
+
+
+-c, --classifier Suffix for the jar containing stubs (e.
+ g. 'stubs' if the stub jar would
+ have a 'stubs' classifier for stubs:
+ foobar-stubs ). Defaults to 'stubs'
+ (default: stubs)
+--maxPort, --maxp <Integer> Maximum port value to be assigned to
+ the WireMock instance. Defaults to
+ 15000 (default: 15000)
+--minPort, --minp <Integer> Minimum port value to be assigned to
+ the WireMock instance. Defaults to
+ 10000 (default: 10000)
+-p, --password Password to user when connecting to
+ repository
+--phost, --proxyHost Proxy host to use for repository
+ requests
+--pport, --proxyPort [Integer] Proxy port to use for repository
+ requests
+-r, --root Location of a Jar containing server
+ where you keep your stubs (e.g. http:
+ //nexus.
+ net/content/repositories/repository)
+-s, --stubs Comma separated list of Ivy
+ representation of jars with stubs.
+ Eg. groupid:artifactid1,groupid2:
+ artifactid2:classifier
+-u, --username Username to user when connecting to
+ repository
+--wo, --workOffline Switch to work offline. Defaults to
+ 'false'
+
+
+
+
+HTTP Stubs
+
+Stubs are defined in JSON documents, whose syntax is defined in WireMock documentation
+
+
+Example:
+
+
+
+{
+ "request": {
+ "method": "GET",
+ "url": "/ping"
+ },
+ "response": {
+ "status": 200,
+ "body": "pong",
+ "headers": {
+ "Content-Type": "text/plain"
+ }
+ }
+}
+
+
+
+
+Viewing registered mappings
+
+Every stubbed collaborator exposes list of defined mappings under __/admin/ endpoint.
+
+
+
+Messaging Stubs
+
+Depending on the provided Stub Runner dependency and the DSL the messaging routes are automatically set up.
+
+
+
+
+
+Stub Runner JUnit Rule
+
+Stub Runner comes with a JUnit rule thanks to which you can very easily download and run stubs for given group and artifact id:
+
+
+
+@ClassRule public static StubRunnerRule rule = new StubRunnerRule()
+ .repoRoot(repoRoot())
+ .downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")
+ .downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer");
-==== Modules
+After that rule gets executed Stub Runner connects to your Maven repository and for the given list of dependencies tries to:
+
+
+
+-
+
download them
+
+-
+
cache them locally
+
+-
+
unzip them to a temporary folder
+
+-
+
start a WireMock server for each Maven dependency on a random port from the provided range of ports / provided port
+
+-
+
feed the WireMock server with all JSON files that are valid WireMock definitions
+
+
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner.adoc - include::{stubrunner_core_path}/README.adoc[]
+Stub Runner uses Eclipse Aether mechanism to download the Maven dependencies.
+Check their docs for more information.
-==== Common properties for JUnit and Spring
+Since the StubRunnerRule implements the StubFinder it allows you to find the started stubs:
+
+
+/*
+ * Copyright 2013-2017 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 org.springframework.cloud.contract.stubrunner;
+
+import java.net.URL;
+import java.util.Collection;
+import java.util.Map;
+
+import org.springframework.cloud.contract.spec.Contract;
+
+public interface StubFinder extends StubTrigger {
+ /**
+ * For the given groupId and artifactId tries to find the matching
+ * URL of the running stub.
+ *
+ * @param groupId - might be null. In that case a search only via artifactId takes place
+ * @return URL of a running stub or throws exception if not found
+ */
+ URL findStubUrl(String groupId, String artifactId) throws StubNotFoundException;
+
+ /**
+ * For the given Ivy notation {@code [groupId]:artifactId:[version]:[classifier]} tries to
+ * find the matching URL of the running stub. You can also pass only {@code artifactId}.
+ *
+ * @param ivyNotation - Ivy representation of the Maven artifact
+ * @return URL of a running stub or throws exception if not found
+ */
+ URL findStubUrl(String ivyNotation) throws StubNotFoundException;
+
+ /**
+ * Returns all running stubs
+ */
+ RunningStubs findAllRunningStubs();
+
+ /**
+ * Returns the list of Contracts
+ */
+ Map<StubConfiguration, Collection<Contract>> getContracts();
+}
+
+
+
+Example of usage in Spock tests:
+
+
+
+@ClassRule @Shared StubRunnerRule rule = new StubRunnerRule()
+ .repoRoot(StubRunnerRuleSpec.getResource("/m2repo/repository").toURI().toString())
+ .downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")
+ .downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")
+
+def 'should start WireMock servers'() {
+ expect: 'WireMocks are running'
+ rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null
+ rule.findStubUrl('loanIssuance') != null
+ rule.findStubUrl('loanIssuance') == rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance')
+ rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null
+ and:
+ rule.findAllRunningStubs().isPresent('loanIssuance')
+ rule.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs', 'fraudDetectionServer')
+ rule.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer')
+ and: 'Stubs were registered'
+ "${rule.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
+ "${rule.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
+}
+
+
+
+Example of usage in JUnit tests:
+
+
+
+@Test
+public void should_start_wiremock_servers() throws Exception {
+ // expect: 'WireMocks are running'
+ then(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")).isNotNull();
+ then(rule.findStubUrl("loanIssuance")).isNotNull();
+ then(rule.findStubUrl("loanIssuance")).isEqualTo(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs", "loanIssuance"));
+ then(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")).isNotNull();
+ // and:
+ then(rule.findAllRunningStubs().isPresent("loanIssuance")).isTrue();
+ then(rule.findAllRunningStubs().isPresent("org.springframework.cloud.contract.verifier.stubs", "fraudDetectionServer")).isTrue();
+ then(rule.findAllRunningStubs().isPresent("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")).isTrue();
+ // and: 'Stubs were registered'
+ then(httpGet(rule.findStubUrl("loanIssuance").toString() + "/name")).isEqualTo("loanIssuance");
+ then(httpGet(rule.findStubUrl("fraudDetectionServer").toString() + "/name")).isEqualTo("fraudDetectionServer");
+}
+
+
+
+Check the Common properties for JUnit and Spring for more information on how to apply global configuration of Stub Runner.
+
+
+Maven settings
+
+The stub downloader honors Maven settings for a different local repository folder.
+Authentication details for repositories and profiles are currently not taken into account, so you need to specify it using the properties mentioned above.
+
+
+
+Providing fixed ports
+
+You can also run your stubs on fixed ports. You can do it in two different ways. One is to pass it in the properties, and the other via fluent API of
+JUnit rule.
+
+
+
+Fluent API
+
+When using the StubRunnerRule you can add a stub to download and then pass the port for the last downloaded stub.
+
+
+
+@ClassRule public static StubRunnerRule rule = new StubRunnerRule()
+ .repoRoot(repoRoot())
+ .downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")
+ .withPort(12345)
+ .downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer:12346");
+
+
+
+You can see that for this example the following test is valid:
+
+
+
+then(rule.findStubUrl("loanIssuance")).isEqualTo(URI.create("http://localhost:12345").toURL());
+then(rule.findStubUrl("fraudDetectionServer")).isEqualTo(URI.create("http://localhost:12346").toURL());
+
+
+
+
+Stub Runner with Spring
+
+Sets up Spring configuration of the Stub Runner project.
+
+
+By providing a list of stubs inside your configuration file the Stub Runner automatically downloads
+and registers in WireMock the selected stubs.
+
+
+If you want to find the URL of your stubbed dependency you can autowire the StubFinder interface and use
+its methods as presented below:
+
+
+
+@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
+@SpringBootTest(properties = [" stubrunner.cloud.enabled=false",
+ "stubrunner.camel.enabled=false",
+ 'foo=${stubrunner.runningstubs.fraudDetectionServer.port}'])
+@AutoConfigureStubRunner
+@DirtiesContext
+@ActiveProfiles("test")
+class StubRunnerConfigurationSpec extends Specification {
+
+ @Autowired StubFinder stubFinder
+ @Autowired Environment environment
+ @Value('${foo}') Integer foo
+
+ @BeforeClass
+ @AfterClass
+ void setupProps() {
+ System.clearProperty("stubrunner.repository.root")
+ System.clearProperty("stubrunner.classifier")
+ }
+
+ def 'should start WireMock servers'() {
+ expect: 'WireMocks are running'
+ stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null
+ stubFinder.findStubUrl('loanIssuance') != null
+ stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance')
+ stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance')
+ stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs')
+ stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null
+ and:
+ stubFinder.findAllRunningStubs().isPresent('loanIssuance')
+ stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs', 'fraudDetectionServer')
+ stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer')
+ and: 'Stubs were registered'
+ "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
+ "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
+ }
+
+ def 'should throw an exception when stub is not found'() {
+ when:
+ stubFinder.findStubUrl('nonExistingService')
+ then:
+ thrown(StubNotFoundException)
+ when:
+ stubFinder.findStubUrl('nonExistingGroupId', 'nonExistingArtifactId')
+ then:
+ thrown(StubNotFoundException)
+ }
+
+ def 'should register started servers as environment variables'() {
+ expect:
+ environment.getProperty("stubrunner.runningstubs.loanIssuance.port") != null
+ stubFinder.findAllRunningStubs().getPort("loanIssuance") == (environment.getProperty("stubrunner.runningstubs.loanIssuance.port") as Integer)
+ and:
+ environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") != null
+ stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") == (environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") as Integer)
+ }
+
+ def 'should be able to interpolate a running stub in the passed test property'() {
+ given:
+ int fraudPort = stubFinder.findAllRunningStubs().getPort("fraudDetectionServer")
+ expect:
+ fraudPort > 0
+ environment.getProperty("foo", Integer) == fraudPort
+ foo == fraudPort
+ }
+
+ @Configuration
+ @EnableAutoConfiguration
+ static class Config {}
+}
+
+
+
+for the following configuration file:
+
+
+
+stubrunner:
+ repositoryRoot: classpath:m2repo/repository/
+ ids:
+ - org.springframework.cloud.contract.verifier.stubs:loanIssuance
+ - org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer
+ - org.springframework.cloud.contract.verifier.stubs:bootService
+
+
+
+Instead of using the properties you can also use the properties inside the @AutoConfigureStubRunner.
+Below you can find an example of achieving the same result by setting values on the annotation.
+
+
+
+@AutoConfigureStubRunner(
+ ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance",
+ "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer",
+ "org.springframework.cloud.contract.verifier.stubs:bootService"],
+ repositoryRoot = "classpath:m2repo/repository/")
+
+
+
+Stub Runner Spring registers environment variables in the following manner
+for every registered WireMock server. Example for Stub Runner ids
+ com.example:foo, com.example:bar.
+
+
+
+-
+
stubrunner.runningstubs.foo.port
+
+-
+
stubrunner.runningstubs.bar.port
+
+
+
+
+Which you can reference in your code.
+
+
+
+
+Stub Runner Spring Cloud
+
+Stub Runner can integrate with Spring Cloud.
+
+
+For real life examples you can check the
+
+
+
+-
+
+
+-
+
+
+
+
+
+Stubbing Service Discovery
+
+The most important feature of Stub Runner Spring Cloud is the fact that it’s stubbing
+
+
+
+-
+
DiscoveryClient
+
+-
+
Ribbon ServerList
+
+
+
+
+that means that regardless of the fact whether you’re using Zookeeper, Consul, Eureka or anything else, you don’t need that in your tests.
+We’re starting WireMock instances of your dependencies and we’re telling your application whenever you’re using Feign, load balanced RestTemplate
+or DiscoveryClient directly, to call those stubbed servers instead of calling the real Service Discovery tool.
+
+
+For example this test will pass
+
+
+
+def 'should make service discovery work'() {
+ expect: 'WireMocks are running'
+ "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
+ "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
+ and: 'Stubs can be reached via load service discovery'
+ restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance'
+ restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer'
+}
+
+
+
+for the following configuration file
+
+
+
+spring.cloud:
+ zookeeper.enabled: false
+ consul.enabled: false
+eureka.client.enabled: false
+stubrunner:
+ camel.enabled: false
+ idsToServiceIds:
+ ivyNotation: someValueInsideYourCode
+ fraudDetectionServer: someNameThatShouldMapFraudDetectionServer
+
+
+
+Test profiles and service discovery
+
+In your integration tests you typically don’t want to call neither a discovery service (e.g. Eureka)
+or Config Server. That’s why you create an additional test configuration in which you want to disable
+these features.
+
+
+Due to certain limitations of spring-cloud-commons to achieve this you have disable these properties
+via a static block like presented below (example for Eureka)
+
+
+
+ //Hack to work around https://github.com/spring-cloud/spring-cloud-commons/issues/156
+ static {
+ System.setProperty("eureka.client.enabled", "false");
+ System.setProperty("spring.cloud.config.failFast", "false");
+ }
+
+
+
+
+
+Additional Configuration
+
+You can match the artifactId of the stub with the name of your app by using the stubrunner.idsToServiceIds: map.
+You can disable Stub Runner Ribbon support by providing: stubrunner.cloud.ribbon.enabled equal to false
+You can disable Stub Runner support by providing: stubrunner.cloud.enabled equal to false
+
+
+
+
+
+Tip
+
+
+By default all service discovery will be stubbed. That means that regardless of the fact if you have
+an existing DiscoveryClient its results will be ignored. However, if you want to reuse it, just set
+ stubrunner.cloud.delegate.enabled to true and then your existing DiscoveryClient results will be
+ merged with the stubbed ones.
+
+
+
+
+
+
+
+Stub Runner Boot Application
+
+Spring Cloud Contract Verifier Stub Runner Boot is a Spring Boot application that exposes REST endpoints to
+trigger the messaging labels and to access started WireMock servers.
+
+
+One of the use-cases is to run some smoke (end to end) tests on a deployed application. You can read
+ more about this in the "Microservice Deployment" article at Too Much Coding blog.
+
+
+How to use it?
+
+Just add the
+
+
+
+compile "org.springframework.cloud:spring-cloud-starter-stub-runner"
+
+
+
+Annotate a class with @EnableStubRunnerServer, build a fat-jar and you’re ready to go!
+
+
+For the properties check the Stub Runner Spring section.
+
+
+
+Endpoints
+
+HTTP
+
+
+-
+
GET /stubs - returns a list of all running stubs in ivy:integer notation
+
+-
+
GET /stubs/{ivy} - returns a port for the given ivy notation (when calling the endpoint ivy can also be artifactId only)
+
+
+
+
+
+Messaging
+
+For Messaging
+
+
+
+-
+
GET /triggers - returns a list of all running labels in ivy : [ label1, label2 …] notation
+
+-
+
POST /triggers/{label} - executes a trigger with label
+
+-
+
POST /triggers/{ivy}/{label} - executes a trigger with label for the given ivy notation (when calling the endpoint ivy can also be artifactId only)
+
+
+
+
+
+
+Example
+
+
+@ContextConfiguration(classes = StubRunnerBoot, loader = SpringBootContextLoader)
+@SpringBootTest(properties = "spring.cloud.zookeeper.enabled=false")
+@ActiveProfiles("test")
+class StubRunnerBootSpec extends Specification {
+
+ @Autowired StubRunning stubRunning
+
+ def setup() {
+ RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning),
+ new TriggerController(stubRunning))
+ }
+
+ def 'should return a list of running stub servers in "full ivy:port" notation'() {
+ when:
+ String response = RestAssuredMockMvc.get('/stubs').body.asString()
+ then:
+ def root = new JsonSlurper().parseText(response)
+ root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs' instanceof Integer
+ }
+
+ def 'should return a port on which a [#stubId] stub is running'() {
+ when:
+ def response = RestAssuredMockMvc.get("/stubs/${stubId}")
+ then:
+ response.statusCode == 200
+ response.body.as(Integer) > 0
+ where:
+ stubId << ['org.springframework.cloud.contract.verifier.stubs:bootService:+:stubs',
+ 'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs',
+ 'org.springframework.cloud.contract.verifier.stubs:bootService:+',
+ 'org.springframework.cloud.contract.verifier.stubs:bootService',
+ 'bootService']
+ }
+
+ def 'should return 404 when missing stub was called'() {
+ when:
+ def response = RestAssuredMockMvc.get("/stubs/a:b:c:d")
+ then:
+ response.statusCode == 404
+ }
+
+ def 'should return a list of messaging labels that can be triggered when version and classifier are passed'() {
+ when:
+ String response = RestAssuredMockMvc.get('/triggers').body.asString()
+ then:
+ def root = new JsonSlurper().parseText(response)
+ root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs'?.containsAll(["delete_book","return_book_1","return_book_2"])
+ }
+
+ def 'should trigger a messaging label'() {
+ given:
+ StubRunning stubRunning = Mock()
+ RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
+ when:
+ def response = RestAssuredMockMvc.post("/triggers/delete_book")
+ then:
+ response.statusCode == 200
+ and:
+ 1 * stubRunning.trigger('delete_book')
+ }
+
+ def 'should trigger a messaging label for a stub with [#stubId] ivy notation'() {
+ given:
+ StubRunning stubRunning = Mock()
+ RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
+ when:
+ def response = RestAssuredMockMvc.post("/triggers/$stubId/delete_book")
+ then:
+ response.statusCode == 200
+ and:
+ 1 * stubRunning.trigger(stubId, 'delete_book')
+ where:
+ stubId << ['org.springframework.cloud.contract.verifier.stubs:bootService:stubs', 'org.springframework.cloud.contract.verifier.stubs:bootService', 'bootService']
+ }
+
+ def 'should throw exception when trigger is missing'() {
+ when:
+ RestAssuredMockMvc.post("/triggers/missing_label")
+ then:
+ Exception e = thrown(Exception)
+ e.message.contains("Exception occurred while trying to return [missing_label] label.")
+ e.message.contains("Available labels are")
+ e.message.contains("org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs=[]")
+ e.message.contains("org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs=")
+ }
+
+}
+
+
+
+
+Stub Runner Boot with Service Discovery
+
+One of the possibilities of using Stub Runner Boot is to use it as a feed of stubs for "smoke-tests". What does it mean?
+ Let’s assume that you don’t want to deploy 50 microservice to a test environment in order
+ to check if your application is working fine. You’ve already executed a suite of tests during the build process
+ but you would also like to ensure that the packaging of your application is fine. What you can do
+ is to deploy your application to an environment, start it and run a couple of tests on it to see if
+ it’s working fine. We can call those tests smoke-tests since their idea is to check only a handful
+ of testing scenarios.
+
+
+The problem with this approach is such that if you’re doing microservices most likely you’re
+ using a service discovery tool. Stub Runner Boot allows you to solve this issue by starting the
+ required stubs and register them in a service discovery tool. Let’s take a look at an example of
+ such a setup with Eureka. Let’s assume that Eureka was already running.
+
+
+
+@SpringBootApplication
+@EnableStubRunnerServer
+@EnableEurekaClient
+@AutoConfigureStubRunner
+public class StubRunnerBootEurekaExample {
+
+ public static void main(String[] args) {
+ SpringApplication.run(StubRunnerBootEurekaExample.class, args);
+ }
+
+}
+
+
+
+As you can see we want to start a Stub Runner Boot server @EnableStubRunnerServer, enable Eureka client @EnableEurekaClient
+and we want to have the stub runner feature turned on @AutoConfigureStubRunner.
+
+
+Now let’s assume that we want to start this application so that the stubs get automatically registered.
+ We can do it by running the app java -jar ${SYSTEM_PROPS} stub-runner-boot-eureka-example.jar where
+ ${SYSTEM_PROPS} would contain the following list of properties
+
+
+
+-Dstubrunner.repositoryRoot=http://repo.spring.io/snapshots (1)
+-Dstubrunner.cloud.stubbed.discovery.enabled=false (2)
+-Dstubrunner.ids=org.springframework.cloud.contract.verifier.stubs:loanIssuance,org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer,org.springframework.cloud.contract.verifier.stubs:bootService (3)
+-Dstubrunner.idsToServiceIds.fraudDetectionServer=someNameThatShouldMapFraudDetectionServer (4)
+
+(1) - we tell Stub Runner where all the stubs reside
+(2) - we don't want the default behaviour where the discovery service is stubbed. That's why the stub registration will be picked
+(3) - we provide a list of stubs to download
+(4) - we provide a list of artifactId to serviceId mapping
+
+
+
+That way your deployed application can send requests to started WireMock servers via the service
+discovery. Most likely points 1-3 could be set by default in application.yml cause they are not
+likely to change. That way you can provide only the list of stubs to download whenever you start
+the Stub Runner Boot.
+
+
+
+Common properties for JUnit and Spring
Some of the properties that are repetitive can be set using system properties or configuration properties (for Spring). Here are their names with their default values:
@@ -16092,9 +14606,8 @@ Unresolved directive in rc/main/asciidoc/verifier/stubrunner.adoc - include::{sa
-
-===== Stub runner stubs ids
-
+
+Stub runner stubs ids
You can provide the stubs to download via the stubrunner.ids system property. They follow the following pattern:
@@ -16159,9 +14672,11 @@ segments are padded with trailing 0 or "ga" segments, respectively, until the ki
-
-=== Stub Runner for Messaging
+
+
+
+Stub Runner for Messaging
Stub Runner has the functionality to run the published stubs in memory. It can integrate with the following frameworks out of the box
@@ -16184,15 +14699,70 @@ segments are padded with trailing 0 or "ga" segments, respectively, until the ki
It also provides points of entry to integrate with any other solution on the market.
-
-==== Stub triggering
-
+
+Stub triggering
To trigger a message it’s enough to use the StubTrigger interface:
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner_msg.adoc - include::{stubrunner_core_path}/src/main/java/org/springframework/cloud/contract/stubrunner/StubTrigger.java[]
+/*
+ * Copyright 2013-2017 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 org.springframework.cloud.contract.stubrunner;
+
+import java.util.Collection;
+import java.util.Map;
+
+public interface StubTrigger {
+
+ /**
+ * Triggers an event by a given label for a given {@code groupid:artifactid} notation. You can use only {@code artifactId} too.
+ *
+ * Feature related to messaging.
+ *
+ * @return true - if managed to run a trigger
+ */
+ boolean trigger(String ivyNotation, String labelName);
+
+ /**
+ * Triggers an event by a given label.
+ *
+ * Feature related to messaging.
+ *
+ * @return true - if managed to run a trigger
+ */
+ boolean trigger(String labelName);
+
+ /**
+ * Triggers all possible events.
+ *
+ * Feature related to messaging.
+ *
+ * @return true - if managed to run a trigger
+ */
+ boolean trigger();
+
+ /**
+ * Returns a mapping of ivy notation of a dependency to all the labels it has.
+ *
+ * Feature related to messaging.
+ */
+ Map<String, Collection<String>> labels();
+}
@@ -16201,53 +14771,822 @@ segments are padded with trailing 0 or "ga" segments, respectively, until the ki
StubTrigger gives you the following options to trigger a message:
+
+Trigger by label
+
+
+stubFinder.trigger('return_book_1')
+
+
+
+Trigger by group and artifact ids
+
+
+stubFinder.trigger('org.springframework.cloud.contract.verifier.stubs:camelService', 'return_book_1')
+
+
+
+
+Trigger by artifact ids
+
+
+stubFinder.trigger('camelService', 'return_book_1')
+
+
+
+
+
+Trigger all messages
+
+
+stubFinder.trigger()
+
+
+
+
+
+
+Stub Runner Camel
-===== Trigger by label
+Spring Cloud Contract Verifier Stub Runner’s messaging module gives you an easy way to integrate with Apache Camel.
+For the provided artifacts it will automatically download the stubs and register the required
+routes.
+
+
+Adding it to the project
+
+It’s enough to have both Apache Camel and Spring Cloud Contract Stub Runner on classpath.
+Remember to annotate your test class with @AutoConfigureMessageVerifier.
+
+
+
+Examples
+
+Stubs structure
+
+Let us assume that we have the following Maven repository with a deployed stubs for the
+camelService application.
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner_msg.adoc - include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_trigger,indent=0]
+└── .m2
+ └── repository
+ └── io
+ └── codearte
+ └── accurest
+ └── stubs
+ └── camelService
+ ├── 0.0.1-SNAPSHOT
+ │  ├── camelService-0.0.1-SNAPSHOT.pom
+ │  ├── camelService-0.0.1-SNAPSHOT-stubs.jar
+ │  └── maven-metadata-local.xml
+ └── maven-metadata-local.xml
-====== Trigger by group and artifact ids
+And the stubs contain the following structure:
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner_msg.adoc - include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=trigger_group_artifact,indent=0]
+├── META-INF
+│  └── MANIFEST.MF
+└── repository
+ ├── accurest
+ │  ├── bookDeleted.groovy
+ │  ├── bookReturned1.groovy
+ │  └── bookReturned2.groovy
+ └── mappings
-====== Trigger by artifact ids
+Let’s consider the following contracts (let' number it with 1):
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner_msg.adoc - include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=trigger_artifact,indent=0]
+Contract.make {
+ label 'return_book_1'
+ input {
+ triggeredBy('bookReturnedTriggered()')
+ }
+ outputMessage {
+ sentTo('jms:output')
+ body('''{ "bookName" : "foo" }''')
+ headers {
+ header('BOOK-NAME', 'foo')
+ }
+ }
+}
-===== Trigger all messages
+and number 2
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner_msg.adoc - include::{tests_path}/spring-cloud-contract-stub-runner-camel/src/test/groovy/org/springframework/cloud/contract/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=trigger_all,indent=0]
+Contract.make {
+ label 'return_book_2'
+ input {
+ messageFrom('jms:input')
+ messageBody([
+ bookName: 'foo'
+ ])
+ messageHeaders {
+ header('sample', 'header')
+ }
+ }
+ outputMessage {
+ sentTo('jms:output')
+ body([
+ bookName: 'foo'
+ ])
+ headers {
+ header('BOOK-NAME', 'foo')
+ }
+ }
+}
+
+
+
+
+Scenario 1 (no input message)
+
+So as to trigger a message via the return_book_1 label we’ll use the StubTigger interface as follows
+
+
+
+stubFinder.trigger('return_book_1')
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner_msg.adoc - include::{tests_path}/spring-cloud-contract-stub-runner-camel/README.adoc[]
+Next we’ll want to listen to the output of the message sent to jms:output
+
+
+
+Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
+
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner_msg.adoc - include::{tests_path}/spring-cloud-contract-stub-runner-integration/README.adoc[]
+And the received message would pass the following assertions
+
+
+
+receivedMessage != null
+assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
+receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
+
+
+
+
+Scenario 2 (output triggered by input)
+
+Since the route is set for you it’s enough to just send a message to the jms:output destination.
+
+
+
+camelContext.createProducerTemplate().sendBodyAndHeaders('jms:input', new BookReturned('foo'), [sample: 'header'])
+
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner_msg.adoc - include::{tests_path}/spring-cloud-contract-stub-runner-stream/README.adoc[]
+Next we’ll want to listen to the output of the message sent to jms:output
+
+
+
+Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
+
-Unresolved directive in rc/main/asciidoc/verifier/stubrunner_msg.adoc - include::{tests_path}/spring-cloud-contract-stub-runner-amqp/README.adoc[]
+And the received message would pass the following assertions
+
+
+
+receivedMessage != null
+assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
+receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
+
+
+
+
+Scenario 3 (input with no output)
+
+Since the route is set for you it’s enough to just send a message to the jms:output destination.
+
+
+
+camelContext.createProducerTemplate().sendBodyAndHeaders('jms:delete', new BookReturned('foo'), [sample: 'header'])
+
+
+
+
+
+
+Stub Runner Integration
+
+Spring Cloud Contract Verifier Stub Runner’s messaging module gives you an easy way to integrate with Spring Integration.
+For the provided artifacts it will automatically download the stubs and register the required
+routes.
+
+
+Adding it to the project
+
+It’s enough to have both Apache Camel and Spring Cloud Contract Stub Runner on classpath.
+Remember to annotate your test class with @AutoConfigureMessageVerifier.
+
+
+
+Examples
+
+Stubs structure
+
+Let us assume that we have the following Maven repository with a deployed stubs for the
+integrationService application.
+
+
+
+└── .m2
+ └── repository
+ └── io
+ └── codearte
+ └── accurest
+ └── stubs
+ └── integrationService
+ ├── 0.0.1-SNAPSHOT
+ │  ├── integrationService-0.0.1-SNAPSHOT.pom
+ │  ├── integrationService-0.0.1-SNAPSHOT-stubs.jar
+ │  └── maven-metadata-local.xml
+ └── maven-metadata-local.xml
+
-=== Contract DSL
+And the stubs contain the following structure:
+
+
+├── META-INF
+│  └── MANIFEST.MF
+└── repository
+ ├── accurest
+ │  ├── bookDeleted.groovy
+ │  ├── bookReturned1.groovy
+ │  └── bookReturned2.groovy
+ └── mappings
+
+
+
+Let’s consider the following contracts (let' number it with 1):
+
+
+
+Contract.make {
+ label 'return_book_1'
+ input {
+ triggeredBy('bookReturnedTriggered()')
+ }
+ outputMessage {
+ sentTo('output')
+ body('''{ "bookName" : "foo" }''')
+ headers {
+ header('BOOK-NAME', 'foo')
+ }
+ }
+}
+
+
+
+and number 2
+
+
+
+Contract.make {
+ label 'return_book_2'
+ input {
+ messageFrom('input')
+ messageBody([
+ bookName: 'foo'
+ ])
+ messageHeaders {
+ header('sample', 'header')
+ }
+ }
+ outputMessage {
+ sentTo('output')
+ body([
+ bookName: 'foo'
+ ])
+ headers {
+ header('BOOK-NAME', 'foo')
+ }
+ }
+}
+
+
+
+and the following Spring Integration Route:
+
+
+
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2013-2017 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.
+ -->
+
+<beans:beans xmlns="http://www.springframework.org/schema/integration"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/integration
+ http://www.springframework.org/schema/integration/spring-integration.xsd">
+
+
+ <!-- REQUIRED FOR TESTING -->
+ <bridge input-channel="output"
+ output-channel="outputTest"/>
+
+ <channel id="outputTest">
+ <queue/>
+ </channel>
+
+</beans:beans>
+
+
+
+
+Scenario 1 (no input message)
+
+So as to trigger a message via the return_book_1 label we’ll use the StubTigger interface as follows
+
+
+
+stubFinder.trigger('return_book_1')
+
+
+
+Next we’ll want to listen to the output of the message sent to output
+
+
+
+Message<?> receivedMessage = messaging.receive('outputTest')
+
+
+
+And the received message would pass the following assertions
+
+
+
+receivedMessage != null
+assertJsons(receivedMessage.payload)
+receivedMessage.headers.get('BOOK-NAME') == 'foo'
+
+
+
+
+Scenario 2 (output triggered by input)
+
+Since the route is set for you it’s enough to just send a message to the output destination.
+
+
+
+messaging.send(new BookReturned('foo'), [sample: 'header'], 'input')
+
+
+
+Next we’ll want to listen to the output of the message sent to output
+
+
+
+Message<?> receivedMessage = messaging.receive('outputTest')
+
+
+
+And the received message would pass the following assertions
+
+
+
+receivedMessage != null
+assertJsons(receivedMessage.payload)
+receivedMessage.headers.get('BOOK-NAME') == 'foo'
+
+
+
+
+Scenario 3 (input with no output)
+
+Since the route is set for you it’s enough to just send a message to the input destination.
+
+
+
+messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete')
+
+
+
+
+
+
+Stub Runner Stream
+
+Spring Cloud Contract Verifier Stub Runner’s messaging module gives you an easy way to integrate with Spring Stream.
+For the provided artifacts it will automatically download the stubs and register the required
+routes.
+
+
+
+
+
+Warning
+
+
+In Stub Runner’s integration with Stream the messageFrom or sentTo Strings are resolved
+first as a destination of a channel, and then if there is no such destination it’s resolved as a
+channel name.
+
+
+
+
+
+Adding it to the project
+
+It’s enough to have both Apache Camel and Spring Cloud Contract Stub Runner on classpath.
+Remember to annotate your test class with @AutoConfigureMessageVerifier.
+
+
+
+Examples
+
+Stubs structure
+
+Let us assume that we have the following Maven repository with a deployed stubs for the
+streamService application.
+
+
+
+└── .m2
+ └── repository
+ └── io
+ └── codearte
+ └── accurest
+ └── stubs
+ └── streamService
+ ├── 0.0.1-SNAPSHOT
+ │  ├── streamService-0.0.1-SNAPSHOT.pom
+ │  ├── streamService-0.0.1-SNAPSHOT-stubs.jar
+ │  └── maven-metadata-local.xml
+ └── maven-metadata-local.xml
+
+
+
+And the stubs contain the following structure:
+
+
+
+├── META-INF
+│  └── MANIFEST.MF
+└── repository
+ ├── accurest
+ │  ├── bookDeleted.groovy
+ │  ├── bookReturned1.groovy
+ │  └── bookReturned2.groovy
+ └── mappings
+
+
+
+Let’s consider the following contracts (let' number it with 1):
+
+
+
+Contract.make {
+ label 'return_book_1'
+ input { triggeredBy('bookReturnedTriggered()') }
+ outputMessage {
+ sentTo('returnBook')
+ body('''{ "bookName" : "foo" }''')
+ headers { header('BOOK-NAME', 'foo') }
+ }
+}
+
+
+
+and number 2
+
+
+
+Contract.make {
+ label 'return_book_2'
+ input {
+ messageFrom('bookStorage')
+ messageBody([
+ bookName: 'foo'
+ ])
+ messageHeaders { header('sample', 'header') }
+ }
+ outputMessage {
+ sentTo('returnBook')
+ body([
+ bookName: 'foo'
+ ])
+ headers { header('BOOK-NAME', 'foo') }
+ }
+}
+
+
+
+and the following Spring configuration:
+
+
+
+stubrunner.repositoryRoot: classpath:m2repo/repository/
+stubrunner.ids: org.springframework.cloud.contract.verifier.stubs:streamService:0.0.1-SNAPSHOT:stubs
+
+spring:
+ cloud:
+ stream:
+ bindings:
+ output:
+ destination: returnBook
+ input:
+ destination: bookStorage
+
+server:
+ port: 0
+
+
+
+
+Scenario 1 (no input message)
+
+So as to trigger a message via the return_book_1 label we’ll use the StubTrigger interface as follows
+
+
+
+stubFinder.trigger('return_book_1')
+
+
+
+Next we’ll want to listen to the output of the message sent to a channel whose destination is returnBook
+
+
+
+Message<?> receivedMessage = messaging.receive('returnBook')
+
+
+
+And the received message would pass the following assertions
+
+
+
+receivedMessage != null
+assertJsons(receivedMessage.payload)
+receivedMessage.headers.get('BOOK-NAME') == 'foo'
+
+
+
+
+Scenario 2 (output triggered by input)
+
+Since the route is set for you it’s enough to just send a message to the bookStorage destination.
+
+
+
+messaging.send(new BookReturned('foo'), [sample: 'header'], 'bookStorage')
+
+
+
+Next we’ll want to listen to the output of the message sent to returnBook
+
+
+
+Message<?> receivedMessage = messaging.receive('returnBook')
+
+
+
+And the received message would pass the following assertions
+
+
+
+receivedMessage != null
+assertJsons(receivedMessage.payload)
+receivedMessage.headers.get('BOOK-NAME') == 'foo'
+
+
+
+
+Scenario 3 (input with no output)
+
+Since the route is set for you it’s enough to just send a message to the output destination.
+
+
+
+messaging.send(new BookReturned('foo'), [sample: 'header'], 'delete')
+
+
+
+
+
+
+Stub Runner Spring AMQP
+
+Spring Cloud Contract Verifier Stub Runner’s messaging module provides an easy way to integrate with Spring AMQP’s Rabbit Template.
+For the provided artifacts it will automatically download the stubs and register the required
+routes.
+
+
+The integration tries to work standalone, that is without interaction with a running RabbitMQ message broker.
+It expects a RabbitTemplate on the application context and uses it as a spring boot test @SpyBean.
+Thus it can use the mockito spy functionality to verify and introspect messages sent by the application.
+
+
+On the message consumer side, it considers all @RabbitListener annotated endpoints as well as all `SimpleMessageListenerContainer`s on the application context.
+
+
+As messages are usually sent to exchanges in AMQP the message contract contains the exchange name as the destination.
+Message listeners on the other side are bound to queues. Bindings connect an exchange to a queue.
+If message contracts are triggered the Spring AMQP stub runner integration will look for bindings on the application context that match this exchange.
+Then it collects the queues from the Spring exchanges and tries to find messages listeners bound to these queues.
+The message is triggered to all matching message listeners.
+
+
+Adding it to the project
+
+It’s enough to have both Spring AMQP and Spring Cloud Contract Stub Runner on the classpath and set the property stubrunner.amqp.enabled=true.
+Remember to annotate your test class with @AutoConfigureMessageVerifier.
+
+
+
+Examples
+
+Stubs structure
+
+Let us assume that we have the following Maven repository with a deployed stubs for the
+spring-cloud-contract-amqp-test application.
+
+
+
+└── .m2
+ └── repository
+ └── com
+ └── example
+ └── spring-cloud-contract-amqp-test
+ ├── 0.4.0-SNAPSHOT
+ │  ├── spring-cloud-contract-amqp-test-0.4.0-SNAPSHOT.pom
+ │  ├── spring-cloud-contract-amqp-test-0.4.0-SNAPSHOT-stubs.jar
+ │  └── maven-metadata-local.xml
+ └── maven-metadata-local.xml
+
+
+
+And the stubs contain the following structure:
+
+
+
+├── META-INF
+│  └── MANIFEST.MF
+└── contracts
+ └── shouldProduceValidPersonData.groovy
+
+
+
+Let’s consider the following contract:
+
+
+
+Contract.make {
+ // Human readable description
+ description 'Should produce valid person data'
+ // Label by means of which the output message can be triggered
+ label 'contract-test.person.created.event'
+ // input to the contract
+ input {
+ // the contract will be triggered by a method
+ triggeredBy('createPerson()')
+ }
+ // output message of the contract
+ outputMessage {
+ // destination to which the output message will be sent
+ sentTo 'contract-test.exchange'
+ headers {
+ header('contentType': 'application/json')
+ header('__TypeId__': 'org.springframework.cloud.contract.stubrunner.messaging.amqp.Person')
+ }
+ // the body of the output message
+ body ([
+ id: $(consumer(9), producer(regex("[0-9]+"))),
+ name: "me"
+ ])
+ }
+}
+
+
+
+and the following Spring configuration:
+
+
+
+stubrunner:
+ repositoryRoot: classpath:m2repo/repository/
+ ids: org.springframework.cloud.contract.verifier.stubs.amqp:spring-cloud-contract-amqp-test:0.4.0-SNAPSHOT:stubs
+ amqp:
+ enabled: true
+server:
+ port: 0
+
+
+
+
+Triggering the message
+
+So to trigger a message using the contract above we’ll use the StubTrigger interface as follows.
+
+
+
+stubTrigger.trigger("contract-test.person.created.event")
+
+
+
+The message has the destination contract-test.exchange so the Spring AMQP stub runner integration looks for bindings related to this exchange.
+
+
+
+@Bean
+public Binding binding() {
+ return BindingBuilder.bind(new Queue("test.queue")).to(new DirectExchange("contract-test.exchange")).with("#");
+}
+
+
+
+The binding definition binds the queue test.queue.
+So the following listener definition is a match and is invoked with the contract message.
+
+
+
+@Bean
+public SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory,
+ MessageListenerAdapter listenerAdapter) {
+ SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
+ container.setConnectionFactory(connectionFactory);
+ container.setQueueNames("test.queue");
+ container.setMessageListener(listenerAdapter);
+
+ return container;
+}
+
+
+
+Also, the following annotated listener represents a match and would be invoked.
+
+
+
+@RabbitListener(bindings = @QueueBinding(
+ value = @Queue(value = "test.queue"),
+ exchange = @Exchange(value = "contract-test.exchange", ignoreDeclarationExceptions = "true")))
+public void handlePerson(Person person) {
+ this.person = person;
+}
+
+
+
+
+
+
+Note
+
+
+The message is directly handed over to the onMessage method of the MessageListener associated with the matching SimpleMessageListenerContainer.
+
+
+
+
+
+
+Spring AMQP Test Configuration
+
+In order to avoid that Spring AMQP is trying to connect to a running broker during our tests we configure a mock ConnectionFactory.
+
+
+To disable the mocked ConnectionFactory set the property stubrunner.amqp.mockConnection=false
+
+
+
+stubrunner:
+ amqp:
+ mockConnection: false
+
+
+
+
+
+
+Contract DSL
@@ -16276,7 +15615,48 @@ a tiny subset of it (namely literals, method calls and closures). What’s m
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy[tags=dsl_example,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ method 'PUT'
+ url '/api/12'
+ headers {
+ header 'Content-Type': 'application/vnd.org.springframework.cloud.contract.verifier.twitter-places-analyzer.v1+json'
+ }
+ body '''\
+ [{
+ "created_at": "Sat Jul 26 09:38:57 +0000 2014",
+ "id": 492967299297845248,
+ "id_str": "492967299297845248",
+ "text": "Gonna see you at Warsaw",
+ "place":
+ {
+ "attributes":{},
+ "bounding_box":
+ {
+ "coordinates":
+ [[
+ [-77.119759,38.791645],
+ [-76.909393,38.791645],
+ [-76.909393,38.995548],
+ [-77.119759,38.995548]
+ ]],
+ "type":"Polygon"
+ },
+ "country":"United States",
+ "country_code":"US",
+ "full_name":"Washington, DC",
+ "id":"01fbe706f872cb32",
+ "name":"Washington",
+ "place_type":"city",
+ "url": "http://api.twitter.com/1/geo/id/01fbe706f872cb32.json"
+ }
+ }]
+ '''
+ }
+ response {
+ status 200
+ }
+}
@@ -16289,9 +15669,8 @@ a tiny subset of it (namely literals, method calls and closures). What’s m
-
-==== Limitations
-
+
+Limitations
@@ -16344,52 +15723,94 @@ Groovy Map notation.
-
-==== Common Top-Level elements
-
-
-===== Description
+
+Common Top-Level elements
+
+Description
You can add a description to your contract that is nothing else but an arbitrary text. Example:
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{contract_spec_path}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy[tags=description,indent=0]
+ org.springframework.cloud.contract.spec.Contract.make {
+ description('''
+given:
+ An input
+when:
+ Sth happens
+then:
+ Output
+''')
+ }
-
-===== Ignoring contracts
+
+Ignoring contracts
If you want to ignore a contract you can either set a value of ignored contracts in the plugin configuration
or just set the ignored property on the contract itself:
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{contract_spec_path}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy[tags=ignored,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ ignored()
+}
-
-==== HTTP Top-Level Elements
+
+
+HTTP Top-Level Elements
Following methods can be called in the top-level closure of a contract definition. Request and response are mandatory, priority is optional.
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=http_dsl,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ // Definition of HTTP request part of the contract
+ // (this can be a valid request or invalid depending
+ // on type of contract being specified).
+ request {
+ //...
+ }
+
+ // Definition of HTTP response part of the contract
+ // (a service implementing this contract should respond
+ // with following response after receiving request
+ // specified in "request" part above).
+ response {
+ //...
+ }
+
+ // Contract priority, which can be used for overriding
+ // contracts (1 is highest). Priority is optional.
+ priority 1
+}
-
-==== Request
+
+Request
HTTP protocol requires only method and address to be specified in a request. The same information is mandatory in request definition of the Contract.
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=request,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ // HTTP request method (GET/POST/PUT/DELETE).
+ method 'GET'
+
+ // Path component of request URL is specified as follows.
+ urlPath('/users')
+ }
+
+ response {
+ //...
+ }
+}
@@ -16397,7 +15818,18 @@ or just set the ignored property on the contract itself:
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=url,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ method 'GET'
+
+ // Specifying `url` and `urlPath` in one contract is illegal.
+ url('http://localhost:8888/users')
+ }
+
+ response {
+ //...
+ }
+}
@@ -16405,7 +15837,47 @@ or just set the ignored property on the contract itself:
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=urlpath,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ //...
+
+ urlPath('/users') {
+
+ // Each parameter is specified in form
+ // `'paramName' : paramValue` where parameter value
+ // may be a simple literal or one of matcher functions,
+ // all of which are used in this example.
+ queryParameters {
+
+ // If a simple literal is used as value
+ // default matcher function is used (equalTo)
+ parameter 'limit': 100
+
+ // `equalTo` function simply compares passed value
+ // using identity operator (==).
+ parameter 'filter': equalTo("email")
+
+ // `containing` function matches strings
+ // that contains passed substring.
+ parameter 'gender': value(consumer(containing("[mf]")), producer('mf'))
+
+ // `matching` function tests parameter
+ // against passed regular expression.
+ parameter 'offset': value(consumer(matching("[0-9]+")), producer(123))
+
+ // `notMatching` functions tests if parameter
+ // does not match passed regular expression.
+ parameter 'loginStartsWith': value(consumer(notMatching(".{0,2}")), producer(3))
+ }
+ }
+
+ //...
+ }
+
+ response {
+ //...
+ }
+}
@@ -16413,7 +15885,24 @@ or just set the ignored property on the contract itself:
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=headers,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ //...
+
+ // Each header is added in form `'Header-Name' : 'Header-Value'`.
+ // there are also some helper methods
+ headers {
+ header 'key': 'value'
+ contentType(applicationJson())
+ }
+
+ //...
+ }
+
+ response {
+ //...
+ }
+}
@@ -16421,35 +15910,55 @@ or just set the ignored property on the contract itself:
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=body,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ //...
+
+ // Currently only JSON format of request body is supported.
+ // Format will be determined from a header or body's content.
+ body '''{ "login" : "john", "name": "John The Contract" }'''
+ }
+
+ response {
+ //...
+ }
+}
-
-==== Response
+
+Response
Minimal response must contain HTTP status code.
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=response,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ //...
+ }
+ response {
+ // Status code sent by the server
+ // in response to request specified above.
+ status 200
+ }
+}
Besides status response may contain headers and body, which are specified the same way as in the request (see previous paragraph).
-
-==== Dynamic properties
+
+Dynamic properties
The contract can contain some dynamic properties - timestamps / ids etc. You don’t want to enforce the consumers to stub their
clocks to always return the same value of time so that it gets matched by the stub. That’s why we allow you to provide the dynamic
parts in your contracts in two ways. One is to pass them directly in the
body and one to set them in a separate section called testMatchers and stubMatchers.
-
-===== Dynamic properties inside the body
-
+
+Dynamic properties inside the body
You can set the properties inside the body either via the value method
@@ -16476,9 +15985,8 @@ $(client(...), server(...))
All of the aforementioned approaches are equal. That means that stub and client methods are aliases over the consumer
method. Let’s take a closer look at what we can do with those values in the subsequent sections.
-
-====== Regular expressions
-
+
+Regular expressions
You can use regular expressions to write your requests in Contract DSL. It is particularly useful when you want to indicate that a given response
should be provided for requests that follow a given pattern. Also, you can use it when you need to use patterns and not exact values both
@@ -16489,7 +15997,30 @@ for your test and your server side tests.
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=regex,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ method('GET')
+ url $(consumer(~/\/[0-9]{2}/), producer('/12'))
+ }
+ response {
+ status 200
+ body(
+ id: $(anyNumber()),
+ surname: $(
+ consumer('Kowalsky'),
+ producer(regex('[a-zA-Z]+'))
+ ),
+ name: 'Jan',
+ created: $(consumer('2014-02-02 12:23:43'), producer(execute('currentDate(it)'))),
+ correlationId: value(consumer('5d1f9fef-e0dc-4f3d-a7e4-72d2220dd827'),
+ producer(regex('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}'))
+ )
+ )
+ headers {
+ header 'Content-Type': 'text/plain'
+ }
+ }
+}
@@ -16498,7 +16029,27 @@ provide the generated string that matches the provided regular expression. For e
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy[tags=dsl_one_side_data_generation_example,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ method 'PUT'
+ url value(consumer(regex('/foo/[0-9]{5}')))
+ body([
+ requestElement: $(consumer(regex('[0-9]{5}')))
+ ])
+ headers {
+ header('header', $(consumer(regex('application\\/vnd\\.fraud\\.v1\\+json;.*'))))
+ }
+ }
+ response {
+ status 200
+ body([
+ responseElement: $(producer(regex('[0-9]{7}')))
+ ])
+ headers {
+ contentType("application/vnd.fraud.v1+json")
+ }
+ }
+}
@@ -16509,7 +16060,61 @@ provide the generated string that matches the provided regular expression. For e
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{contract_spec_path}/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexPatterns.groovy[tags=regexps,indent=0]
+protected static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/)
+protected static final Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/)
+protected static final Pattern NUMBER = Pattern.compile('-?\\d*(\\.\\d+)?')
+protected static final Pattern IP_ADDRESS = Pattern.compile('([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])')
+protected static final Pattern HOSTNAME_PATTERN = Pattern.compile('((http[s]?|ftp):\\/)\\/?([^:\\/\\s]+)(:[0-9]{1,5})?')
+protected static final Pattern EMAIL = Pattern.compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}');
+protected static final Pattern URL = Pattern.compile('((www\\.|(http|https|ftp|news|file)+\\:\\/\\/)[_.a-z0-9-]+\\.[a-z0-9\\/_:@=.+?,##%&~-]*[^.|\\\'|\\# |!|\\(|?|,| |>|<|;|\\)])')
+protected static final Pattern UUID = Pattern.compile('[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}')
+protected static final Pattern ANY_DATE = Pattern.compile('(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])')
+protected static final Pattern ANY_DATE_TIME = Pattern.compile('([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])')
+protected static final Pattern ANY_TIME = Pattern.compile('(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])')
+
+String onlyAlphaUnicode() {
+ return ONLY_ALPHA_UNICODE.pattern()
+}
+
+String number() {
+ return NUMBER.pattern()
+}
+
+String anyBoolean() {
+ return TRUE_OR_FALSE.pattern()
+}
+
+String ipAddress() {
+ return IP_ADDRESS.pattern()
+}
+
+String hostname() {
+ return HOSTNAME_PATTERN.pattern()
+}
+
+String email() {
+ return EMAIL.pattern()
+}
+
+String url() {
+ return URL.pattern()
+}
+
+String uuid(){
+ return UUID.pattern()
+}
+
+String isoDate() {
+ return ANY_DATE.pattern()
+}
+
+String isoDateTime() {
+ return ANY_DATE_TIME.pattern()
+}
+
+String isoTime() {
+ return ANY_TIME.pattern()
+}
@@ -16517,12 +16122,35 @@ provide the generated string that matches the provided regular expression. For e
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy[tags=contract_with_regex,indent=0]
+Contract dslWithOptionalsInString = Contract.make {
+ priority 1
+ request {
+ method 'POST'
+ url '/users/password'
+ headers {
+ contentType(applicationJson())
+ }
+ body(
+ email: $(consumer(optional(regex(email()))), producer('abc@abc.com')),
+ callback_url: $(consumer(regex(hostname())), producer('http://partners.com'))
+ )
+ }
+ response {
+ status 404
+ headers {
+ contentType(applicationJson())
+ }
+ body(
+ code: value(consumer("123123"), producer(optional("123123"))),
+ message: "User not found by email = [${value(producer(regex(email())), consumer('not.existing@user.com'))}]"
+ )
+ }
+}
-
-====== Passing optional parameters
+
+Passing optional parameters
It is possible to provide optional parameters in your contract. It’s only possible to have optional parameter for the:
@@ -16541,7 +16169,29 @@ provide the generated string that matches the provided regular expression. For e
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=optionals,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ priority 1
+ request {
+ method 'POST'
+ url '/users/password'
+ headers {
+ contentType(applicationJson())
+ }
+ body(
+ email: $(consumer(optional(regex(email()))), producer('abc@abc.com')),
+ callback_url: $(consumer(regex(hostname())), producer('http://partners.com'))
+ )
+ }
+ response {
+ status 404
+ headers {
+ header 'Content-Type': 'application/json'
+ }
+ body(
+ code: value(consumer("123123"), producer(optional("123123")))
+ )
+ }
+}
@@ -16552,7 +16202,23 @@ provide the generated string that matches the provided regular expression. For e
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=optionals_test,indent=0]
+"""
+ given:
+ def request = given()
+ .header("Content-Type", "application/json")
+ .body('''{"email":"abc@abc.com","callback_url":"http://partners.com"}''')
+
+ when:
+ def response = given().spec(request)
+ .post("/users/password")
+
+ then:
+ response.statusCode == 404
+ response.header('Content-Type') == 'application/json'
+ and:
+ DocumentContext parsedJson = JsonPath.parse(response.body.asString())
+ assertThatJson(parsedJson).field("code").matches("(123123)?")
+"""
@@ -16560,12 +16226,37 @@ provide the generated string that matches the provided regular expression. For e
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{plugins_path}/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy[tags=wiremock,indent=0]
+'''
+{
+ "request" : {
+ "url" : "/users/password",
+ "method" : "POST",
+ "bodyPatterns" : [ {
+ "matchesJsonPath" : "$[?(@.email =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,4})?/)]"
+ }, {
+ "matchesJsonPath" : "$[?(@.callback_url =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]"
+ } ],
+ "headers" : {
+ "Content-Type" : {
+ "equalTo" : "application/json"
+ }
+ }
+ },
+ "response" : {
+ "status" : 404,
+ "body" : "{\\"code\\":\\"123123\\",\\"message\\":\\"User not found by email == [not.existing@user.com]\\"}",
+ "headers" : {
+ "Content-Type" : "application/json"
+ }
+ },
+ "priority" : 1
+}
+'''
-
-====== Executing custom methods on server side
+
+Executing custom methods on server side
It is also possible to define a method call to be executed on the server side during the test. Such a method can be added to the class defined as "baseClassForTests"
in the configuration. Example:
@@ -16575,7 +16266,27 @@ in the configuration. Example:
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=method,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ method 'PUT'
+ url $(consumer(regex('^/api/[0-9]{2}$')), producer('/api/12'))
+ headers {
+ header 'Content-Type': 'application/json'
+ }
+ body '''\
+ [{
+ "text": "Gonna see you at Warsaw"
+ }]
+ '''
+ }
+ response {
+ body (
+ path: $(consumer('/api/12'), producer(regex('^/api/[0-9]{2}$'))),
+ correlationId: $(consumer('1223456'), producer(execute('isProperCorrelationId($it)')))
+ )
+ status 200
+ }
+}
@@ -16583,7 +16294,21 @@ in the configuration. Example:
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{plugins_path}/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/bootSimple/src/test/groovy/org/springframework/cloud/contract/verifier/twitter/places/BaseMockMvcSpec.groovy[tags=base_class,indent=0]
+abstract class BaseMockMvcSpec extends Specification {
+
+ def setup() {
+ RestAssuredMockMvc.standaloneSetup(new PairIdController())
+ }
+
+ void isProperCorrelationId(Integer correlationId) {
+ assert correlationId == 123456
+ }
+
+ void isEmpty(String value) {
+ assert value == null
+ }
+
+}
@@ -16601,9 +16326,10 @@ the authToken() method returns everything that you need.
-
-===== Dynamic properties in matchers sections
+
+
+Dynamic properties in matchers sections
If you’ve been working with Pact this might seem familiar. Quite a few users
are used to having a separation between the body and setting dynamic parts of your contract.
@@ -16686,7 +16412,105 @@ will result in calling a foo method to which the value matching the
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy[tags=matchers,indent=0]
+Contract contractDsl = Contract.make {
+ request {
+ method 'GET'
+ urlPath '/get'
+ body([
+ duck: 123,
+ alpha: "abc",
+ number: 123,
+ aBoolean: true,
+ date: "2017-01-01",
+ dateTime: "2017-01-01T01:23:45",
+ time: "01:02:34",
+ valueWithoutAMatcher: "foo",
+ valueWithTypeMatch: "string"
+ ])
+ stubMatchers {
+ jsonPath('$.duck', byRegex("[0-9]{3}"))
+ jsonPath('$.duck', byEquality())
+ jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
+ jsonPath('$.alpha', byEquality())
+ jsonPath('$.number', byRegex(number()))
+ jsonPath('$.aBoolean', byRegex(anyBoolean()))
+ jsonPath('$.date', byDate())
+ jsonPath('$.dateTime', byTimestamp())
+ jsonPath('$.time', byTime())
+ }
+ headers {
+ contentType(applicationJson())
+ }
+ }
+ response {
+ status 200
+ body([
+ duck: 123,
+ alpha: "abc",
+ number: 123,
+ aBoolean: true,
+ date: "2017-01-01",
+ dateTime: "2017-01-01T01:23:45",
+ time: "01:02:34",
+ valueWithoutAMatcher: "foo",
+ valueWithTypeMatch: "string",
+ valueWithMin: [
+ 1,2,3
+ ],
+ valueWithMax: [
+ 1,2,3
+ ],
+ valueWithMinMax: [
+ 1,2,3
+ ],
+ valueWithMinEmpty: [],
+ valueWithMaxEmpty: [],
+ ])
+ testMatchers {
+ // asserts the jsonpath value against manual regex
+ jsonPath('$.duck', byRegex("[0-9]{3}"))
+ // asserts the jsonpath value against the provided value
+ jsonPath('$.duck', byEquality())
+ // asserts the jsonpath value against some default regex
+ jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
+ jsonPath('$.alpha', byEquality())
+ jsonPath('$.number', byRegex(number()))
+ jsonPath('$.aBoolean', byRegex(anyBoolean()))
+ // asserts vs inbuilt time related regex
+ jsonPath('$.date', byDate())
+ jsonPath('$.dateTime', byTimestamp())
+ jsonPath('$.time', byTime())
+ // asserts that the resulting type is the same as in response body
+ jsonPath('$.valueWithTypeMatch', byType())
+ jsonPath('$.valueWithMin', byType {
+ // results in verification of size of array (min 1)
+ minOccurrence(1)
+ })
+ jsonPath('$.valueWithMax', byType {
+ // results in verification of size of array (max 3)
+ maxOccurrence(3)
+ })
+ jsonPath('$.valueWithMinMax', byType {
+ // results in verification of size of array (min 1 & max 3)
+ minOccurrence(1)
+ maxOccurrence(3)
+ })
+ jsonPath('$.valueWithMinEmpty', byType {
+ // results in verification of size of array (min 0)
+ minOccurrence(0)
+ })
+ jsonPath('$.valueWithMaxEmpty', byType {
+ // results in verification of size of array (max 0)
+ maxOccurrence(0)
+ })
+ // will execute a method `assertThatValueIsANumber`
+ jsonPath('$.duck', byCommand('assertThatValueIsANumber($it)'))
+ }
+ headers {
+ contentType(applicationJson())
+ }
+ }
+}
@@ -16768,12 +16592,63 @@ assertions and the one from matchers with an and section):
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{plugins_path}/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy[tags=matchers,indent=0]
+ '''
+{
+ "request" : {
+ "urlPath" : "/get",
+ "method" : "GET",
+ "headers" : {
+ "Content-Type" : {
+ "matches" : "application/json.*"
+ }
+ },
+ "bodyPatterns" : [ {
+ "matchesJsonPath" : "$[?(@.valueWithoutAMatcher == 'foo')]"
+ }, {
+ "matchesJsonPath" : "$[?(@.valueWithTypeMatch == 'string')]"
+ }, {
+ "matchesJsonPath" : "$.list.some.nested[?(@.anothervalue == 4)]"
+ }, {
+ "matchesJsonPath" : "$.list.someother.nested[?(@.anothervalue == 4)]"
+ }, {
+ "matchesJsonPath" : "$.list.someother.nested[?(@.json == 'with value')]"
+ }, {
+ "matchesJsonPath" : "$[?(@.duck =~ /([0-9]{3})/)]"
+ }, {
+ "matchesJsonPath" : "$[?(@.duck == 123)]"
+ }, {
+ "matchesJsonPath" : "$[?(@.alpha =~ /([\\\\p{L}]*)/)]"
+ }, {
+ "matchesJsonPath" : "$[?(@.alpha == 'abc')]"
+ }, {
+ "matchesJsonPath" : "$[?(@.number =~ /(-?\\\\d*(\\\\.\\\\d+)?)/)]"
+ }, {
+ "matchesJsonPath" : "$[?(@.aBoolean =~ /((true|false))/)]"
+ }, {
+ "matchesJsonPath" : "$[?(@.date =~ /((\\\\d\\\\d\\\\d\\\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))/)]"
+ }, {
+ "matchesJsonPath" : "$[?(@.dateTime =~ /(([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]))/)]"
+ }, {
+ "matchesJsonPath" : "$[?(@.time =~ /((2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]))/)]"
+ }, {
+ "matchesJsonPath" : "$.list.some.nested[?(@.json =~ /(.*)/)]"
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{\\"duck\\":123,\\"alpha\\":\\"abc\\",\\"number\\":123,\\"aBoolean\\":true,\\"date\\":\\"2017-01-01\\",\\"dateTime\\":\\"2017-01-01T01:23:45\\",\\"time\\":\\"01:02:34\\",\\"valueWithoutAMatcher\\":\\"foo\\",\\"valueWithTypeMatch\\":\\"string\\",\\"valueWithMin\\":[1,2,3],\\"valueWithMax\\":[1,2,3],\\"valueWithMinMax\\":[1,2,3]}",
+ "headers" : {
+ "Content-Type" : "application/json"
+ }
+ }
+}
+'''
-
-==== JAX-RS support
+
+
+JAX-RS support
Starting with release 0.8.0 we support JAX-RS 2 Client API. Base class needs to define protected WebTarget webTarget and server initialization, right now the only option how to test JAX-RS API is to start a web server.
@@ -16793,12 +16668,34 @@ assertions and the one from matchers with an and section):
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy[tags=jaxrs,indent=0]
+'''
+ // when:
+ Response response = webTarget
+ .path("/users")
+ .queryParam("limit", "10")
+ .queryParam("offset", "20")
+ .queryParam("filter", "email")
+ .queryParam("sort", "name")
+ .queryParam("search", "55")
+ .queryParam("age", "99")
+ .queryParam("name", "Denis.Stepanov")
+ .queryParam("email", "bob@email.com")
+ .request()
+ .method("GET");
+
+ String responseAsString = response.readEntity(String.class);
+
+ // then:
+ assertThat(response.getStatus()).isEqualTo(200);
+ // and:
+ DocumentContext parsedJson = JsonPath.parse(responseAsString);
+ assertThatJson(parsedJson).field("property1").isEqualTo("a");
+'''
-
-==== Async support
+
+Async support
If you’re using asynchronous communication on the server side (your controllers are returning
Callable, DeferredResult etc. then inside your contract you have to provide in the response
@@ -16819,9 +16716,9 @@ section a async() method. Example:
}
-
-==== Working with Context Paths
+
+Working with Context Paths
Spring Cloud Contract supports context paths.
@@ -16872,7 +16769,15 @@ real requests and you need to setup your generated test’s base class to wo
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGeneratorSpec.groovy[tags=context_path_contract,indent=0]
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ method 'GET'
+ url '/my-context-path/url'
+ }
+ response {
+ status 200
+ }
+}
@@ -16880,7 +16785,22 @@ real requests and you need to setup your generated test’s base class to wo
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGeneratorSpec.groovy[tags=context_path_baseclass,indent=0]
+import com.jayway.restassured.RestAssured;
+import org.junit.Before;
+import org.springframework.boot.context.embedded.LocalServerPort;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest(classes = ContextPathTestingBaseClass.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+class ContextPathTestingBaseClass {
+
+ @LocalServerPort int port;
+
+ @Before
+ public void setup() {
+ RestAssured.baseURI = "http://localhost";
+ RestAssured.port = this.port;
+ }
+}
@@ -16897,36 +16817,81 @@ have that information (e.g. in the stubs you’ll see that you have too call
-
-==== Messaging Top-Level Elements
+
+Messaging Top-Level Elements
The DSL for messaging looks a little bit different than the one that focuses on HTTP.
-
-===== Output triggered by a method
-
+
+Output triggered by a method
The output message can be triggered by calling a method (e.g. a Scheduler was started and a message was sent)
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{tests_path}/samples-messaging-integration/src/test/groovy/com/example/IntegrationMessagingApplicationSpec.groovy[tags=method_trigger,indent=0]
+def dsl = Contract.make {
+ // Human readable description
+ description 'Some description'
+ // Label by means of which the output message can be triggered
+ label 'some_label'
+ // input to the contract
+ input {
+ // the contract will be triggered by a method
+ triggeredBy('bookReturnedTriggered()')
+ }
+ // output message of the contract
+ outputMessage {
+ // destination to which the output message will be sent
+ sentTo('output')
+ // the body of the output message
+ body('''{ "bookName" : "foo" }''')
+ // the headers of the output message
+ headers {
+ header('BOOK-NAME', 'foo')
+ }
+ }
+}
In this case the output message will be sent to output if a method called bookReturnedTriggered will be executed. In the message publisher’s side
we will generate a test that will call that method to trigger the message. On the consumer side you can use the some_label to trigger the message.
-
-===== Output triggered by a message
+
+Output triggered by a message
The output message can be triggered by receiving a message.
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{tests_path}/samples-messaging-integration/src/test/groovy/com/example/IntegrationMessagingApplicationSpec.groovy[tags=message_trigger,indent=0]
+def dsl = Contract.make {
+ description 'Some Description'
+ label 'some_label'
+ // input is a message
+ input {
+ // the message was received from this destination
+ messageFrom('input')
+ // has the following body
+ messageBody([
+ bookName: 'foo'
+ ])
+ // and the following headers
+ messageHeaders {
+ header('sample', 'header')
+ }
+ }
+ outputMessage {
+ sentTo('output')
+ body([
+ bookName: 'foo'
+ ])
+ headers {
+ header('BOOK-NAME', 'foo')
+ }
+ }
+}
@@ -16934,21 +16899,40 @@ we will generate a test that will call that method to trigger the message. On th
we will generate a test that will send the input message to the defined destination. On the consumer side you can either send a message to the input
destination or use the some_label to trigger the message.
-
-===== Consumer / Producer
+
+Consumer / Producer
In HTTP you have a notion of client/stub and `server/test notation. You can use them also in messaging but we’re providing also the consumer and produer methods
as presented below (note you can use either $ or value methods to provide consumer and producer parts)
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=consumer_producer]
+Contract.make {
+ label 'some_label'
+ input {
+ messageFrom value(consumer('jms:output'), producer('jms:input'))
+ messageBody([
+ bookName: 'foo'
+ ])
+ messageHeaders {
+ header('sample', 'header')
+ }
+ }
+ outputMessage {
+ sentTo $(consumer('jms:input'), producer('jms:output'))
+ body([
+ bookName: 'foo'
+ ])
+ }
+}
-
-=== Extending the DSL
+
+
+
+Extending the DSL
It is possible to provide your own functions to the DSL. The key requirement for this
feature was to maintain the static compatibility. Below you will be able to see an example
@@ -16967,9 +16951,8 @@ of:
The full example can be found here.
-
-==== Common JAR
-
+
+Common JAR
Below you can find three classes that we will reuse in the DSLs.
@@ -16978,7 +16961,49 @@ of:
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{samples_url}/common/src/main/java/com/example/PatternUtils.java[]
+package com.example;
+
+import java.util.regex.Pattern;
+
+/**
+ * If you want to use {@link Pattern} directly in your tests
+ * then you can create a class resembling this one. It can
+ * contain all the {@link Pattern} you want to use in the DSL.
+ *
+ * <pre>
+ * {@code
+ * request {
+ * body(
+ * [ age: $(c(PatternUtils.oldEnough()))]
+ * )
+ * }
+ * </pre>
+ *
+ * Notice that we're using both {@code $()} for dynamic values
+ * and {@code c()} for the consumer side.
+ *
+ * @author Marcin Grzejszczak
+ */
+public class PatternUtils {
+ public static String tooYoung() {
+ return "[0-1][0-9]";
+ }
+
+ public static Pattern oldEnough() {
+ return Pattern.compile("[2-9][0-9]");
+ }
+
+ public static Pattern anyName() {
+ return Pattern.compile("[a-zA-Z]+");
+ }
+
+ /**
+ * Makes little sense but it's just an example ;)
+ */
+ public static Pattern ok() {
+ return Pattern.compile("OK");
+ }
+}
@@ -16986,7 +17011,65 @@ of:
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{samples_url}/common/src/main/java/com/example/ConsumerUtils.java[]
+package com.example;
+
+import org.springframework.cloud.contract.spec.internal.ClientDslProperty;
+import org.springframework.cloud.contract.spec.internal.DslProperty;
+
+/**
+ * DSL Properties passed to the DSL from the consumer's perspective.
+ * That means that on the input side {@code Request} for HTTP
+ * or {@code Input} for messaging you can have a regular expression.
+ * On the {@code Response} for HTTP or {@code Output} for messaging
+ * you have to have a concrete value.
+ *
+ * @author Marcin Grzejszczak
+ */
+public class ConsumerUtils {
+ /**
+ * Consumer side property. By using the {@link ClientDslProperty}
+ * you can omit most of boilerplate code from the perspective
+ * of dynamic values. Example
+ *
+ * <pre>
+ * {@code
+ * request {
+ * body(
+ * [ age: $(ConsumerUtils.oldEnough())]
+ * )
+ * }
+ * </pre>
+ *
+ * That way the consumer side value of age field will be
+ * a regular expression and the producer side will be generated.
+ *
+ * @author Marcin Grzejszczak
+ */
+ public static ClientDslProperty oldEnough() {
+ return new ClientDslProperty(PatternUtils.oldEnough());
+ }
+
+ /**
+ * Consumer side property. By using the {@link ClientDslProperty}
+ * you can omit most of boilerplate code from the perspective
+ * of dynamic values. Example
+ *
+ * <pre>
+ * {@code
+ * request {
+ * body(
+ * [ name: $(ConsumerUtils.anyName())]
+ * )
+ * }
+ * </pre>
+ *
+ * That way the consumer will be a regular expression and the
+ * producer side value will be equal to {@code marcin}
+ */
+ public static DslProperty anyName() {
+ return new DslProperty<>(PatternUtils.anyName(), "marcin");
+ }
+}
@@ -16994,19 +17077,53 @@ of:
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{samples_url}/common/src/main/java/com/example/ProducerUtils.java[]
+package com.example;
+
+import org.springframework.cloud.contract.spec.internal.ServerDslProperty;
+
+/**
+ * DSL Properties passed to the DSL from the producer's perspective.
+ * That means that on the input side {@code Request} for HTTP
+ * or {@code Input} for messaging you have to have a concrete value.
+ * On the {@code Response} for HTTP or {@code Output} for messaging
+ * you can have a regular expression.
+ *
+ * @author Marcin Grzejszczak
+ */
+public class ProducerUtils {
+
+ /**
+ * Producer side property. By using the {@link ProducerUtils}
+ * you can omit most of boilerplate code from the perspective
+ * of dynamic values. Example
+ *
+ * <pre>
+ * {@code
+ * response {
+ * body(
+ * [ status: $(ProducerUtils.ok())]
+ * )
+ * }
+ * </pre>
+ *
+ * That way the producer side value of age field will be
+ * a regular expression and the consumer side will be generated.
+ */
+ public static ServerDslProperty ok() {
+ return new ServerDslProperty(PatternUtils.ok());
+ }
+}
-
-==== Adding the dependency to project
+
+Adding the dependency to project
In order for the plugins and IDE to be able to reference the common JAR classes you need
to pass the dependency to your project.
-
-===== Test dependency in project’s dependencies
-
+
+Test dependency in project’s dependencies
First add the common jar dependency as a test dependency. That way since your
contracts files are available at test resources path, automatically the
@@ -17015,47 +17132,114 @@ common jar classes will be visible in your Groovy files.
Maven
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{samples_url}/producer/pom.xml[tags=test_dep,indent=0]
+<dependency>
+ <groupId>com.example</groupId>
+ <artifactId>beer-common</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+</dependency>
Gradle
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{samples_url}/producer/build.gradle[tags=test_dep,indent=0]
+testCompile("com.example:beer-common:0.0.1-SNAPSHOT")
-
-===== Test dependency in plugin’s dependencies
+
+Test dependency in plugin’s dependencies
Now you have to add the dependency for the plugin to reuse at runtime.
Maven
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{samples_url}/producer/pom.xml[tags=test_dep_in_plugin,indent=0]
+<plugin>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-contract-maven-plugin</artifactId>
+ <version>${spring-cloud-contract.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <packageWithBaseClasses>com.example</packageWithBaseClasses>
+ </configuration>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-contract-verifier</artifactId>
+ <version>${spring-cloud-contract.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.example</groupId>
+ <artifactId>beer-common</artifactId>
+ <version>${project.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+</plugin>
Gradle
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{samples_url}/producer/build.gradle[tags=test_dep_in_plugin,indent=0]
+classpath "com.example:beer-common:0.0.1-SNAPSHOT"
-
-===== Referencing classes in DSLs
+
+Referencing classes in DSLs
Now you can reference your classes in your DSL. Example:
-Unresolved directive in rc/main/asciidoc/verifier/contract.adoc - include::{samples_url}/producer/src/test/resources/contracts/beer/rest/shouldGrantABeerIfOldEnough.groovy[indent=0]
+package contracts.beer.rest
+
+import org.springframework.cloud.contract.spec.Contract
+
+import static com.example.ConsumerUtils.oldEnough
+import static com.example.ProducerUtils.ok
+
+Contract.make {
+ request {
+ description("""
+Represents a successful scenario of getting a beer
+
+given:
+ client is old enough
+when:
+ he applies for a beer
+then:
+ we'll grant him the beer
+""")
+ method 'POST'
+ url '/check'
+ body(
+ age: $(oldEnough())
+ )
+ headers {
+ contentType(applicationJson())
+ }
+ }
+ response {
+ status 200
+ body("""
+ {
+ "status": "${value(ok())}"
+ }
+ """)
+ headers {
+ contentType(applicationJson())
+ }
+ }
+}
-
-=== Links
+
+
+
+Links
Here you can find interesting links related to Spring Cloud Contract Verifier:
@@ -17087,9 +17271,12 @@ common jar classes will be visible in your Groovy files.
-
-= Appendix: Compendium of Configuration Properties
+
+
+Appendix: Compendium of Configuration Properties
+
+
@@ -17656,11 +17843,6 @@ common jar classes will be visible in your Groovy files.
-eureka.instance.environment
-
-
-
-
eureka.instance.health-check-url
Gets the absolute health check page URL for this instance. The users can provide
@@ -17796,17 +17978,6 @@ common jar classes will be visible in your Groovy files.
used in prference to the hostname reported by the OS.
-eureka.instance.registry.default-open-for-traffic-count
-1
-Value used in determining when leases are cancelled, default to 1 for standalone.
- Should be set to 0 for peer replicated eurekas
-
-
-eureka.instance.registry.expected-number-of-renews-per-min
-1
-
-
-
eureka.instance.secure-health-check-url
Gets the absolute secure health check page URL for this instance. The users can
@@ -17833,7 +18004,7 @@ common jar classes will be visible in your Groovy files.
eureka.instance.secure-virtual-host-name
-unknown
+
Gets the secure virtual host name defined for this instance.
This is typically the way other instance would find this instance by using the
secure virtual host name.Think of this as similar to the fully qualified domain
@@ -17862,7 +18033,7 @@ common jar classes will be visible in your Groovy files.
eureka.instance.virtual-host-name
-unknown
+
Gets the virtual host name defined for this instance.
This is typically the way other instance would find this instance by using the
virtual host name.Think of this as similar to the fully qualified domain name, that
@@ -18244,11 +18415,6 @@ common jar classes will be visible in your Groovy files.
Flag to indicate that the config server health indicator should be installed.
-health.config.time-to-live
-0
-Time to live for cached result, in milliseconds. Default 300000 (5 min).
-
-
hystrix.metrics.enabled
true
Enable Hystrix metrics polling. Defaults to true.
@@ -18295,16 +18461,6 @@ common jar classes will be visible in your Groovy files.
Fully qualified class name for monitor registry used by Servo.
-proxy.auth.load-balanced
-
-
-
-
-proxy.auth.routes
-
-Authentication strategy per route.
-
-
spring.cloud.bus.ack.destination-service
Service that wants to listen to acks. By default null (meaning all services).
@@ -18464,170 +18620,6 @@ common jar classes will be visible in your Groovy files.
Multiplier for next interval.
-spring.cloud.config.server.bootstrap
-false
-Flag indicating that the config server should initialize its own Environment with
- properties from the remote repository. Off by default because it delays startup but
- can be useful when embedding the server in another application.
-
-
-spring.cloud.config.server.default-application-name
-application
-Default application name when incoming requests do not have a specific one.
-
-
-spring.cloud.config.server.default-label
-
-Default repository label when incoming requests do not have a specific label.
-
-
-spring.cloud.config.server.default-profile
-default
-Default application profile when incoming requests do not have a specific one.
-
-
-spring.cloud.config.server.encrypt.enabled
-true
-Enable decryption of environment properties before sending to client.
-
-
-spring.cloud.config.server.git.basedir
-
-Base directory for local working copy of repository.
-
-
-spring.cloud.config.server.git.clone-on-start
-
-Flag to indicate that the repository should be cloned on startup (not on demand).
- Generally leads to slower startup but faster first query.
-
-
-spring.cloud.config.server.git.default-label
-
-
-
-
-spring.cloud.config.server.git.environment
-
-
-
-
-spring.cloud.config.server.git.force-pull
-
-Flag to indicate that the repository should force pull. If true discard any local
- changes and take from remote repository.
-
-
-spring.cloud.config.server.git.git-factory
-
-
-
-
-spring.cloud.config.server.git.password
-
-Password for authentication with remote repository.
-
-
-spring.cloud.config.server.git.repos
-
-Map of repository identifier to location and other properties.
-
-
-spring.cloud.config.server.git.search-paths
-
-Search paths to use within local working copy. By default searches only the root.
-
-
-spring.cloud.config.server.git.timeout
-
-Timeout (in seconds) for obtaining HTTP or SSH connection (if applicable). Default
- 5 seconds.
-
-
-spring.cloud.config.server.git.uri
-
-URI of remote repository.
-
-
-spring.cloud.config.server.git.username
-
-Username for authentication with remote repository.
-
-
-spring.cloud.config.server.health.repositories
-
-
-
-
-spring.cloud.config.server.native.fail-on-error
-false
-Flag to determine how to handle exceptions during decryption (default false).
-
-
-spring.cloud.config.server.native.search-locations
-[]
-Locations to search for configuration files. Defaults to the same as a Spring Boot
- app so [classpath:/,classpath:/config/,file:./,file:./config/].
-
-
-spring.cloud.config.server.native.version
-
-Version string to be reported for native repository
-
-
-spring.cloud.config.server.overrides
-
-Extra map for a property source to be sent to all clients unconditionally.
-
-
-spring.cloud.config.server.prefix
-
-Prefix for configuration resource paths (default is empty). Useful when embedding
- in another application when you don’t want to change the context path or servlet
- path.
-
-
-spring.cloud.config.server.strip-document-from-yaml
-true
-Flag to indicate that YAML documents that are text or collections (not a map)
- should be returned in "native" form.
-
-
-spring.cloud.config.server.svn.basedir
-
-Base directory for local working copy of repository.
-
-
-spring.cloud.config.server.svn.default-label
-trunk
-The default label for environment properties requests.
-
-
-spring.cloud.config.server.svn.environment
-
-
-
-
-spring.cloud.config.server.svn.password
-
-Password for authentication with remote repository.
-
-
-spring.cloud.config.server.svn.search-paths
-
-Search paths to use within local working copy. By default searches only the root.
-
-
-spring.cloud.config.server.svn.uri
-
-URI of remote repository.
-
-
-spring.cloud.config.server.svn.username
-
-Username for authentication with remote repository.
-
-
spring.cloud.config.token
Security Token passed thru to underlying environment repository.
@@ -18695,11 +18687,8 @@ common jar classes will be visible in your Groovy files.
spring.cloud.consul.config.watch.wait-time
-55
-The number of seconds to wait (or block) for watch query, defaults to 55.
- Needs to be less than default ConsulClient (defaults to 60). To increase ConsulClient
- timeout create a ConsulClient bean with a custom ConsulRawClient with a custom
- HttpClient.
+60
+The number of seconds to wait (or block) for watch query. Defaults to 60.
spring.cloud.consul.discovery.acl-token
@@ -18722,23 +18711,11 @@ common jar classes will be visible in your Groovy files.
Tag to query for in service list if one is not listed in serverListQueryTags.
-spring.cloud.consul.discovery.default-zone-metadata-name
-zone
-Service instance zone comes from metadata.
- This allows changing the metadata tag name.
-
-
spring.cloud.consul.discovery.enabled
true
Is service discovery enabled?
-spring.cloud.consul.discovery.fail-fast
-true
-Throw exceptions during service registration if true, otherwise, log
- warnings (defaults to true).
-
-
spring.cloud.consul.discovery.health-check-interval
10s
How often to perform the health check (e.g. 10s)
@@ -18799,11 +18776,6 @@ common jar classes will be visible in your Groovy files.
Unique service instance id
-spring.cloud.consul.discovery.instance-zone
-
-Service instance zone
-
-
spring.cloud.consul.discovery.ip-address
IP address to use when accessing service (must also set preferIpAddress
@@ -18942,26 +18914,11 @@ common jar classes will be visible in your Groovy files.
List of Java regex expressions for network interfaces that will be ignored.
-spring.cloud.inetutils.preferred-networks
-
-List of Java regex expressions for network addresses that will be ignored.
-
-
spring.cloud.inetutils.timeout-seconds
1
Timeout in seconds for calculating hostname.
-spring.cloud.inetutils.use-only-site-local-interfaces
-false
-Use only interfaces with site local addresses. See {@link InetAddress#isSiteLocalAddress()} for more details.
-
-
-spring.cloud.loadbalancer.retry.enabled
-false
-
-
-
spring.cloud.stream.binders
@@ -19325,11 +19282,6 @@ common jar classes will be visible in your Groovy files.
the traces).
-spring.sleuth.trace-id128
-false
-When true, generate 128-bit trace IDs instead of 64-bit ones.
-
-
zuul.add-host-header
false
Flag to determine whether the proxy forwards the Host header.
@@ -19355,14 +19307,6 @@ common jar classes will be visible in your Groovy files.
-zuul.ignore-security-headers
-true
-Flag to say that SECURITY_HEADERS are added to ignored headers if spring security is on the classpath.
- By setting ignoreSecurityHeaders to false we can switch off this default behaviour. This should be used together with
- disabling the default spring security headers
- see https://docs.spring.io/spring-security/site/docs/current/reference/html/headers.html#default-security-headers
-
-
zuul.ignored-headers
Names of HTTP headers to ignore completely (i.e. leave them out of downstream
@@ -19406,11 +19350,11 @@ common jar classes will be visible in your Groovy files.
Map of route names to properties.
-zuul.s-e-c-u-r-i-t-y-h-e-a-d-e-r-s
+zuul.security_headers
Headers that are generally expected to be added by Spring Security, and hence often
duplicated if the proxy and the backend are secured with Spring. By default they
- are added to the ignored headers if Spring Security is present and ignoreSecurityHeaders = true.
+ are added to the ignored headers if Spring Security is present.
zuul.semaphore.max-semaphores
@@ -19453,12 +19397,5 @@ common jar classes will be visible in your Groovy files.
-
-
-
-
-
-
-