Spring Cloud Streams With Functional Programming Model
This story talks about writing Spring Cloud Functions using Java Functional Program. Before that, we can write using @EnableBinding, @Input, @Output. But these annotations have been deprecated as of 3.1 in favor of the functional programming model. We will see how to write producers, consumers using the Java Functional Programming model.
Message-driven processing is a widely adopted design pattern in service-based architecture. Such design efficiently supports a high volume of request processing as the service integration is based on non-blocking message communication.
A message broker is a key infrastructure component that conveys messages among services. However, it is a chore for developers to learn APIs of the specific message broker in order to write code for the integration with message brokers because there are many popular message brokers are available in the industry such as ActiveMQ, RabbitMQ, and Kafka.
Spring Cloud Stream is a framework for building message-driven microservice applications. Spring Cloud Stream builds upon Spring Boot and uses Spring Integration to provide connectivity to message brokers. It provides configuration of middleware, introducing the concepts of publish-subscribe, consumer groups, and partitions.
Annotation-based implementation
If you are used to building applications based on the Spring framework, then you should be familiar with the use of annotation. You can simply tag features to classes and methods by adding annotations. For example, annotation @Streamlistener is to specify the method to be triggered by incoming messages. While an annotation can still be used for development for a while, such an approach is already deprecated and it is no longer recommended since Spring Cloud Stream version 3.x.
Functional programming model
On the other hand, the functional programming model is the new approach. Functions are the basic unit in the paradigm of functional programming. Each function is an independent module that solves a specific problem while the composition of them can solve complicated problems. Java Stream is a typical example of functional programming.
To extend this we will come up with a simple example ie., A famous FizzBuzz question asked in interviews.
Different ways to implement, the message-driven applications using either any message broker like Kafka, RabbitMQ. Spring Cloud Stream simplifies the development of message-driven systems, making the development so much easier and contributing to effortless integration with message brokers. Moreover, the framework automatically provisions topic exchanges, message queues, and binding based on configuration, achieving code as infrastructure which greatly reduces the dependency on environment setup.
Ways to implement event-driven applications using Spring Cloud Stream
- Annotation-based implementation using Binders, Input, and Output (This is deprecated from spring-cloud version 3x)
- Functional-based implementation leveraging Java Functional programming.
Functional-based implementation
Functional-based implementation does not need the definition of message channels because the framework links directly the binding to functions.
Instead of using @StreamListener annotation, we define the message handlers using Consumer function implementation and register them as beans.
Please find the complete source code on the GitHub link.
Let’s define functions
Here, I am using the same application as both producer and consumer for the convenience of the demo.
fizzBuzzProducer is the producer which will generate the random number within range for every 5 seconds.
fizzBuzzProcessor is the processor just like middleware, the number generated by the producer. This acts as a consumer from the previously produced message and processes the logic of fizzBuzz logic and published to another topic.
fizzBuzzConsumer will consume the message that is published by the processor and prints out in the console.
The use case seems simple, my intention is to show who Java Functional Programming can be used to define producers, consumers in Spring boot application of course multiple topics.
Configuration
The configuration is similar to the annotation-based approach but the binding name is determined by the framework based on this naming convention: <function name>-in-<index> where <index> is always 0 for most cases unless functions with multiple inputs and outputs.
Secondly, register the function names in spring.cloud.function.definition for the usage of stream binding.