Cookies
Diese Website verwendet Cookies und ähnliche Technologien für Analyse- und Marketingzwecke. Durch Auswahl von Akzeptieren stimmen Sie der Nutzung zu, alternativ können Sie die Nutzung auch ablehnen. Details zur Verwendung Ihrer Daten finden Sie in unseren Datenschutz­hinweisen, dort können Sie Ihre Einstellungen auch jederzeit anpassen.

Engineering

AMQP in a REST scenario
Minuten Lesezeit
Blog Post - AMQP in a REST scenario
Heiko Zeus

Starting point: A SOA via REST

The footing for this case study is a SOA where multiple web applications communicate via REST interfaces. These apps can be split up into two camps: on the one hand there are apps for master data which rarely need to access other systems, and on the other hand there are many client apps that access the master data via a REST interface. This diagram depicts a section of the current REST architecture:

data access paths via REST

Our case study consists of two master data apps, one for article data and another for customer data, and two client apps. The orders app needs access to the articles and customers master data, while the contract management only needs access to customer data.

The task at hand: Notifying data changes

Aside from connecting all the apps via REST the additional technological challenge is keeping the client apps updated when master data changes. A great example of this is when current orders have to be canceled because a customer hasn't been able to fulfill long due payment deadlines on past orders. A proposed solution would have to adhere to the following constraints:

  • Loose coupling: New client apps should be able to be integrated into the system without having to change any other app. The master data apps should not need to know which client apps to notify of data changes.
  • Asynchronous execution of updates: Users of the master data apps should not be slowed down when updates need to be issued to client apps, thus the updates need to happen asynchronously. Eventual consistency is taken into account here.
  • Ensured delivery of data: Apps should be able to receive the updated regardless of whether they were online at the time of the updates or not.

A central pub-sub mechanism would suffice the first two constraints. Client apps would be able to subscribe to certain types of updates without the master data apps needing to know which apps are subscribed. The third constraint excludes "simple" solutions like a Redis Pub/Sub because only active apps would receive the updates. This is where AMQP can play its strengths and proves to be a handy solution, as we'll explore further.

Solution: AMQP as a communications hub

AMQP is an open standard network protocol for message-oriented middleware, that is independent of the utilized programming language. There are a lot of commercial and free server implementations for AMQP and we chose the open-source implementation written in Erlang called RabbitMQ.

Structurally AMQP consists of 3 parts:

  • Exchanges receive messages from external apps ("publisher"), which in our case study are the master data apps.
  • Queues push out the messages. Consumers (in our case the client apps) register to a queue and process the messages on it. If no consumer is subscribed to a queue the messages are buffered.
  • Routes are the connection between exchanges and queues and define a set of rules on which messages are routed from one to the other.

These parts can be configured in a multitude of ways; there's a very good guide on the RabbitMQ tutorial page.

The following diagram depicts the configuration we chose, which will be explained in detail below:

Exchange, routes and queues in an AMQP infastructure

A central exchange of the type headers receives messages, which are routed to the queues based on their attributes. For every data flow of messages from the master data apps to the client apps we create a separate queue and connect them to the central exchange via routes.

The actual setup of this structure and associated functionality that keep the conventions in place are capsuled in a Ruby gem we wrote (AmqpInfrastructure) which we based off of bunny. Our gem provides apps with an abstracted interface to the AMQP infrastructure.

A master data app can publish data changes with the following code:

# provider.rb
class Article < ActiveRecord::Base
  after_update ->(article) { AmqpInfrastructure.publish_update(service: 'article', id: article.id) }
end

This example shows a possible integration with ActiveRecord. AmqpInfrastructure ensures that the messages are sent to the correct exchange and receive the necessary headers:

  • service: contains the name of the service (in this example the service name is equal to the app, but an app isn't limited to a single service)
  • action contains the type of the message (in our example: update)
  • id contains the identifier for the changed resource

In client apps the AmqpInfrastructure gem enables a simplified way to build queues and set routings based on the header conventions:

# consumer.rb
AmqpInfrastructure.listen_on(service: 'article', action: 'update') do |message|
  article_id = message.id
  # handle update for article
end

The processing of messages is done in a separate subscriber process, that can be started via a Rake task. The following diagram depicts the complete path a message can take:

Complete path of an article update through exchange, routes and queues to a consumer in an AMQP infastructure
  • The article master data app sends a change message to the central exchange. The message's headers are:
  • AMQP's header mechanism ensures that the messages are directed to the correct queues; in our example the route matches {service: "article", action: "update" }. The message is routed to the queue ArticleOrder.
  • The subscriber processes for the orders app receive the messages from the queue and can process them.

Other uses for the Messaging-oriented Middleware

Apart from the described case study the middleware can also be used in other ways:

  • Asynchronous code execution inside of a single app: The established structure makes it easy for apps to create internal queues to delay the execution of certain code. This way the cost of setting up additional tools and services like Redis and sidekiq can be evaded. In this scenario the use of a default exchange makes sense, since it can directly route messages to an addressed queue.
  • Parallel code execution: When multiple consumers can listen to the same queue, RabbitMQ ensures that the messages get distributed evenly, which enables a straightforward parallel processing of messages.
  • Splitting apps into smaller parts: The orders app features the possibility for more than one input. For example it doesn't just accept manual entry but also accepts other data sources like electronic data interchange (EDI) or well structured e-mails. These parts could be split off into their own small applications that communicate the incoming orders in well structured AMQP messages.

Pitfalls and Challenges

Adding a component to an existing system is never without side effects. Even when using RabbitMQ there are things to take into account:

An additional component yields more administrative attention for a running system. RabbitMQ must be integrated into the infrastructure; special attention needs to be put into monitoring and a well defined update process.

The messaging-oriented nature of the communication requires a certain finesse when it comes to parsing the domain event of sent messages. The example we portrayed from our case study shows how an article update is distributed by all consumers. But in practice it's pertinent that the consumers parse the messages very thoroughly to ensure they do not do unnecessary work. An example: If a typo is corrected in the article master data the orders app shouldn't be concerned with the update message for that article, whereas a status change for the article would be of importance. Paying attention to these details can lower the load on both ends of the system.

Conclusion

The architecture described enables client apps to reliably listen for changes in master data without influencing the master data apps. Through use of the AmqpInfrastructure gem this all happens without the apps having to know anything about the internal structure of the AMQP. Our experience shows that RabbitMQ and the bunny gem work very reliably. RabbitMQ also boasts a well rounded web-view for administration and load monitoring, where peaks in load can be anticipated and analyzed. In conclusion, AMQP is a great tool to facilitate asynchronous communication in a REST-centered architecture.

Ihr sucht den richtigen Partner für eure digitalen Vorhaben?

Lasst uns reden.