Message modeling is the structuring of information for transfer between applications. Modern software systems rely on effective data transfer between applications, modules, or processes. More than ever before, business and other systems are running in complex cloud infrastructures, often with hybrid or distributed architectures. Effective messaging between elements is essential for the performance, accuracy and security of these systems.
Message structure must be interpretable by both the sender and receiver of a message for it to be usable. That has meant the development of a range of proprietary structures as well as standardised message formats like XML or JSON which better serve interoperability.
With well-established object-oriented languages like Java, message modeling is important for communication within applications as well as across networks and distributed systems. It is also important for parallel programming, allowing the passage of messages between threads. In this article, we’ll look more closely at message modeling, including some specific implementations and practical examples. We’ll also look at how these techniques can best be used with Java.
The ABC of message modeling
Messaging works at a number of levels in software and network systems. Message modeling is a series of techniques used to ensure consistency and effectiveness in these communications.
In the era of the public Internet, we’re all very familiar with the effects of digital messaging. But it is most effective when its end-users have no awareness of how it takes place, nor of any latency or failures. Message protocols like HTTP and SMTP have long formed the bedrock of Internet communications. However, they are now joined by many more (and more secure) protocols for network operations as well as a much more intricate and subtle range of messaging systems that work across as well as within application architectures.
Standards are key to any messaging system. Communication cannot take place unless there is an understood message format that both sender and receiver can interpret in order to produce or process messages. Commonly used message formats include:
- Other custom or binary data
Some message formats are self-defining, meaning the message can be parsed without reference to a predefined model. This generally means that the message includes its own metadata along with the data. XML messages typically work in this way, by encoding properties in an intrinsically readable (and often human-readable) structure. JSON similarly can encode its attribute labels, meaning that such formats are extremely flexible for serialising all manner of digital objects.
JSON libraries to communicate with Java
The Java language was originally devised with platform independence and networking in mind. That means effective messaging is key to its success. It’s no surprise then that Java supports a number of different JSON libraries. A typical setup could use:
- Spring: Spring is a Java application framework leveraging the Inversion of Control (IoC) pattern. IoC is used extensively and depends on the effective dispatch of events and messages, where RabbitMQ makes an ideal partner.
- RabbitMQ: RabbitMQ is a fast, open-source message-broker, widely used for JSON transmission. RabbitMQ originally implemented the AMQP (Advanced Message Queuing Protocol), but its plugin architecture now means it can serve a number of other protocols including HTTP and STOMP.
- AMQP: Advanced Message Queuing Protocol is an application layer protocol that defines message orientation, routing, queuing, resilience and security. It is commonly used for Java applications. However, a key benefit is that, unlike some earlier protocols like JMS (Java Message Service), it is ‘wire-level’, meaning it is not tied to any particular API or implementation.
Implementing message modeling the easy way
To gain a better understanding of message modeling, it’s worthwhile taking a look at some practical implementations.
JSON Simple is a utility for serialising and deserialising JSON for Java applications. The library is very lightweight, with the declared aims being simplicity and ease of use. With the latter in mind, it’s worth pointing out some features: lenient deserialisation means that it won’t break with missing commas or colons. Plus deserialisation interruptions are reported with line numbers, making error tracing easier. Serialising data can be as simple as:
The data can then be written to a file or stream as required for your transmission or storage systems. The JSONObject class used here is an extension of java.util.HashMap, which makes it an easy drop-in for any interfaces using that existing Java class. Similarly, JSON Simple’s JsonArray extends java.util.ArrayList facilitating similar integrations.
JSON Simple features strong typing, meaning data integrity is better guarded. It is not dependent on any other third-party libraries and is suitable for processing any serialisable data into JSON.
The JSON simple library provides an interface to facilitate modelling development.
To create a model you need to create a Java class that implements the “Jsonable” interface. This interface requires the “toJson” method to be implemented in order to serialize everything into a string in json format.
It is advisable to create an “enum” to better define the characteristics of the keys.
It is also possible to create getter and setter methods for fields, constructors and utility methods to facilitate use by the programmer.
Finally, it is possible to further customize the classes by adding metadata useful for carrying out compatibility checks.
Protobuf (or Protocol Buffers) is a platform-neutral mechanism for serialising data. It was originally developed by Google to improve messaging within their own suite of apps and services but is now available as a free, open-source format for general use. It can be used in any situations where XML or JSON may previously have been leveraged – its major claim is that it is smaller and faster than XML. It integrates easily with Java and many other modern languages such as Python, C++, Kotlin, Dart and Ruby.
Protobuf achieves its efficiencies by the use of a definition language, created in .proto files. This allows structuring to be declared only once (unlike the self-defining structure of XML). From there, automatic compilation processes data structures into highly compact units for storage and transmission, which can be quickly parsed by receiving systems.
It is worth mentioning a specific benefit of Protobuf from an implementation point of view: it is both backward and forward-compatible. That means that it works well with legacy structures – a feature not unusual for systems. More unusually however, it claims forward compatibility, meaning that, provided you follow best practices with your .proto definitions, old code will still read new messages, simply ignoring fields that are not compatible. This makes migration much easier.
Java message modeling best practices
While JSON is a highly flexible and convenient format for effective message modeling in Java, it is still important to use it in the right way. Here we offer a few best practices to help keep your JSON usable and extensible:
- Use a single entry point. A root node or top-level object is not required by the standard, but a single entry point makes your data easier to handle.
- Don’t use hyphens in your keys. Some parsers will interpret them as operators and developers will need to escape the keys. Instead, use underscores or camel case.
- Use native datatypes. Ideally, your properties should only use JSON’s native types (string, number, boolean, null, object, array).
- Don’t assume arrays are ordered. The JSON specification states that arrays are ordered, but developers don’t always comply.
- Provide a unique identifier for each object. Just as in databases, a unique ID is important for your entities. If they describe a web resource, you may use the URL as an ID.
- Give users sample metadata. JSON is excellent for flexibility and readability, but large, nested datasets can be hard to navigate. Users will benefit from sample metadata to aid interpretation.
- Use references for attributes. Use a reference to a thing (@thing) rather than a string (“thing”) to preserve the unique identification of entities.
- Use context caching. If you provide a context, set cache-control headers to permit caching. This can significantly improve processing time.
- Nest related objects. If related objects are given inline, they should be nested. For bi-directional relationships, the order of nesting is arbitrary.
In this article we’ve outlined the basics of message modeling, focusing on JSON and its use for communications between Java applications. The techniques here are of course widely extensible and we hope this has provided some useful entry points for messaging in your distributed systems and applications. If you’re interested in seeing how message modeling is used in up-to-date real-world applications, you can discover more with Zucchetti.