netEvent Overview

NetEvent redefines what a messaging framework can be, setting a new standard for both ease of use and extreme performance. It is the only unified platform delivering request-reply and message queue functionality in a single solution. Its speed surpasses every competing framework by a wide margin, while its design makes distributed and concurrent enterprise software accessible to every engineer. From the beginning, NetEvent was built on one principle: distributed systems should be powerful and easy to create and maintain. Despite its extensive API, most developers only need to learn a small subset before becoming productive.

NetEvent is perfect for new projects, but it also enables a smooth migration path for enterprise systems, replacing internal APIs step by step to modernize an existing architecture. The platform supports both synchronous and reactive approaches. At its core, the NetEvent Router—the backend server handling event routing—is highly configurable and scales from a Raspberry Pi to the largest enterprise servers. Properly configured, a single router can sustain transaction volumes in the millions per second. In one real-world example, a single client thread delivered over 200,000 messages per second (125-byte payloads) through a router running on a 2018 Mac Mini.

Capybara
Hi, I'm Capy, the NetEvent mascot.

What follows is a brief overview of NetEvent and its design goals along with some actual code examples. The project has been under development for the past ten years and is currently funded internally. NetEvent will initially be available for Java and JVM languages such as Scala and Kotlin. JavaScript/TypeScript, Python, C/C++, C# and Golang will follow.

So without further ado, let's get started—and for those of you in a hurry (or in upper-management), you are in luck. Behold! A TL;DR!

NetEvent In a Nutshell

Capybara
Enough of the fluff. Let's go coding!

Examples

NetEvent is based on a language-independent wire protocol (i.e., a low-level protocol that moves data) with the first runtime implementation written in Java. The code is very straightforward and non-opinionated. It was written to be easily translated into in other languages such as JavaScript/TypeScript. NetEvent is a low-level API so it does not interfere with (or require) any other framework. It can be used with Spring/Spring Boot, Jakarta, JEE, etc. but doesn't require any portion of those frameworks. Below you will find the following examples:

Sending a Broadcast Event

The following example is a snippet showing how to broadcast a single event to any listener registered for it. (See Targeted Events Example if you need to explicitly target one or more listeners by domain and user name.) Note the reportEventService magically appears and assumed initialized. We'll show full initialization in another example below.

When an event is dispatched it returns an EventFuture which can be used to detect changes to the event's status using an EventFutureListener. There are about three dozen states an event can exhibit during its lifecycle depending upon the event type. Some of the more important states are listed below. For a complete list see the constant definitions in the EventState class in the API documentation.


    /*
     *  Create a NetEvent to broadcast the current maintenance report for an 
     *  engine. In this simple case the future will be completed when the event
     *  has been sent to the Router—also known as "fire and forget."" (See 
     *  Event Tracking example below to set one or more "tracking waypoints" to
     *  update the future during the event's life cycle.)
     *
     *  Note the EventPath class which is used everywhere in NetEvent to identify
     *  events. In this case the Event Group is "engine" (essentially an event 
     *  category) followed by an event path for a full maintenance report. Event
     *  naming conventions are up to the application developer although they are
     *  similar to REST URLs in that they should identify the event cleanly.
     */
    EventPath maintenanceReportPath = new EventPath("engine:maintenance/report/full");
    NetEvent  newEvent              = new BroadcastEventBuilder(maintenanceReportPath)
                                          .setCompressionType(CompressionType.GZIP)
                                          .setPayload(engineReport)
                                          .build();
    /*
     *  Notes:
     *    - The await() method returns the future (as does broadcast()).
     *    - await() can be called repeatedly even if the future has "completed". 
     *      (In that case it will return the status immediately.) 
     *    - One or more listeners can be attached to the future to allow
     *      processing asynchonously. In this case we assume four seconds
     *      is more than enough time to send the event. In production code, there
     *      would be resend logic in here but this is a short example.
     */
    EventFuture future = reportEventService.broadcast(newEvent)
                                           .await(4000L);    // Times out in 4 seconds

    if (!future.isComplete()) {
        System.out.printf("Engine maintenance report event failed to reach " +
                          "listener(s) after 4 seconds. Error: %s",
                          future.failureMessage());  
    }

Listening for Events

The above example showed how to dispatch an event to any registered listener. Below is a code snippet showing how to register an event listener. In this case we are listening for more than one event but using a single handler which is common for many use cases.


    /*
     *  Create a NetEvent to broadcast the current maintenance report for an 
     *  engine. In this simple case the future will be completed when the event
     *  has been sent to the Router—also known as "fire and forget."" (See 
     *  Event Tracking example below to set one or more "tracking waypoints" to
     *  update the future during the event's life cycle.)
     *
     *  Note the EventPath class which is used everywhere in NetEvent to identify
     *  events. In this case the Event Group is "engine" (essentially an event 
     *  category) followed by an event path for a full maintenance report. Event
     *  naming conventions are up to the application developer although they are
     *  similar to REST URLs in that they should identify the event cleanly.
     */
    EventPath maintenanceReportPath = new EventPath("engine:maintenance/report/full");
    NetEvent  newEvent              = new BroadcastEventBuilder(maintenanceReportPath)
                                          .setCompressionType(CompressionType.GZIP)
                                          .setPayload(engineReport)
                                          .build();
    /*
     *  Notes:
     *    - The await() method returns the future (as does broadcast()).
     *    - await() can be called repeatedly even if the future has "completed". 
     *      (In that case it will return the status immediately.) 
     *    - One or more listeners can be attached to the future to allow
     *      processing asynchonously. In this case we assume four seconds
     *      is more than enough time to send the event. In production code, there
     *      would be resend logic in here but this is a short example.
     */
    EventFuture future = reportEventService.broadcast(newEvent)
                                           .await(4000L);    // Times out in 4 seconds

    if (!future.isComplete()) {
        System.out.printf("Engine maintenance report event failed to reach " +
                          "listener(s) after 4 seconds. Error: %s",
                          future.failureMessage());  
    }

Tracking a Broadcast Event

Event Waypoint Tracking is a very important feature giving NetEvent applications the ability to receive updates on the progress of any event throughout its lifecyle. It is very useful for distributed debugging (there are many features in NetEvent to make debugging in general much easier) but is also the mechanism by which transactional requests can be made.

In this case we are asking the service engine to inform us when it has received the event. This acknowledgement is done by the NetEvent runtime when the event is received by the engine (no intervention is required by the engine.) There are several other event states (mostly for transactional events—TRANSACTION_COMPLETE, or TRANSACTION_ROLLED_BACK for example) for which the service engine is responsible for reporting back to the originating event dispatcher through the Router.

The following example builds upon the previous one by adding the following line to the event builder:

    .setTrackingWayPoints(EventState.CONSUMER_DELIVERED)
        

    /*
     *  Note: Because we are setting the CONSUMER_DELIVERED tracking waypoint, the 
     *        future will not complete until at least one event consumer acknowledges 
     *        delivery.
     */
    EventPath   maintenanceReportPath = new EventPath("engine:maintenance/report/full");
    NetEvent    newEvent              = new BroadcastEventBuilder(maintenanceReportPath)
                                            .setCompressionType(CompressionType.GZIP)
                                            .setPayload(engineReport)
                                            /*
                                             *  Sets one or more tracking waypoints. 
                                             *  (This is a varargs method.)
                                             */
                                            .setTrackingWayPoints(EventState.CONSUMER_DELIVERED)
                                            .build();
    EventFuture future = engineEventService.broadcast(newEvent)
                                           .await(4000L);    // Times out in 4 seconds

    if (!future.isComplete()) {
        System.out.printf("Engine maintenance report event failed to reach " +
                          "listener(s) after 4 seconds. Error: %s",
                          future.failureMessage());  
    }

Targeting Events

When a request or broadcast event is sent, it is targeted at one or more entities or groups. (NetEvent features a fine-grained authorization mechanism for defining and managing authorized entities and groups.) If no NetEventTarget is specified, the event is automatically assumed to target "all registered listeners with the authorization to receive the event." In the example below the code snippet is targeting entities at a single domain for sending a chat message.




    /*
     *  A code snippet to send a chat messages to Bob, Alice and Ted
     *  using a NetEventTarget. First we will start a conversation
     *  by creating an event target to send messages explicitly to
     *  Bob, Alice and Ted. We then register a listener for any chat
     *  message sent our way.
     *
     *  Note this example assumes we have made a connection to the 
     *  Router hosting the "chatService" and the service has been 
     *  started. We also show the registration of the listener for 
     *  any events sent to us from any authorized entity including
     *  Bob, Alice or Ted.
     *  
     */
    NetEventPath   chatEventPath = new NetEventPath("chat:conversation");
    NetEventTarget target        = new NetEventTargetBuilder(chatEventPath)
                                       /*
                                        *  A domain target specifies a 
                                        *  NetEventDomain as the first arg
                                        *  followed by each entity name
                                        *  at the domain to which to send
                                        *  the message. A NetEventTarget
                                        *  can support any number of 
                                        *  domain/entity targets.
                                        */
                                       .addDomainTarget("bigcorp.com", 
                                                        "bob", 
                                                        "alice",
                                                        "ted")
                                       .build(); 
    /*
     *  Add a listener to print out all inbound messages to the console.
     *  This listener is generic and will allow any sender with authorization
     *  to send you chat messages. (Obviously a bit of production coding is
     *  necessary, but you get the idea...)
     */
    chatService.addListener(new NetEventListener(chatEventPath, 
                                                 new NetEventListener()) 
    {
        @Override
        public Object onEvent(Event event, EventService service) {
            if (event.payload() != null) {
                /*
                 *  The payload is auto-deserialized as a String. Just
                 *  print it to the console along with the name of the
                 *  sender.
                 */
                 String.format("%s: %s", 
                               event.dispatchingEntity().name(),
                               event.payload().deserializedPayload())
                 return null;
             }
    }
    /*
     *  Open stdin for CLI input and send messages until "quit" is entered.
     */
    System.out.println("Type 'quit' to exit.");
    try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in))) {   
        while (true) {
            System.out.print(">> ");    // Show prompt
            
            String command = in.readLine(); 
            
            if (command == null || command.trim().isEmpty()) 
                continue;
            command = command.toLowerCase();
            
            if (command.equalsIgnoreCase("quit")) {
                System.out.println("Exiting...");
                return;
            }

        /*
             *  In this case we are going to use a different broadcast() method
             *  signature that will create the corresponding NetEvent for us. We
             *  are also going to process the completion of the event future 
             *  asynchronously by assigning a listener to the future itself.
             */                                          
            EventFuture future = chatService.broadcast(chatEventPath,
                                                       messagePayload,
                                                       target)
                                            .addListener(new EventFutureListener() 
            {
                @Override
                public void operationComplete(EventFuture future) {
                    if (future.isFailed()) {
                        System.out.printf("Failed to reach one or more recipients. Error: %s", 
                                          future.cause());
                    }
                }
            });
        }    // while (true) 
    }

Sending Request Events

NetEvent's Request-Reply mechanism supports both synchronous (blocking) and asynchronous (non-blocking) requests. It is an RPC by any other name and provides automatic generation and handling of idempotency keys and all the scaffolding required to build transactional event services. NetEvent Request-Reply is a direct replacement for HTTP/REST, gRPC and SOAP but with many more built-in features.

Below we show a code snippet of an asynchronous request-reply workflow using NetEvent. Note: Requests can also be made synchronously either by attaching a listener with a specified timeout to the future returned by sendRequest() or by calling sendSynchronousRequest() instead of sendRequest(). In this case we show the async approach.


    /*
     *  A nearly complete example of sending an asynchronous (i.e., non-blocking) request
     *  using a HVAC monitoring and control service to monitor temperature in a specific 
     *  room and in this case to send a request to change the temperature in that room. 
     *
     *  Note: The code snippet below is verbose because it is reasonably close to production 
     *        code but requires more generalization, error handling and removal of many 
     *        of the comments which are used solely to illustrate features of NetEvent.)
     *        example to set the thermostat in cases where the temperature is not 
     *        within a specified range. 
     */

    final String          HVAC_EVENT_SERVICE       = "hvacservice";
    final String          CONTROLLING_DOMAIN       = "hvac";
    final String          BUILDING_ID              = "bld1";
    final String          ROOM_ID                  = "rm202";
    final String          ROUTER_ADDRESS           = "192.168.1.45:4381"; 
    /*
     *  Creates the event path "hvac:monitor/bld1/rm202"
     */
    final EventPath       MONITOR_TEMP_EVENT_PATH  = new EventPath("hvac", String.format("monitor/%s/%s",
                                                                                         BUILDING_ID,
                                                                                         ROOM_ID));
    final EventPath       SET_TEMP_EVENT_PATH      = new EventPath("hvac", String.format("set/%s/%s",
                                                                                         BUILDING_ID,
                                                                                         ROOM_ID));
    final float           ALARM_TEMP_LIMIT_CELCIUS = 25.55f;               // 78 degrees Fahrenheit
    final float           DEFAULT_ROOM_TEMP        = 22.2222f;             // 72 degrees Fahrenheit
    final float           ALLOWABLE_TEMP_DELTA     = 1.0f;                 // Allow +- 1 degree Celcius
    NetEventEntity        user                     = new NetEventEntity("jane_smith", "megacorp.com");
    /*
     *  Just use password instead of X.509 cert for NetEvent AuthenticationRequest
     */
    AuthenticationRequest authRequest              = new AuthenticationRequest(user, 
                                                                               "J23_18^3~463$");
    NetEventService       hvacService              = null;
    /*
     *  The listener to be called when any room temperature event arrives
     *  for Room 212 in Building 1. The listener itself will issue a request
     *  of the same event service to set the temperature to a default value 
     *  if the room temperature is out of a specified range.
     */
    NetEventListener temperatureListener = new NetEventListener() {
        @Override
        public Object onEvent(NetEvent event, ListenerContext lctx) {
            /*
             *  Deserialize the payload which is an encoded MonitorData instance
             *  using a MarshalingBuffer constructor.
             */
            try {
                MonitorData roomData = new MonitorData(MarshallingBuffer
                                                       .getBuffer(event.payload())); 
            }
            catch (Exception e) {
                logger.error(String.format("Error occurred attempting to deserialize " +
                                           "a MonitorData object to monitor room "     +
                                           "temperature for Building %s, Room %s.", 
                                           BUILDING_ID,
                                           ROOM_ID)
                             e);
                return null;
            }
            if (roomData.buildingId() != BUILDING_ID || roomData.roomId() != ROOM_ID) {
                logger.warning(String.format("Incorrect location data sent while listening " +
                                             "for temperature events from Building %s, "     +
                                             "Room %s. Actual room data sent: %s",
                                             BUILDING_ID,
                                             ROOM_ID,
                                             roomData.toString()));
                return null;   // An onEvent() method returns an Object. If non-null, the 
                               // NetEventListener.onEventReturn() listener is called with
                               // the object as a parameter.
            }
            /* 
             *  Update the dashboard display with the current temp and show an 
             *  alert dialog if it is above the maximum allowed.
             */
            updateDisplay(roomData);
            if (roomData.currentTemp() > ALARM_TEMP_LIMIT_CELCIUS) 
                showTempAlert(roomData);
            if (roomData.currentTemp() > (DEFAULT_ROOM_TEMP - ALLOWABLE_TEMP_DELTA) || 
                roomData.currentTemp() < (DEFAULT_ROOM_TEMP + ALLOWABLE_TEMP_DELTA)) 
            {
                /* 
                 *  The temperature is out of the normal operating range for the 
                 *  thermostat. It is possible the thermostat was set incorrectly 
                 *  by another person or program or, it is malfunctioning. 
                 */
                MarshalingBuffer    buffer         = MarshalingBuffer.getBuffer();
                RequestEventBuilder builder        = new RequestEventBuilder();
                NetEventService     service        = lctx.netEventService();
                // 0 for humidity means "don't set"
                MonitorData         requestPayload = new MonitorData(DEFAULT_ROOM_TEMP, 0); 
     
                try {
                    buffer.putMarshalingBuffer(requestPayload.encode());
                    builder.payload(buffer.compact().toArray()); 
                }
                catch (Exception e) {
                    logger.error(String.format("Error occurred attempting to build a request " + 
                                                "event to create a new account. Error: %s", 
                                                e.toString()));
                    return null;
                }
                
                EventFuture future    = null;
                
                try {
                   /*
                    *  Attempt to set the temperature asynchronously (i.e., send 
                    *  the request and set a listener for the event future to log 
                    *  whether the request was successfully fulfilled.)
                    */
                    future = service.sendRequest(builder.build());
                }
                catch (Exception e) {
                    /* 
                     *  Usually a TimeoutException issue when the router is not 
                     *  available for whatever reason.
                     */
                    logger.error(String.format("Error occurred requesting a temperature " +
                                               "change to %3.2f for building %s, room %s.",
                                               DEFAULT_ROOM_TEMP,
                                               BUILDING_ID,
                                               ROOM_ID),
                                 e);
                    return null;
                }
                /* 
                 *  Add a listener to the future to process any errors that occur. 
                 *  The program continues to execute and the event is only logged.
                 *  If this is powering a dashboard display, it should update that 
                 *  here as well.
                 */
                future.addListener(new EventFutureListener() {
                    /* 
                     *  No need to override the other methods in EventFutureListener. 
                     *  We only care if an error occurred.
                     */
                    /Override
                    public void onError(NetEvent event, 
                                        NetEventService service, 
                                        EventFuture future, 
                                        String errorMessage) 
                    {
                        logger.error(String.format("Error occurred requesting a temperature " +
                                                   "change to %3.2f for building %s, room %s.",
                                                   DEFAULT_ROOM_TEMP,
                                                   BUILDING_ID,
                                                   ROOM_ID,
                                                   future()
                                                   .failureMessage() == null ? "<No error message>"
                                                                             : response.future().failureMessage()));
                    }
                });
            }
            return null;
        }
    };
    try {
        /*
         *  Notes: 
         *
         *  1) You can get the underlying NetEventConnection from any  
         *     NetEventService using the {@code connection()} method. 
         *     (Interacting with NetEvent at the connection level is usually 
         *     not required.) Example: 
         *
         *     NetEventConnection connection = hvacService.connection();
         *
         *   2) The following uses the default connection listener which 
         *      will log all connection events/errors. This is distinct from 
         *      an EventListener and only listens for connection-oriented
         *      events which are most often errors and authorization failures.
         */
        hvacService = EventManager.getConnection(ROUTER_ADDRESS, authRequest, null) 
                      .setDescription("Code Example - login for HVAC service")
                      .startEventService(HVAC_SERVICE, 
                                         CONTROLLING_DOMAIN); // Opens the connection 
                                                              // and starts the event 
                                                              // service.
        /*
         *  You can get the underlying NetEventConnection from any 
         *  NetEventService using the {@code connection()} method
         *  Example: 
         *  {@code NetEventConnection connection = hvacService.connection()};
         */
        
    }
    catch (Exception e1) {
        logger.error(String.format("Could not create a NetEventConnection/NetEventService " + 
                                   "for the %s event service. Event %s. Cause: %s",
                                   HVAC_EVENT_SERVICE,
                                   MONITOR_TEMP_EVENT_PATH.fullPath(),
                                   e1.toString()));
        break;
    }
    if (hvacService == null)
        return;     // Nothing to do here. The connection listener 
                    // will have logged any issues
    try {
        /*
         *  Add the listener for the specific temperature readings we are 
         *  looking for. A more sophisticated approach would be to use a 
         *  RequestEvent to tell the event service what room to monitor 
         *  and how often to send events.
         */
        hvacService.addListener(MONITOR_TEMP_EVENT_PATH, user, temperatureListener);
    }
    catch (Exception e2) {
        logger.error(String.format("Could not add listener for Event %s. Cause: %s",
                                   MONITOR_TEMP_EVENT_PATH.fullPath(),
                                   e2.toString()));
    }

Introduction to Service Engines

All events created by NetEvent applications are eventually handled by an EventServiceEngine. Service engines will publish an API using the neteventdoc tool and respond to requests defined by the API and likely dispatch broadcast events as well. The example below shows a weather service engine responding to a request for a daily forecast.

Note: This is for demostration purposes. The code is correct, but there is no actual weather service.


    /*
     *  An example class defining an EventServiceEngine responder
     *  to provide forecasts upon request from a mythical weather service.
     *  Note an event service will usually have many responders to support the
     *  full API it has published.    
     */
    class WeatherServiceEngine {

         // This exists in our imagination...
         private ForecastService forecastService;
         
         // Constructor for our mythical class.
         protected WeatherServiceEngine(ForecastService usForecastService) {
             forecastService = usForecastService;
         };
         
         // Getter for the underlying service.
         protected ForecastService service() {
             return forecastService;
         }

         /*
         *  Runs the weather service. This will run asynchronously as long as
         *  the event service is running.
         *
         *  Throws Exception on any error (connection issues, etc.) In a production
         *  application, error handling/logging would be more robust.
         */
         protected final void runWeatherService() throws Exception {
             /*
              *  Here we are making a connection to the weather service Router 
              *  and starting the weather event service and finally assigning 
              *  a listener/responder for the service to respond to requests
              *  for forecasts from authorized listeners.
              *
              *  getConnection() returns a NetEventConnection upon which you
              *  can start any number of event services with I/O multiplexed 
              *  over the connection by NetEvent. 
              *
              *  The getConnection() method will throw exceptions on error.
              */
             NetEventService weatherService = EventManager
                                              .getConnection("weather.netevent.io",      
                                                             "NetEvent Demo - US Weather Forecast",   
                                                             new AuthenticationRequest("admin", 
                                                                                       "weather-api.com",
                                                                                       "x9iuleifjni234")
                                              /*
                                               *  Note the difference: We are starting a Service 
                                               *  Engine, not opening a service as a client.
                                               * 
                                               *  Usage: startEventServiceEngine(serviceName, 
                                               *                                 domainName)
                                               */
                                              .startEventServiceEngine("usweather", 
                                                                       "weather-api.com");

            if (weatherService == null) {
                throw new Exception("Could not start the weather service.")
            }

            /*
             *  Here we are adding a listener that is the full responder for a
             *  daily forecast. All daily forecasts will be routed through this
             *  handler. If you are processing a large number of requests or the
             *  processing time is excessive, you can define a 
             *  LoadBalancedNetEventListener which takes a single extra argument 
             *  for the number of threads to use for parallel responders. The 
             *  requests for forecasts would be automatically load balanced by
             *  NetEvent. 
             *
             *  It is likely in this case we would use load balanced listeners 
             *  since this would be a commercial service and we'd like to guarantee
             *  response times for paying customers.
             *
             *  This will throw an exception if the connected entity is not 
             *  authorized to process weather forecast requests.
             */
             weatherService.addListener("forecast:zip", 
                                        new NetEventListener() 
             {
                 @Override
                 public Object onEvent(Event event, EventService service) {
                     if (event.payload() != null) {
                         /*
                          *  Note that the 'event' argument is used to build a response. This
                          *  saves the application engineer from filling in boilerplate.
                          */
                         responseEvent = new ResponseEventBuilder(event)
                                             .responseCode(ResponseCode.BAD_REQUEST)
                                             .errorMessage("No payload sent with request event.");
                         service.sendErrorResponse(responseEvent);
                         return null;    // Do nothing, but this should be logged.
                     }
                     
                     /*
                      *  The request payload was automatically deserialized upon receipt but
                      *  is defined by the class as a generic Java Object so it needs to be
                      *  cast to a ForecastRequest.
                      */
                     ForecastRequest request = (ForecastRequest) response
                                                                 .payload()
                                                                 .deserializedData();
                     NetEvent        responseEvent;
     
                     if (request == null) {
                         responseEvent = new ResponseEventBuilder(event)
                                             .responseCode(ResponseCode.BAD_REQUEST)
                                             .errorMessage("ForecastRequest payload could " + 
                                                           "not be deserialized.");
                         service.sendErrorResponse(responseEvent);
                         return null;
                     }

                     ErrorWrapper  wrapper  = new ErrorWrapper();
                     DailyForecast forecast = forecastService.getForecast(request, wrapper);
     
                     /*
                      *  Do error processing if the forecast is not available, etc.
                      */
                     if (forecast == null) {
                         responseEvent = new ResponseEventBuilder(event)
                                             .responseCode(ResponseCode.BAD_REQUEST)
                                             .errorMessage(wrapper.toString());
                         service.sendErrorResponse(responseEvent);
                         return null;
                     }
                     /*
                      *  "Shortcut" available to create a response event and send it with 
                      *   the designated payload. Equivalent to:
                      *
                      *   responseEvent = new ResponseEventBuilder(event)
                      *                       .setPayload(forecast.encode());
                      *   service.sendResponse(responseEvent);
                      */
                     service.sendResponse(event, forecast.encode());  // Response code 
                                                                      // automatically set 
                                                                      // to SUCCESS
                     return null;
                 }
                 /*
                  *  Should also override onError() for a production application. 
                  *   Left out for brevity.
                  */
             });
         }
     }

Putting it All Together - Part I: A Simple Calculator Application

For the last example, we will create a workable application to send a mathematical expression to a calculator service and receive the answer. For example entering (1024 * 4) + (512 / 128) would return 4100. Part 2 below shows how to create the service engine.

Note: This is an actual running application and is included with NetEvent as an example.


    package calculator;

    import java.io.BufferedReader;
    import java.io.InputStreamReader;

    import org.apache.commons.lang3.exception.ExceptionUtils;

    import io.netevent.exception.GetStackTraceException;
    import io.netevent.client.EventManager;
    import io.netevent.client.NetEventService;
    import io.netevent.client.Response;
    import io.netevent.constants.protocol.ResponseCode;
    import io.netevent.constants.shared.Global;
    import io.netevent.event.EventGroupName;
    import io.netevent.event.EventPath;
    import io.netevent.event.NetEvent;
    import io.netevent.event.RequestEventBuilder;
    import io.netevent.identification.EventServiceName;
    import io.netevent.identification.NetEventDomainId;
    import io.netevent.identification.NetEventEntity;
    import io.netevent.security.AuthenticationRequest;

    /**
     *  A (very) simple application implementing a calculator
     *  application that sends arithmetic expressions (for example:
     *  ((18675 * 302)/290)^2) event service engine to evaluate
     *  arithmetic expressions and send the answer in return.
     *  
     *  Evaluates a expression as a mathematical expression and returns the
     *  result as a double. The expression string may contain the following
     *  operators: '*', '/', '+', '-' and '^' for exponentiation. It supports nested
     *  expressions using parentheses.
     *  
     *  Expression associativity for operators is left-to-right and negation is
     *  supported. Operator precedence is ^ followed by *  or /, followed by + or -.
     *  The modulus operator '%' is not supported. A unary '+' operator is not
     *  supported because it is ambiguous, unary '-' is supported (e.g.,
     *  -10 * -10 would produce the answer 100.
     */
    public class CalculatorApp {
        
        private static final EventServiceName EVENT_SERVICE      = new EventServiceName("calculator");
        private static final NetEventDomainId CONTROLLING_DOMAIN = new NetEventDomainId("demo");
        private static final NetEventEntity   ENTITY             = new NetEventEntity("calc-app@demo");
        private static final EventGroupName   EVENT_GROUP        = new EventGroupName("calc");                                     
        private static final String           EVENT_NAME         = "calculate"; 
        private static final EventPath        EVENT_PATH         = new EventPath(EVENT_GROUP,
                                                                                 EVENT_NAME);  
        private static final String           PROMPT             = "Enter Expression>> ";
        
        /**
         *  The application entry point.
         *  
         *  @param args The command line arguments (this program does not require any.)
         */
        public static void main(String[] args) {
            
            NetEventService calculatorService = null;
            
            try {
                /*
                 *  This will search for and load the default NetEvent application
                 *  runtime configuration properties.
                 */
                EventManager.initialize();

                calculatorService = EventManager
                                    .getConnection("localhost",
                                                   Global.DEFAULT_ROUTER_TCP_PORT,
                                                   new AuthenticationRequest(ENTITY,
                                                                             "$7slEaixm8^sd%"),
                                                   null)   // Use default connection listener
                                    .setDescription("Calculator Service")
                                    .startEventService(EVENT_SERVICE, CONTROLLING_DOMAIN);
            }
            catch (Exception e) {
                System.out.printf("Unexpected exception thrown attempting to obtain " + 
                                  "a connection or open an event service. Error: %s%n", 
                                  e);
            }
            if (calculatorService == null) {
                System.out.printf("Could not start the '%s' event service.", 
                                  EVENT_SERVICE);
                return; 
            }
            System.out.println("Type 'quit' to exit.");
            /*
             *  Open stdin for CLI input.
             */
            try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in))) {   
                while (true) {
                    System.out.print(PROMPT);    
                    
                    String command = in.readLine(); 
                    
                    if (command == null || command.trim().isEmpty()) 
                        continue;
                    command = command.toLowerCase();
                    
                    if (command.equalsIgnoreCase("quit")) {
                        System.out.println("Exiting...");
                        quit();
                        return;
                    }
                    
                    try {
                        NetEvent requestEvent = new RequestEventBuilder(EVENT_PATH)
                                                    .payload(command)
                                                    .build();
                        
                        Response response     = calculatorService
                                                .sendSynchronousRequest(requestEvent, 
                                                                        3000L);
                        
                        if (response.responseCode() == ResponseCode.SUCCESS) {
                            System.out.println(response.event().payload().deserializedPayload());
                        }
                        else {
                            System.out.printf("sendSynchronousRequest() call failed. " + 
                                               "Error: %s%n%s%n",
                                               response.cause(),
                                               response.toString(2));
                        }
                    }
                    catch (Exception e) {
                        System.out.printf("Error occurred attempting to dispatch an event " + 
                                          "to the  event service. Error: %s%n",
                                          e);
                    }     
                }
            }
            catch (Exception e) {
                System.out.printf("Unexpected I/O error on input stream. Exiting.%n" + 
                                  "    Error: %s%n"                                  +
                                  "    StackTrace:%n%s%n",
                                  e,
                                  ExceptionUtils.getStackTrace(e));
                quit();
            }
        }
        
        /**
         *  Shuts down the {@link EventManager} and quits the calculator application.
         */
        public static void quit() {
            try {
                EventManager.shutdownGracefully();  
            }
            catch (Exception e) {
                System.err.printf("Unexpected error occurred attempting to " +
                                   "shut down the EventManager. Stack Trace:%n%s",
                                   ExceptionUtils
                                   .getStackTrace(new GetStackTraceException("quit(String, Throwable)")));
            }        
            System.exit(0);
        }
    }

Putting it All Together - Part II: The Calculator Service Engine

Above we showed the front-end application to make requests of the calculator service. Below is the implementation of that service.


    package calculator;

    import java.io.BufferedReader;
    import java.io.InputStreamReader;

    import io.netevent.constants.protocol.ResponseCode;
    import org.apache.commons.lang3.exception.ExceptionUtils;

    import io.netevent.client.EventManager;
    import io.netevent.client.ListenerContext;
    import io.netevent.client.NetEventListener;
    import io.netevent.client.NetEventListenerContext;
    import io.netevent.client.serviceengine.EventServiceEngine;
    import io.netevent.constants.shared.Global;
    import io.netevent.event.EventGroupName;
    import io.netevent.event.EventPath;
    import io.netevent.event.NetEvent;
    import io.netevent.event.ResponseEventBuilder;
    import io.netevent.identification.EventServiceName;
    import io.netevent.identification.NetEventDomainId;
    import io.netevent.identification.NetEventEntity;
    import io.netevent.security.AuthenticationRequest;
    import io.netevent.util.base.NetEventUtils;

    /**
     *  Implements a service engine that takes a string arithmetic expression
     *  (for example {@code "4 * (1024 * 1024"}) and returns the answer or 
     *  an error if the expression was invalid.
     */
    public class CalculatorServiceEngine {
        
        private static final EventServiceName        EVENT_SERVICE      = new EventServiceName("calculator");
        private static final NetEventDomainId        CONTROLLING_DOMAIN = new NetEventDomainId("demo");
        private static final NetEventEntity          ENTITY             = new NetEventEntity("calc-service@demo");
        private static final EventGroupName          EVENT_GROUP        = new EventGroupName("calc");                                     
        private static final String                  EVENT_NAME         = "calculate"; 
        private static final EventPath               EVENT_PATH         = new EventPath(EVENT_GROUP,
                                                                                        EVENT_NAME); 
        /*
         *  Just use a password. Otherwise we use an X.509 cert or a public 
         *  depending upon how authentication is set up for the Router. In
         *  this case the Router will accept user name/password credentials.
         */
        private static final AuthenticationRequest   CREDENTIALS        = new AuthenticationRequest(ENTITY, 
                                                                                                    "#alkU4$%sk4");
        private static final String                  PROMPT             = "Calulator Service Engine ('quit' to exit)>> ";
        private static       EventServiceEngine    eventService;
        
        /*
         *  Define the endpoint listener to respond to "calc:calculate" 
         *  request events.
         */
        private static final NetEventListener        endpoint = new NetEventListener() {
            @Override
            public Object onEvent(NetEvent event, ListenerContext lctx) {
                try {
                    /*
                     *  String payloads are automatically deserialized so just get the
                     *  deserialized payload which contains the arithmetic expression
                     *  to evaluate.
                     */
                    String       expression   = (String) event.deserializedPayload();
                    ResponseCode responseCode = ResponseCode.SUCCESS;
                    double       answer;
                    String       returnValue;

                    try {
                        /*
                         *  Evaluate the arithmetic expression string.
                         *  Example: "(1024 * 1024) * 8"
                         */
                        answer      = NetEventUtils.evaluateExpression(expression);
                        returnValue = Double.toString(answer);
                    }
                    catch (Exception e) {
                        returnValue = String.format("Could not evaluate expression " +
                                                    "'%s'. Error: %s%n", 
                                                    expression,
                                                    e);
                        responseCode = ResponseCode.BAD_REQUEST;
                    }
                    
                    NetEvent response = new ResponseEventBuilder(event.header())
                                            .payload(returnValue)
                                            .responseCode(responseCode)
                                            .build();
                    
                    lctx.netEventService().sendResponse(response);
                }
                catch (Exception e) {
                    System.out.printf("Unexpected error responding to request. " + 
                                      "Error: %s%n", 
                                      e);
                }
                /*
                 *  If the return value is non-null, the NetEventListener::onEventReturn() 
                 *  method is called with the returned object.
                 */
                return null;       
            }
        };
        
        /**
         *  The entry point for the calculator service engine. Runs a CLI which
         *  can only take 'quit' or 'exit' as arguments.
         *  
         *  @param args The command line arguments. (This program does 
         *              not require any.)
         */
        public static void main(String[] args)  {   
            
            try {
                EventManager.initialize();

                eventService = EventManager
                               .getConnection("localhost", 
                                              Global.DEFAULT_ROUTER_TCP_PORT, 
                                              CREDENTIALS,
                                              null)  // Use default connection listener
                               .setDescription("Calculator Service Engine")
                               .startEventServiceEngine(EVENT_SERVICE, CONTROLLING_DOMAIN);   
            }
            catch (Exception e) {
                System.out.printf("Unexpected exception thrown attempting to obtain " + 
                                  "a connection or open an event service. Error: %s%n", 
                                  e);
                System.exit(1);
                return;
            }
            if (eventService == null) {
                System.out.printf("Could not get connection for NetEventService '%s'.%n",
                                  EVENT_SERVICE);
                System.exit(1);
                return;
            }
                
            /*
             *  Add the endpoint listener for the "calc:calculator" request event. 
             *  (This is a synchronous call and blocks until the listener is 
             *  registered with the NetEvent Router or an error occurs.) 
             */
            NetEventListenerContext lctx = null;
            
            try {
                lctx = (NetEventListenerContext) eventService.addListener(EVENT_PATH, 
                                                                          endpoint);
            }
            catch (Exception e) {
                System.out.printf("Could not add listener for event '%s'. Error: %s", 
                                  EVENT_PATH,
                                  e);
                System.exit(1);
                return;
            }
            if (lctx == null) {
                /* 
                 *  If this was caused by a RouterSecurityException, the toString() 
                 *  method will return a string consisting of the error as well 
                 *  as any message sent from the NetEvent Router. 
                 */
                System.out.printf("Error occurred attempting to add a listener to " + 
                                  "the '%s' event service. Unexpected null return.",
                                  EVENT_SERVICE.name());
                System.exit(1);
                return;
            } 
            /*
             *  Note at this point the NetEventLoop thread is running and will process 
             *  events until it is explicitly shut down.
             */
            try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in))) {   
                System.out.println("Enter 'quit' to exit.");
                while (true) {
                    System.out.print(PROMPT);    
                    
                    String command = in.readLine(); 
                    
                    if (command == null || command.trim().isEmpty()) 
                        continue;
                    command = command.toLowerCase();
                    
                    if (command.equals("quit")) {
                        System.out.println("Exiting...");
                        EventManager.shutdown();
                        System.exit(0);
                        return;
                    }
                    System.out.printf("Invalid command '%s'. Only 'quit' is valid.%n",
                                      command);
                }
            }
            catch (Exception e) {
                System.out.printf("Unexpected I/O error on input stream. Exiting.%n" + 
                                  "    Error: %s%n"                                  +
                                  "    StackTrace:%n%s%n",
                                  e,
                                  ExceptionUtils.getStackTrace(e));
            }
        }      
    }
Capybara
You've seen the tip of the NetEvent Iceberg.
I Hope you enjoyed the tour!

NetEvent will be available for download in the fourth quarter of 2026. The company will seek external funding very soon. If you are interested in participating in the upcoming round, contact us at info@netevent.io.