DZone
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
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Dust Actors and Large Language Models: An Application
  • Keep Your Application Secrets Secret
  • Techniques You Should Know as a Kafka Streams Developer
  • High-Performance Java Serialization to Different Formats

Trending

  • Automatic Code Transformation With OpenRewrite
  • Start Coding With Google Cloud Workstations
  • Event-Driven Architectures: Designing Scalable and Resilient Cloud Solutions
  • Testing SingleStore's MCP Server
  1. DZone
  2. Software Design and Architecture
  3. Microservices
  4. How to Develop Event-Driven Architectures

How to Develop Event-Driven Architectures

In this tutorial, learn how to use open-source Chronicle Queue and Chronicle Wire to structure applications to use the event-driven architecture (EDA) design pattern.

By 
Rob Austin user avatar
Rob Austin
DZone Core CORE ·
Feb. 09, 22 · Tutorial
Likes (8)
Comment
Save
Tweet
Share
7.3K Views

Join the DZone community and get the full member experience.

Join For Free

Last month, I wrote an article on open-source Chronicle Wire that discusses how we could serialize an application’s state into different message formats.

Now in this article, I'm going to look at how we can use open-source Chronicle Queue and Chronicle Wire to structure applications to use event-driven architecture (EDA). EDA is a design pattern in which decoupled components (often microservices) can asynchronously publish and subscribe to events. 

At a high level, event-driven architectures are usually made up of application components connected via an async message system. The events flow as messages between the application components and these components act independently as they don’t need to know about other components. All a component needs to know is how to process incoming messages and how to send messages upon the completion of business logic. In other words, event-driven architectures are basically fire and forget.

To connect these components, a broker-based message system is often used. However, introducing a broker introduces a degree of additional latency. Instead, if performance is important to the system design, it is preferable to use a point-to-point message bus, as this will offer better latency since it removes the additional network hops required by the broker. Even better, it may be possible to remove the network latency entirely. With modern hardware, we can take advantage of large multi-core machines; applications are able to run multiple processes on a single server, with each component or micro-service bound to a CPU core by utilizing thread affinity and CPU isolation. Additionally, we set up a failover server acting for redundancy. By centralizing the application in this way, we eliminate the overall network overhead, yielding much better end-to-end application performance.

When it comes to choosing the message bus, if the components are written in Java, then the open-source library Chronicle Queue can be selected. This is especially beneficial if these components are on the same host, as Chronicle Queue is a point-to-point messaging layer, which works by writing your events to shared off-heap memory. Chronicle Queue is able to offer messaging performance characteristics that are close to reading and writing from RAM. There is also an extension to Chronicle Queue that allows it to send replicated messages over the network, which is required to support HA/DR and server failover. Even though I love Java, it is good to know that Chronicle Queue also supports other languages such as Python and C++.

Now, let's take a step back and demonstrate how we can start building up an event-driven solution using Chronicle Wire and Chronicle Queue by looking at a very simple example that demonstrates how we can construct a Java method call.

The basic premise here is that information is exchanged (in the form of a method parameter) but, because the EDA design sends asynchronous (fire and forget) events, we must use a void method. For example void print(String message)

Java
 
package net.openhft.chronicle.wire.examples;

public class WireExamples3 {

   interface Printer {
       void print(String message);
   }

   public static void main(String[] args) {
       final Printer consolePrinter = System.out::println;
       consolePrinter.print("hello world");
   }
}


When run, this code will print:

hello world

In summary, the class above calls the standard console implementation of the printer interface. So, in this simple example, we could say that the Java method call is our transport.

Now, what if we were to change this and make Chronicle Wire our transport?

Java
 
package net.openhft.chronicle.wire.examples;

import net.openhft.chronicle.wire.JSONWire;
import net.openhft.chronicle.wire.Wire;

public class WireExamples4 {

   interface Printer {
       void print(String message);
   }

   public static void main(String[] args) {
       Wire wire = new JSONWire();
       final Printer printer = wire.methodWriter(Printer.class);
       printer.print("hello world");    

       wire.methodReader((Printer) System.out::println).readOne();
   }
}


As expected this code prints exactly the same text:

hello world

It takes the method call print(“hello world”) and serializes it to JSON using the Chronicle Wire methodWriter. This JSON is then subsequently read by the methodReader, then the print method is called. We can inspect the payload of Chronicle Wire if we add the following statement:

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


By changing our code to the following...

Java
 
package net.openhft.chronicle.wire.examples;

import net.openhft.chronicle.wire.JSONWire;
import net.openhft.chronicle.wire.Wire;

public class WireExamples4 {

   interface Printer {
       void print(String message);
   }

   public static void main(String[] args) {
       Wire wire = new JSONWire();
       final Printer printer = wire.methodWriter(Printer.class);
       printer.print("hello world");
       System.out.println(wire.bytes());
       wire.methodReader((Printer) System.out::println).readOne();
   }
}


It outputs:

Plain Text
 
  "print":"hello world"


hello world

Where  "print":"hello world" is the serialized form of this method call, it is this information that will be transmitted on our message bus.

So let’s introduce a message bus, and make a remote procedure call (RPC). In other words, make a method call from one Java thread or process to another.

One way we could do this is to use Chronicle Queue, which is built upon the foundation of Chronicle Wire. Chronicle Queue uses Chronicle Wire as its serializer, but instead of writing the serialized event (in this case "print":"hello world") to JSON and storing it in Java memory, it instead writes this event to a memory-mapped file, which can be shared between Java threads and processes.  

Another feature I like is that Chronicle Queue can support multiple processes of writing and reading from the same queue and it stores its data in a compact binary format.

You can see the example below looks very similar to the previous example, but each section of code can be run in its own process. 

Java
 
// first java process

package net.openhft.chronicle.queue.example;

import net.openhft.chronicle.queue.ChronicleQueue;

public class QueueExamples1 {

    public static void main(String[] args) {

        ChronicleQueue queue = ChronicleQueue.single("./myQueueDir");
        Printer printer = queue.methodWriter(Printer.class);
        printer.print("hello world");
    }

    // this interface has to be deployed to both java processes
    interface Printer {
        void print(String message);
    }

}


// second java process

package net.openhft.chronicle.queue.example;

import net.openhft.chronicle.bytes.MethodReader;
import net.openhft.chronicle.queue.ChronicleQueue;

public class QueueExamples2 {

    public static void main(String[] args) {

        ChronicleQueue queue = ChronicleQueue.single("./myQueueDir");
        final MethodReader methodReader = queue.createTailer().methodReader((Printer) System.out::println);

        for (; ; ) {
            final boolean successIfMessageRead = methodReader.readOne();
            Thread.yield();
        }

    }

    // this interface has to be deployed to both java processes
    interface Printer {
        void print(String message);
    }

}


The only shared configuration between the two processes is the chronicle queue directory “./myQueueDir” and the Printer interface.

Several other libraries are using Chronicle Queue internally, such as Chronicle Services or Apache Cassandra, with the latter taking this concept to the next level and offering a production-hardened EDA framework.

Summary

There is more to an event-driven architecture than setting up a few method calls between a few Java processes. Other important considerations are: 

  • Service configuration with the messaging layer, perhaps via a configuration file
  • Configurable redundancy
  • Designating which server is running on which core
  • Choosing your deployment fabric, either as a series of interoperable microservices or as a monolithic application
  • Being able to regain your component state by replaying in the same events, in the same order, is ideal for backtesting and bug fixing
Architecture Open source Java (programming language) application Event-driven architecture

Opinions expressed by DZone contributors are their own.

Related

  • Dust Actors and Large Language Models: An Application
  • Keep Your Application Secrets Secret
  • Techniques You Should Know as a Kafka Streams Developer
  • High-Performance Java Serialization to Different Formats

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!