Snack Vending over TCP – or how Spring Integration saved the day

Stories about project management and coding

Snack Vending over TCP – or how Spring Integration saved the day

Recently I got a new project, create a REST backend to process sales data from snack vending machines. Set up Spring Boot, create some controller, database connector, little admin web interface, Spring Security, done. Sound easy enough, right? Wrong…at least in this story.

Prologue

(if your only interested in the Spring Integration part, skip this section)
When I got the the first few infos about the project I was told the machines send their recent sales data as soon as they start in JSON format.
These vending machines are not your ordinary soda vending machines, they are fitted in coach buses. The buses travel hundreds of kilometers every day, so they have to be smaller and tougher than ordinary stationary machines.
Anyway, the task sounded easy enough, so over the weekend I setup a backend using Spring Boot and wrote the first few controllers and services, the initial models I designed from the bits of documentation I already head (3 pages, mostly filler text).

This week I finally received a test board, basically a Arduino board with a few LEDs to simulate a vending machine (from here forth, this will be called the machines). The firmware on this board is the same as in the controllers in the machines in the buses.
I plugged everything in, configured the machine and fired up my rough draft of a backend and watched as the machine connected. To my great surprise the machine could not connect to the server.

Ok, debugging time! I tested the REST controllers thoroughly with Postman, no issue there. Reset the config, retry, still fail, every message from the machines ended in weird Java exceptions. So I fired up Wireshark to take a closer look at those weird messages. I saw a normal TCP handshake, SYN, ACK, PSH – but then a RST from the client. Why the hell would the client reset the connection? Also, why are there now HTTP headers in these packets? Then I realised something I hoped I’d never have to deal with – these crazy machines actually send the JSON string via TCP socket! To test this I quickly created a TCP server in Java and sure enough, there was the JSON.
At top of that, the implementation was botched, because the client didn’t send any EOL and instead of a clean socket connection teardown it just closed the socket. This just creates the TCP RST. Ah yeah, almost forgot, the JSON string it send was not valid JSON (missing quotes on strings, no comma between objects).
Would I really have to integrate this clusterfuck of a client into my backend?

Spring Integration to the rescue

One of the reasons I like the Spring Framework so much are it’s modules. Most of the tasks you’ll encounter in enterprise software development will already have been done and for most stuff you can find existing patters and solutions. In this case the task was to integrate a low level TCP message protocol into my backend. Spring Integration can handle a lot of different message protocols,  (HTTP, AMQP, JMS, XMPP, SMTP, IMAP, FTP) and extends the inversion of control principle to these services.

The following will describe how to integrate the TCP message protocol into an existing or new Spring Boot application.

Add dependencies

Just add the following dependency to your existing pom.xml.

Most examples you can find for Spring Integration are still using XML for their Spring configuration – we’ll have none of that!
Create a new config class, like this one.

Some interesting notes about this config:

TcpNetServerConnectionFactory Serializer & Deserializer

Sprint Integration has to know some details about the incoming / outgoing TCP stream, most importantly how the end of a transmission will look like. For this you have to configure the correct Serializer/Deserializer:

  • ByteArrayStxEtxSerializer
    <stx>  and terminated by <etx>
  • ByteArrayCrLfSerializer
    data must be terminated by \r\n
  • ByteArrayRawSerializer
    Message termination for assembly purposes is signaled by the client closing the connection. If the client does not correctly teardown the connection, SocketException will be thrown
  • ByteArraySingleTerminatorSerializer
    data must be terminated by a single byte

Port configuration

Happens here, in this case port 8000. This can easily be put in an external config file.

ServiceActivator

Here you’d use your service classes to handle the incoming message, maybe you’d want to use Jackson to convert the JSON to a POJO, like this:

So all that’s left is to write the rest of the business logic and your done.

Tags: , ,

Leave a Reply

Your email address will not be published. Required fields are marked *