Before we start talking about Spring Cloud Bus
, let’s look at another IT term: ESB (Enterprise Service Bus)
. ESB
is described in Wikipedia (https://en.wikipedia.org/wiki/Enterprise_service_bus):
An enterprise service bus (ESB) implements a communication system between mutually interacting software applications in a service-oriented architecture (SOA). It represents a software architecture for distributed computing, and is a special variant of the more general client-server model, wherein any application may behave as server or client. ESB promotes agility and flexibility with regard to high-level protocol communication between applications. Its primary use is in enterprise application integration (EAI) of heterogeneous and complex service landscapes.
Enterprise service bus
usually provides an abstraction layer on the enterprise message system, so that the integration architect can use the value of the message to complete the integration without coding. In simple terms, the enterprise service bus
is another abstraction layer that is built on top of the message middleware, so that we can complete the processing of business logic without caring about message-related processing.
At this point, have you suddenly understood the relationship between Spring Cloud Bus
and Spring Cloud Stream
? When you first know these two components, most of you will be confused about what is the difference between the two? What is the connection between them? Stream
abstracts and encapsulates the message middleware to provide a unified interface for us to send and listen to messages, while Bus
is abstracted and encapsulated again on the basis of Stream
, so that we can complete the processing of business logic without understanding the basics of message sending and listening.
So how does Spring Cloud Bus
do it for us? In a word, it is the event mechanism.
Spring’s event mechanism
There is an event mechanism in the Spring
framework, which is an implementation of the observer pattern. Observer pattern establishes a kind of object-to-object relationship. When an object(called: observation target
) changes, it will automatically notify other objects(called: observer
), and these observers
will make corresponding reaction. An observation target
can correspond to multiple observers
, and there is no mutual connection between these observers
. You can add and delete observers
as needed, making the system easier to expand. The following purposes can be achieved through the Spring event mechanism:
- Decoupling between application modules;
- You can define multiple processing methods for the same event according to your needs;
- Not disturbing the main line application is an excellent
Open and Close Principle (OCP)
practice.
When we introduce event mechanism in our application, we need to use the following interfaces or abstract classes in Spring
:
- ApplicationEventPublisher: This is an interface for publishing an event;
- ApplicationEvent: This is an abstract class used to define an event;
- ApplicationListener <E extends ApplicationEvent>: This is an interface that implements event listening.
The context of the Spring application, ApplicationContext
, implements the ApplicationEventPublisher
interface by default, so when publishing events, we can directly use the ApplicationContext.publishEvent()
method.
A typical Spring
event sending and listening code is as follows:
Define event
For example, we define a user event:
1 | public class UserEvent extends ApplicationEvent { |
Define listener
We define a user event listener and handle the event when the user changes:
1 |
|
Event listening is relatively simple. You only need to implement the ApplicationListener
interface and handle it accordingly.
Publish event
We can implement it directly in Event class. For example, we change the above UserEvent
to the following:
1 | public class UserEvent extends ApplicationEvent { |
Then we can publish events with the following code where needed:
1 | new UserEvent(user, UserEvent.ET_UPDATE).fire(); |
Spring Cloud Bus mechanism
We learned about Spring
‘s event mechanism above, so how does Spring Cloud Bus
combine the event mechanism with Stream
? In summary, the mechanism is as follows:
- Add the
@RemoteApplicationEventScan
annotation to the application that needs to publish or listen to events. With this annotation we can start the binding of the message channel mentioned in theStream
; - For event publishing, you need to extend the
ApplicationEvent
extension class-RemoteApplicationEvent
. When this type of event is published throughApplicationContext.publishEvent()
,Spring Cloud Bus
will wrap the event, form a message we are familiar with, and then send it to the message broker through the defaultspringCloudBus
message channel; - For event listeners, you don’t need to make any changes, and you can still listen to messages in the same way as above. However, it should be noted that the events defined in step 2 must also be defined in the consumer microservices project, and the entire class names need to be consistent (if they are inconsistent, a little extra work is needed).
With Spring Cloud Bus
, we can develop like writing a monolithic application without having to deal with a lot of concepts such as message broker, topics, messages, channels, and so on.
Let’s look at how we can modify the Stream
demo to incooperate Spring Cloud Bus
.
Refactor Spring Cloud Stream demo
Refactor Product-Service
1. Add bus dependency
1 | <dependency> |
2. Create Product Event
We change the product message to an event with the following code:
1 | public class ProductEvent extends RemoteApplicationEvent { |
Here the difference in constructor is that you need to specify the originService
and destinationService
when constructing an event. For event publishers, originService
is itself, and destinationService
refers to those microservice instances that need to publish this event. The format of the destinationService
configuration is: {serviceId}: {appContextId}
. During configuration, serviceId
and appContextId
can use wildcards. If both variables use wildcards (*:**)
, the event will be published to all microservice instances. If only the appContextId
is omitted, the event will only be published to all instances of the specified microservice. For example: userservice:**
, the event will only be published to the userservice
microservice.
3. Implement event publishing
We change the code in ProductService as follows:
1 |
|
The code of RemoteApplicationEventPublisher
:
1 | public class RemoteApplicationEventPublisher { |
4. Main application
Finally, modify the main class and add the @RemoteApplicationEventScan
annotation:
1 |
|
Note: the remote event must be defined in a subpackage of the class annotated by
@RemoteApplicationEventScan
annotation, otherwise remote event publishing cannot be achieved
Refactor Product-Service-Consumer
1. Add bus dependency
1 | <dependency> |
2. Copy ProductEvent to this project
3. Implement event listening
1 |
|
4. Main class
As with Product-Service
, remote message scanning needs to be enabled for both event publishing and event monitoring. Add the @RemoteApplicationEventScan
annotation directly to the main class
1 |
|
Testing
Start service in the following order:
- Kafka server;
- Service-discovery;
- Product-Service microservice;
- Product-Service-Consumer microservices.
POST a HTTP request to: http://localhost:2100/products/item-2. In the console of the Product-Service
microservice, you can see output similar to the following:
1 | 2020-03-06 23:40:26.498 DEBUG 82437 --- [nio-2100-exec-1] o.s.web.servlet.DispatcherServlet : POST "/products/item-2", parameters={} |
From the output log, you can see that the product event has been published. If at this time we look at the console of the Product-Service-Consumer
microservice, we can see the output of the following:
1 | 2020-03-06 23:40:33.722 INFO 82441 --- [container-0-C-1] s.mall.event.ProductEventListener : Received update event itemCode: item-2 |
From the log output, you can see that the Product-Service-Consumer
microservice has been able to correctly receive the product change event and handle it accordingly.
Conclusion
It is indeed easier to understand and easier to use Bus
from the refactored code. This is very good for simple applications, such as broadcasting. A typical application is configuration refresh in Config
. When both Config
and Bus
are introduced into a project, configuration changes can be broadcasted via the /bus/refresh
endpoint, allowing the corresponding microservice to reload configuration data.
Of course, another layer of the simplicity of Bus
is that it is not flexible enough, so whether you use Bus
or directly use Streams
in your project depends on your needs.
Check out the source code here: Bus demo