Event notification

The event notification pattern works by emitting events to subscribers once a command is executed. This can be compared to the observer pattern in which you observe a subject that has a list of many listeners or subscribers that are automatically notified when the state of the observed object changes.

This behavior is widely used by event bus libraries that allow publish-subscribe communication among the components that are part of an application. The most common use cases for these libraries are targeted towards the UI, but they are also applicable to other parts of the system in the backend. The following diagram demonstrates how an event is sent to the bus and then propagated to all subscribers, which were previously registered: 

Event bus

There are two main benefits to using this event-notification mechanism:

  • Decoupled systems and functionalities
  • Inverted dependencies

To better understand these benefits, let's imagine that we need to work on the following requirement of our banking application:

The bank wants to offer customers who are using the mobile app the chance to transfer money. This will include either transferring between accounts owned by our bank or transferring to external banks. Once this transaction is executed, we need to notify the customers about the transaction status using their preferred notification channels.

The bank also has an application that is used by the call center staff that notifies our agents of our clients' balance. When a client's account balance is higher than a predetermined amount of money, the call center system will alert the agents, who will then call the clients to make them aware of the possibility of investing their money in the bank. Finally, if a transaction involves an external bank, we need to notify them about the transaction status too.

Using a classic approach to write an application, we can correctly build a system where all the following postconditions listed in the requirements are executed within the transfer application boundaries after the money transfer occurs, as demonstrated in the following diagram:

Coupled transfer money application

As we can see from the preceding diagram, the transfer money application needs to know about all the postconditions that have to be met once the transaction has occurred; using this approach, we will end up writing all the necessary code to interact with other systems, which leads us to couple the application with other systems.

On the other hand, using the event-notification pattern, we can decouple the transfer money application, as shown in the following diagram:

Decoupled transfer money application

In the preceding diagram, we can see that once the <Transfer money> command is executed, a <Money transferred> event is emitted, and all the subscribed systems are notified. By doing this, we can get rid of the coupling among the systems.

The important thing to note here is that the money transfer application doesn't even need to know about the existence of other software systems, and all the postconditions are met out of the boundaries of this application. In other words, decoupled systems lead us to invert dependencies.

Decoupled systems and inverted dependencies sound fantastic, but the implicit disadvantage of these is that you lose visibility. This is because the application emitting events doesn't know anything about the processes that are executed once the event is published, and there is no code for reading other systems.

It is often impossible to identify dependencies downstream, and some techniques for correlating events across different logs are commonly used to relieve this nightmare. 

Coupled systems give all the information regarding downstream dependencies, and are hard to evolve. Conversely, decoupled systems know nothing about downstream dependencies, but they offer the chance to evolve systems independently.

Now that we have learned of the underlying concepts that are supporting the event-notification pattern, we can say that the most visible technology for implementing this kind of application is the use of messaging systems such as RabbitMQ, AWS SQS/SNS, MSMQ, and so on. These are supported by Spring under the Spring Cloud Stream project umbrella. In our case, we are going to use RabbitMQ, which can be supported by adding the following dependency: 

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId> </dependency>
In order to make the setup process of RabbitMQ accessible, the code provided for this book in this chapter includes a Docker Compose file that should be executed using the docker-compose up commandWe will look at what Docker Compose is and how it works in Chapter 10, Containerizing your Applications.

Spring Cloud Stream is built on the top of Spring Integration and offers the chance to produce and consume messages easily, as well as the chance to use all the built-in functionalities of Spring Integration. We are going to use this project to implement the example of the banking application mentioned earlier, so we will need to add the following dependency:

<dependency> 
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>

The transfer money application will expose an endpoint to allow the transferring of money. Once this transaction is done, an event notification needs to be sent to the other apps. Spring Cloud Stream makes it possible to define messaging channels with the use of the @Output annotation, as follows:

public interface EventNotificationChannel 
{
@Output
MessageChannel moneyTransferredChannel();
}

This interface can be annotated and used wherever you want. Let's look at how to use this in the controller, which exposes the functionality to transfer money:

@RestController
public class TransferController
{
private final MessageChannel moneyTransferredChannel;
public TransferController(EventNotificationChannel channel)
{
this.moneyTransferredChannel = channel.moneyTransferredChannel();
}
@PostMapping("/transfer")
public void doTransfer(@RequestBody TransferMoneyDetails
transferMoneyDetails)
{
log.info("Transferring money with details: " +
transferMoneyDetails);
Message<String> moneyTransferredEvent = MessageBuilder
.withPayload
("Money transferred for client with id: " +

transferMoneyDetails.getCustomerId()).build()
;
this.moneyTransferredChannel.send(moneyTransferredEvent);
}
}

One thing to keep in mind when we are using the event-notification pattern is that the application that is emitting events simply provides very basic information regarding the executed command. In this case, the <Money Transferred> event contains the client ID that should be used later to query more information and determine whether or not additional operations need to be executed. This process always involves one or more additional interactions with other systems, databases, and so on.

The subscribers can take advantage of Spring Cloud Stream as well. In this case, the @Input annotation should be used as follows:

public interface EventNotificationChannel 
{
@Input
SubscribableChannel subscriptionOnMoneyTransferredChannel();
}

Using Spring Integration, a complete integration flow can be executed to process the incoming message in this way:

@Bean
IntegrationFlow integrationFlow(
EventNotificationChannel eventNotificationChannel) {
return IntegrationFlows.from
(eventNotificationChannel
.subscriptionOnMoneyTransferredChannel()).
handle(String.class, new GenericHandler<String>() {
@Override
public Object handle(String payload,
Map<String, Object> headers) {

// Use the payload to find the transaction and determine
//
if a notification should be sent to external banks

}
}).get();
}

Once the message is retrieved, it should be used to query additional information regarding the transaction and determine whether a notification should be sent to external banks. This approach is useful for reducing the size of the payload. It also helps to avoid sending information that is often unnecessary and useless for other systems, but which increases the traffic retrieved by the source application.

In worst-case scenarios, every single event produced will retrieve at least one additional request asking for the transaction details, as shown in the following diagram:

 
Downstream dependencies requesting transaction details

In our example, we will have at least three other requests from the dependent systems for each event produced. 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.142.194