DZone
Java Zone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Java Zone > How to Leverage Method Chaining To Add Smart Message Routing in Java

How to Leverage Method Chaining To Add Smart Message Routing in Java

This article explores how to use method chaining to add routing information to serialized data structures in a lightweight fashion, where the routing information can be read separately by an intelligent message router.

Rob Austin user avatar by
Rob Austin
·
May. 09, 22 · Java Zone · Tutorial
Like (8)
Save
Tweet
4.28K Views

Join the DZone community and get the full member experience.

Join For Free

We are going to use the open-source serialization library Chronicle Wire. Let's assume we have some data that we want to send to a particular destination; we don't want to pollute our business data structures with the routing information. In the future, the routing information could be removed or changed based on external factors such as system failover or horizontal scaling.  

 Having a separation of routing and business messages is nothing new; after all, JMS has been doing it for years with their createObjectMessage (below): 

Java
 
MessageProducer producer = session.createProducer( destination ); 
ObjectMessage message = session.createObjectMessage( obj ); 
producer.send( message );

However, what is different here, is that the routing information is serialized alongside but separate from the business message and away from the messaging layer, where this data structure can later be read and dispatched.

 In this example, we will route a product to a country, 

Java
 
final Routing routing = wire.methodWriter(Routing.class);
routing.to("Italy").product(new Product("Coffee"));
routing.to("France").product(new Product("Cheese"));
routing.to("America").product(new Product("Popcorn"));

You can see the full code example here - MessageRoutingExample 

In the rest of this article, we'll go into this code in a bit more detail;  We will start by defining our business message. This business message is a Data-Transfer-Object (DTO); in an Event-Driven-Architecture (EDA), this DTO would be our event; for our simple example, we are going to create an event called Product. 

Java
 
class Product extends SelfDescribingMarshallable {
        String name;
        public Product(String name) {
            this.name = name;
        }
 }

The Product class extends net.openhft.chronicle.wire.SelfDescribingMarshallable; this is the Chronicle-Wires standard base class used for all Data-Transfer-Objects. However, if you don't wish to extend SelfDescribingMarshallable, you could instead implement the marker interface net.openhft.chronicle.wire.Marshallable, which almost does the same thing as SelfDescribingMarshallable apart from providing default toString() equals() and hashCode() implementations.

Now, let's define the Routing interface; this gives us the ability to add destination information; depending on the Product's destination, it will be acted upon by the appropriate handler. You could think of these as regional handlers, each dispatching messages to its target destination.

 For example, if the destination required the message to get routed over the network, the ProductHandler could use TCP/IP to a target host.

Java
 
@FunctionalInterface
interface Routing {
      ProductHandler to(String destination);
}

@FunctionalInterface
interface ProductHandler {
      void product(Product product);
}

To keep our example simple, we will build a HashMap, keyed on the destination, and we will then use a lambda expression that outputs a String for the ProductHandler. 

Java
 
final Map<String, ProductHandler> destinationMap = new HashMap<>();

// add ProductHandler to handle messages routed to each destination
destinationMap.put("Italy", product -> System.out.println("Sends the product to Italy, product=" + product));  

destinationMap.put("France", product -> System.out.println("Sends the product to France, product=" + product));

destinationMap.put("America", product -> System.out.println("Sends the product to America, product=" + product));

Below, we use Chronicle Wire for our serialization.

Java
 
private final Wire wire = new TextWire(Bytes.allocateElasticOnHeap());

The code above will allow us to serialize our data structure into a human-readable text format. If we were to use Java to write the following:

Java
 
final Routing routing = wire.methodWriter(Routing.class);
routing.to("Italy").product(new Product("Coffee"));
routing.to("France").product(new Product("Cheese"));
routing.to("America").product(new Product("Popcorn"));

And then dump the output of the wire, using:

Java
 
System.out.println(wire);

The following will get logged to the console:

Java
 
to: Italy
product: {
  name: Coffee
}
...
to: France
product: {
  name: Cheese
}
...
to: America
product: {
  name: Popcorn
}
...

Above, you can see how easily the data structure can be read and debugged.  

Java
 
 CLASS_ALIASES.addAlias(Product.class, ProductHandler.class);

Using aliases (above) allows Chronicle-Wire serialization to use the short name of the class rather than the entire pathname. Taking this approach helps us reduce the number of bytes used in encoding these classes; it also has the advantage of producing a more concise, less verbose output when viewed as text. 

However, if performance was a concern, we can use a more efficient compact encoding such as:

Java
 
private final Wire wire = new BinaryWire(new HexDumpBytes());

Below is a hex-dump of exactly the same data encoded using binary wire; it's more compact but nowhere near as easy to read. This can be done using

Java
 
System.out.println(wire.bytes().toHexString())

Chronicle Wire will automatically add in the #comment on the right-hand side.

Java
 
21 00 00 00                                     # msg-length
b9 02 74 6f                                     # to: (event)
e5 49 74 61 6c 79                               # Italy
b9 07 70 72 6f 64 75 63 74                      # product: (event)
80 0c                                           # Product
   c4 6e 61 6d 65                                  # name:
   e6 43 6f 66 66 65 65                            # Coffee
22 00 00 00                                     # msg-length
b9 02 74 6f                                     # to: (event)
e6 46 72 61 6e 63 65                            # France
b9 07 70 72 6f 64 75 63 74                      # product: (event)
80 0c                                           # Product
   c4 6e 61 6d 65                                  # name:
   e6 43 68 65 65 73 65                            # Cheese
24 00 00 00                                     # msg-length
b9 02 74 6f                                     # to: (event)
e7 41 6d 65 72 69 63 61                         # America
b9 07 70 72 6f 64 75 63 74                      # product: (event)
80 0d                                           # Product
   c4 6e 61 6d 65                                  # name:
   e7 50 6f 70 63 6f 72 6e                         # Popcorn

Now that we have written data, this could be sent as a streaming event over an Event-Driven-Architecture, for example by using a Chronicle Queue. Then to read these streaming events, we can use a MethodReader. 

Java
 
MethodReader reader = wire.methodReader((Routing) destinationMap::get);

 To continuously process messages using the current thread, the code would look like this:

Java
 
for (; ; ) {
    // true if a message was read
    boolean success = reader.readOne();
}

Or if we only want to run until there were no more messages to read, then the code would look like this:

Java
 
boolean success;
do {
    // true if a message was read
    success = reader.readOne();
} while (success);

Running the code outputs:

Java
 
Sends the product to Italy, product=!Product {
  name: Coffee
}

Sends the product to France, product=!Product {
  name: Cheese
}

Sends the product to America, product=!Product {
  name: Popcorn
}

Summary

All of the above has been built using the open-source product Chronicle-Wire. This article has shown how it is possible to use method chaining to route messages, but this is not the only use-case for method chaining. This technique can also allow other types of metadata to be associated with business events. Other uses for method chaining and associating meta-information include setting a message priority for a priority queue or recording access history. Then, Dispatching events with associated metadata over an event-driven architecture (EDA) framework allows custom lightweight microservices to read and act upon that metadata. 

What Else 

Chronicle-Services takes this concept to the next stage, offering an EDA framework; it also comes with a web gateway that uses a similar principle to provide routing of JSON messages (below) to web-based clients using REST and WebSockets:

Java
 
"to":"websocketClient1","product":{"name":"Coffee"}
"to":"websocketClient2","product":{"name":"Cheese"}
"to":"websocketClient3","product":{"name":"Popcorn"}
Data structure Data transfer object Event-driven architecture Method chaining Smart message Java (programming language) Leverage (statistics)

Published at DZone with permission of Rob Austin. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Take Control of Your Application Security
  • A Developer Evangelist's Thoughts on Angular 2
  • The Engineer’s Guide to Creating a Technical Debt Proposal
  • 3 Pieces of Bad Advice on How to Keep Your IT Job

Comments

Java Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • MVB Program
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo