Skip to content
Spiderwiz

Spiderwiz

Let the wizard weave your web

Menu
  • About
  • Vision
  • Download
  • Tutorial
  • Javadoc
  • Blog
  • Contact

Lesson 2: Communication – Producers and Consumers

In the previous lesson we set a property in a HelloWorld data object and committed it, then, when handling the event, we retrieved the object’s property and displayed it. That means that we acted as both a Producer and a Consumer of the object. Since the application implemented both roles we did not have to worry about communication.

Obviously, this is not a common scenario. Normally you would have a service-mesh in which some microservices act as Producers and others act as Consumers, communicating over some kind of a communication channel and protocol. Frequently a microservice acts as a producer of some types of data and a consumer of others, but the idea is the same.

In this lesson we keep with the HelloWorld application, but we will have two of them – HelloWorldProducer and HelloWorldConsumer. The first produces and commits the HelloWorld data object, while the second acts upon receiving an object event and displays the greeting set by the producer, as in the previous lesson. We will also learn how to connect the two applications over the network (spoiler: this will not require even a single line of code).

We will have two projects – hello-world-producer and hello-world-consumer. Needless to say, both shall include the Spiderwiz dependency as in lesson 1 (we will not mention it again in this tutorial). Each of the applications has its own Main class. Let’s see first HelloWorldProducerMain.java:

package org.spiderwiz.tutorial.lesson2.producer;

import java.util.List;
import org.spiderwiz.core.DataObject;
import org.spiderwiz.core.Main;
import org.spiderwiz.tutorial.objectLib.HelloWorld;

/**
 * Provides the entry point of the application. Initializes and executes the Spiderwiz framework.
 */
public class HelloWorldProducerMain extends Main{
    private static final String ROOT_DIRECTORY = "";
    private static final String CONF_FILENAME = "hello-world-producer.conf";
    private static final String APP_NAME = "Hello World Producer";
    private static final String APP_VERSION = "Z1.01";  // Version Z1.01: Initial version

    /**
     * Class constructor with constant parameters.
     */
    public HelloWorldProducerMain() {
        super(ROOT_DIRECTORY, CONF_FILENAME, APP_NAME, APP_VERSION);
    }

    /**
     * Application entry point. Instantiate the class, initialize the instance, then call a command-line hook that would shut down
     * the application when "exit" is typed.
     * 
     * @param args the command line arguments. Not used in this application.
     */
    public static void main(String[] args) {
        HelloWorldProducerMain main = new HelloWorldProducerMain();
        if (main.init())
            main.commandLineHook();
    }
    
    /**
     * @return the list of produced objects, in this case HelloWorld is the only one.
     */
    @Override
    protected String[] getProducedObjects() {
        return new String[]{HelloWorld.ObjectCode};
    }

    /**
     * @return the list of consumed objects, in this case we do not consume any so we return an empty list.
     */
    @Override
    protected String[] getConsumedObjects() {
        return new String[]{};
    }

    /**
     * Add HelloWorld class to the object factory list of this application.
     * @param factoryList
     */
    @Override
    protected void populateObjectFactory(List<Class<? extends DataObject>> factoryList) {
        super.populateObjectFactory(factoryList);
        factoryList.add(HelloWorld.class);
    }

    /**
     * Create a HelloWorld object, set its field and commit it.
     */
    @Override
    protected void postStart() {
        try {
            HelloWorld helloWorld = createTopLevelObject(HelloWorld.class, null);
            helloWorld.setSayHello("Hello World");
            helloWorld.commit();
            return;
        } catch (NoSuchFieldException | IllegalAccessException ex) {
            ex.printStackTrace();
        }
    }
}

Beyond the obvious changes in APP_NAME and CONF_FILENAME, you can see (line 40) that the application is still a producer of HelloWorld objects. However in getConsumedObjects() (line 48) we return an empty list because the application consumes nothing. The rest of the code is identical to the code of the previous lesson.

The sharp-eyed among you may notice another small change. While in the previous lesson HelloWorld class was defined in the same package as HelloWorldMain, here we import it from org.spiderwiz.tutorial.objectLib (line 6). The reason is that from now on we will have multiple projects sharing the same data object classes therefore we have created a class library that can be shared by other projects – producers and consumers alike. We store HeloWorld in that library so that it can be used also by the other project that we are going to build in this lesson – hello-world-consumer. Before we start with it let’s first look at the new HelloWorld class, as it differs a bit from the previous lesson.

package org.spiderwiz.tutorial.objectLib;

import org.spiderwiz.annotation.WizField;
import org.spiderwiz.core.DataObject;

/**
 * Implements HelloWorld data object.
 */
public class HelloWorld extends DataObject{

    /**
     * Mandatory public static field for all data objects.
     */
    public final static String ObjectCode = "HLWRLD";
    
    @WizField private String sayHello;

    public String getSayHello() {
        return sayHello;
    }

    public void setSayHello(String sayHello) {
        this.sayHello = sayHello;
    }

    /**
     * @return null as this is a root object.
     */
    @Override
    protected String getParentCode() {
        return null;
    }

    /**
     * @return true as this object is disposable.
     */
    @Override
    protected boolean isDisposable() {
        return false;
    }
}

There are two changes from the HelloWorld class of the previous lesson. First, we do not override onEvent(). The reason is that only the consumer needs to implement this method, while classes defined in objectLib library are used by the producer and the consume alike.

The other change is the value returned by isDisposable() (38). This was modified from true to false. This is because we are going to have two applications that communicate, and obviously we cannot guarantee that they will start simultaneously. We also do not want to mess up with synchronization of the “hello world” greeting until the two applications connect. The solution is the Spiderwiz way. Right after the producer starts and initializes, it creates a HelloWorld object and commits it. From now on, since the object is not disposable, it is conceptually “in the space”. Once the consumer starts and connects to the producer, it will encounter the event that is fired by the object.

In order to handle HelloWorld events, hello-world-consumer defines (in its own package) a new class – HelloWorldConsumer that extends HelloWorld. Here it is:

package org.spiderwiz.tutorial.lesson2.consumer;

import org.spiderwiz.tutorial.objectLib.HelloWorld;

/**
 * Extends HelloWorld and implement consumer code in onEvent().
 */
public class HelloWorldConsumer extends HelloWorld {

    /**
     * Do the consumer work.
     * @return true to indicate that the event has been handled.
     */
    @Override
    protected boolean onEvent() {
        System.out.println(getSayHello());
        return true;
    }
}

The extension adds onEvent() to the base HelloWorld class. The method does exactly what it did in lesson 1.

We still need to see how the hello-world-consumer project implements HelloWorldConsumerMain. Here it goes:

package org.spiderwiz.tutorial.lesson2.consumer;

import java.util.List;
import org.spiderwiz.core.DataObject;
import org.spiderwiz.core.Main;
import org.spiderwiz.tutorial.objectLib.HelloWorld;

/**
 * Provides the entry point of the application. Initializes and executes the Spiderwiz framework.
 */
public class HelloWorldConsumerMain extends Main{
    private static final String ROOT_DIRECTORY = "";
    private static final String CONF_FILENAME = "hello-world-consumer.conf";
    private static final String APP_NAME = "Hello World Consumer";
    private static final String APP_VERSION = "Z1.01";  // Version Z1.01: Initial version

    /**
     * Class constructor with constant parameters.
     */
    public HelloWorldConsumerMain() {
        super(ROOT_DIRECTORY, CONF_FILENAME, APP_NAME, APP_VERSION);
    }

    /**
     * Application entry point. Instantiate the class, initialize the instance, then call a command-line hook that would shut down
     * the application when "exit" is typed.
     * 
     * @param args the command line arguments. Not used in this application.
     */
    public static void main(String[] args) {
        HelloWorldConsumerMain main = new HelloWorldConsumerMain();
        if (main.init())
            main.commandLineHook();
    }
    
    /**
     * @return an empty list as we produce nothing.
     */
    @Override
    protected String[] getProducedObjects() {
        return new String[]{};
    }

    /**
     * @return the list of consumed objects, in this case HelloWorld is the only one.
     */
    @Override
    protected String[] getConsumedObjects() {
        return new String[]{HelloWorld.ObjectCode};
    }

    /**
     * Add HelloWorldConsumer class to the object factory list of this application.
     * @param factoryList
     */
    @Override
    protected void populateObjectFactory(List<Class<? extends DataObject>> factoryList) {
        super.populateObjectFactory(factoryList);
        factoryList.add(HelloWorldConsumer.class);
    }
}

As expected, here getProducedObjects() (line 40) and getConsumedObjects() (line 48) reverse their roles compared to HelloWorldProducerMain – we produce nothing and consume HelloWorld.

There is one more crucial discrepancy. In populateObjectFactory() (line 57) we register the HelloWorldConsumer extension class rather than HelloWorld. This ensures that whenever a HelloWorld object is received it will be handled by an instance of HelloWorldConsumer and its onEvent() method will be activated.

We are done with the code. Now the big question is how do the two applications communicate. The answer is: through the magic of Spiderwiz – two lines that we add to the configuration files – one per application. Let’s see that.

With the current Spiderwiz version we have two ways to connect the applications – TCP/IP sockets and WebSockets. Other methods can be implemented as plugins, as we will see later in this tutorial. In this lesson we will use TCP/IP (WebSockets are used in the next lesson). In Java, an implementation of TCP/IP connection involves two classes – Socket and ServerSocket. We do not need to use them here since they are already built into the Spiderwiz framework, but we need to configure their use.

Let’s assume the hello-world-producer project is the server. A TCP/IP server requires the allocation of a port number – ours will be 31415. Here is hello-world-producer.conf:

[log folder]/tests/HelloWorldProducer/Logs
[producer server-1]port=31415

The first line is a definition of a log folder as in lesson 1. The second line is what interests us now. We set up a communication server by using the producer server-n property, when n is any number between 1 and 99 that is unique across all producer servers defined in the configuration file. We can define as many as 99 producer servers and 99 consumer servers for a single application. There is no material difference between producer servers and consumer servers. Both can be used for both produced objects and consumed objects. The choice is mainly cosmetic for the clarity of the network topology – applications that mostly produce objects will be normally set as producers, while applications that mostly consume objects will be normally set as consumers. It is very important, however, that a consumer client always connects to a producer server and vice versa because the hand shaking mechanism depends on this relationship.

Our application defines producer server-1 property. A TCP/IP server is the default for Spiderwiz servers, so all we need to do is to specify the port number served by this server, 31415 in our case.

It is left for us to set hello-world-consumer.conf and then we will be ready to run:

[log folder]/tests/HelloWorldConsumer/Logs
[consumer-1]ip=localhost;port=31415

Here we configure a TCP/IP client. Similarly to servers, we can have up to 99 producer clients and 99 consumer clients for a single application, using the properties producer-n and consumer-n. Note that the value of n has no meaning except the unique identification of the property across the configuration file. Specifically there is no need to match the number of the client to the number of the server. They find each other by the IP address and the port number. However, as mentioned above, consumer clients connect to producer servers and vice versa.

Our consumer application defines consumer-1 property. Again, TCP/IP is the default for Spiderwiz clients, so we just need to specify a server address and a port number. We use localhost and 31415.

Everything is ready. The following table shows what happens when we run and stop the two applications few times in some occasional order:

OperationProducer ConsoleConsumer Console
Run ProducerHello World Producer ver. Z1.01 (core version Z2.30) has been initiated successfully
Include spiderwiz-admin.jar in your project in order to use www.spiderwiz.org/SpiderAdmin
Producer is listening to consumers on port 31415
Run ConsumerA connection from 127.0.0.1 has been establishedHello World Consumer ver. Z1.01 (core version Z2.30) has been initiated successfully
Include spiderwiz-admin.jar in your project in order to use www.spiderwiz.org/SpiderAdmin
A connection from localhost:31415 has been established
Hello World
Exit ConsumerA connection from 127.0.0.1 has been dropped. Reason: Channel closed by other peerexit
A connection from localhost:31415 has been dropped. Reason: Channel closed
Hello World Consumer undeployed
Exit Producerexit
Producer stopped listening to consumers on port 31415
Hello World Producer undeployed
Run ConsumerHello World Consumer ver. Z1.01 (core version Z2.30) has been initiated successfully
Include spiderwiz-admin.jar in your project in order to use www.spiderwiz.org/SpiderAdmin
Connection to localhost:31415 failed on java.net.ConnectException: Connection refused: connect
Run producerHello World Producer ver. Z1.01 (core version Z2.30) has been initiated successfully
Include spiderwiz-admin.jar in your project in order to use www.spiderwiz.org/SpiderAdmin
Producer is listening to consumers on port 31415
After about one minuteA connection from 127.0.0.1 has been establishedA connection from localhost:31415 has been established
Hello World

These logs are quite straightforward but there are few points to notice.

First, you see that hello-world-consumer prints Hello World regardless of whether the producer or the consumer was launched first. You can also see that this is prompted every time a connection between them is established, even if this happens more than once during application lifetime.

You can also see the client-server mechanism applied by Spiderwiz. If a server is available when the client is launched, connection is established immediately. If a server is not available when a client is trying to connect to the designated port, connection fails and the client repeats the attempt every set time interval until success. By default this time interval is 1 minute, but it can be configured using the reconnection seconds property in the application configuration file.

Before concluding this lesson let’s revisit the log files that were mentioned in the previous lesson. Besides the main log that was described there, classified by date and hour of the day, you will now see a new subfolder named Consumers under /tests/HelloWorldProducer/Logs and a new subfolder named Producers under /tests/HelloWorldConsumer/Logs. The former will contain a subfolder named “Hello World Consumer.127.0.0.1” and the latter will contain a subfolder named “Hello World Producer.localhost”. In each of these there will be a folder by the date and a file by the hour of the day. The following table compares the contents of these two files:

/tests/HelloWorldProducer/Logs/Consumers/Hello World Consumer.127.0.0.1/20-06-06/pm03.txt/tests/HelloWorldConsumer/Logs/Producers/Hello World Producer.localhost/20-06-06/pm03.txt
15:27:10:580 Received a request from Hello World Consumer running at 127.0.0.1 to reset objects: HLWRLD15:27:13:498 $HLWRLD counter has been reset to zero. Previous value was 0

These log lines are produced when the two applications connect and the HelloWorld object is sent from the Producer to the Consumer. The log of HelloWorldProducer shows that it received a request from a consumer called Hello World Consumer running on IP 127.0.0.1 to reset the objects whose ObjectCode was HLWRLD, and the log of HelloWorldConsumer shows that it received the first object whose ObjectCode was HLWRLD from a producer called Hello World Producer running on localhost. This is indeed not much information, but this logging sub-system plays a major role when it comes to full-fledged data logging. Spiderwiz can be configured to log every message that passes between applications and then these log files can become pretty fat.

In the next lesson we will learn more about communication and the Spiderwiz network topology with a WebSocket based application hub.

Post navigation

Lesson 1: Hello World
Lesson 3: Going World Wide with a WebSocket Hub

Table of Contents

  • Lesson 1: Hello World
  • Lesson 2: Communication – Producers and Consumers
  • Lesson 3: Going World Wide with a WebSocket Hub
  • Lesson 4: Fine Grained Object Routing
  • Lesson 5: Persistent Data Objects and the Data Object Tree
  • Lesson 6: Walking Through the Data Object Tree
  • Lesson 7: Query Objects
  • Lesson 8: Streaming Queries and Data Object Archiving
  • Lesson 9: Manual Data Object Reset
  • Lesson 10: Asynchronous Event Handling
  • Lesson 11: Import/Export – Interfacing with Legacy Frameworks
  • Lesson 12: Don’t Miss a Bit – Lossless Data Objects
  • Lesson 13: Everything that Makes Maintenance Life Easier
  • Lesson 14: Monitor, Manage, Maintain and Control Your Applications with SpiderAdmin
  • Lesson 15: Writing a Communication Plugin
  • Lesson 16: Writing a Mail Plugin
  • Lesson 17: Writing an Import Handler Plugin
  • Lesson 18: Customizing SpiderAdmin
  • Where do We Go from Here?

Spiderwiz 2025 . Powered by WordPress