Protocol-free Integration with Spring

Amir Vosough
5 min readJul 29, 2020

--

I’ve been reading about Spring Integration for some time now, and I have started shifting my pet project from web towards Messaging. I remember the days when the company share-holders would kill me if I were to do such changes, but nowadays, if you try just a bit harder, a change like this could be rather small.

The number one rule you should always remember is, “your code should not depend on the transfer technology”. But what does that mean? This means anything related to the “transfer technologies” preferably should be (order is important!):

  • Config
  • Metadata (e.x. Annotations)
  • O(1) classes containing all “implementation details” of an infrastructure (I mean, the bigger your domain becomes, the number of these classes should remain the same)

If we do this, we will have an application which could easily switch protocols, and even support multiple protocols. Another advantage that is overlooked these days, we can even have a client library which is maintained by us and will completely decouple our consumers from the technology we use for transfer. Imagine providing an interface for others to call your APIs as if they were calling methods from the same JVM! I will try to explain how to do this with an example application. I will also try to list anti-patterns when explaining parts of the application. I will use “spring-cloud-streams” on the “server side” and will use “spring integration” on the “client side”. This example application will just take an Order from a RabbitMQ topic exchange and will log it in the console.

Update: When I almost finished writing this blog, it suddenly hit me: will our team for example actually use this “client”? The first answer is No, but that could depend also. I think that depends on the communication level between the teams that are implementing the two sides of a “protocol”. If these teams are in active communication and their main goal is to deliver the requirements of the each other, a client would probably slow them down, but if the teams are two completely independent teams in the same company who would rarely communicate with each other, and their applications do not have high dependency, then a client would probably be useful. For example, in an online shop, Order and Inventory probably will not use clients and will just agree with each other to use specific message channels, but Order could probably use a client to notify the user. Anyways, I think it’s still a good idea to show how you can deliver such clients, so I kept the content!

I have put the example project on github. If you clone the project, you will see it consists of 3 different modules:

  • protocol-free-app: the “server application” which listens on a rabbit topic for receiving orders and will log these orders.
  • protocol-free-api: the “client application” which provides API according to our conventions.
  • sample-application: a sample application which uses “protocol-free-api” to call our API to submit an Order to the app every second.

Now let’s take a look at each module to see how we avoided coupling our code to “protocols”.

First stop, the “app” module. This module is using Spring Cloud Streams, and uses java functions to define our business logic. Explaining Spring Cloud Streams is beyond the scope of this blog post, but thanks to the functional model of this library, you can already see that everything that was protocol-specific like the fact that we use RabbitMQ to receive messages, the topic name, and anything else forming a bridge between RabbitMQ topic and our consumer function is “Config”. If you look at the code

@Bean
public Consumer<Order> order() {
return order -> log.info("Order received: {}", order);
}

you will see we are only defining a function which is saying what needs to happen for a given Order, that’s the ideal case, because this could be used with any protocol as long as they provide an instance of an Order. An anti-pattern here could be the usage of Message<Order> (which is more common in spring integration) as this would couple your logic with “Messaging”.

Next module is the protocol-free-api module which uses Spring Integration to take advantage of its “Messaging Gateway”. Spring Integration is the ancestor of Spring Cloud Streams and provides abstractions for concepts defined in Enterprise Integration Patterns. Our main goal is to define an API here which would act as the client for our Order App with minimal code. That’s where you will see I am using @MessagingGateway annotation to define my API. This annotation technically is similar to Spring Data’s @Repository annotation in a way that they both would give you the ability to decouple your code from the infrastructure by letting you define an interface and them providing the implementing bean on runtime using proxies. So, as you can see on the code

@MessagingGateway(name = "OrderGateway")
public interface OrderGateway {

@Gateway(requestChannel = "asyncSubmitOrder.input")
void submitOrder(Order order);
}

our “Gateway” has one method, which will get an Order and using annotations (our second preferred way), we have defined the “Channel” which these Orders should go to.

Now, if you open the configuration class for the API module, you will see the (implicit) channel definition:

@Bean
public IntegrationFlow asyncSubmitOrder(AsyncRabbitTemplate asyncRabbitTemplate) {
return f -> f
.handle(Amqp.asyncOutboundGateway(asyncRabbitTemplate)
.exchangeName("order"));
}

This “Integration Flow” has an input channel which is used by our Gateway to send the Orders to, and then this flow will redirect them towards the RabbitMQ topic “order”.

As you can see, we could provide a fluent API using annotations and configurations and managed to avoid protocol-specific code so far. One of the common anti-patterns happening here is the usage of Rabbit Templates to send the message towards our App. Doing that would couple our code to messaging and specifically RabbitMQ implementation of messaging which is worse!

Last module is the sample-application which shows how easy it is to use our fluent API:

@SpringBootApplication
@EnableProtocolFreeApi
public class SampleApplication {

public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication
.run(SampleApplication.class, args);
OrderGateway orderGateway = applicationContext.getBean(OrderGateway.class);
SecureRandom random = new SecureRandom();
new Timer()
.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
orderGateway.submitOrder(Order.builder()
.orderId(UUID.randomUUID())
.username("amir")
.cost(random.nextInt(1000))
.build());
}
}, 100, 1000);
}
}

We have defined our own EnableProtocolFreeApi annotation which is similar to other Spring’s Enable* annotations. This annotation enables us to inject our API interfaces to any bean on the consumer application. I don’t really need to know anything about our Order exchange, even the name of the exchange is already configured in the API I am using.

One last remark: If you want to have a maintainable “client” jar files, you should use semantic versioning for releasing your client jar files. There are some maven plugins which could help you with that but that is also out of scope of this blog post.

Let me know your thought on this or if you had similar experience with decoupling code from protocol.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response