Pages

Thursday, March 7, 2019

Build a distributed system with Hardware and SpringBoot using Robo4J framework on Java 11

Img.0: Robo4J Application running on RaspberryPi with Java 11

Following post is specially dedicated to all Spring framework fans and others :). It looks nowadays that basic knowledge of spring framework is necessary in the Java field. 

I recently asked random attendee at the meetup a question: "... how good do you feel with Java language?". He started his answer with a long Spring story about how good he is in developing and "hacking" Spring using various libraries, streams, message queues etc. ... I don't even remember all technologies he listed.  
  In his answer is also one important point. People know and are able to develop some apps with the Spring framework. This also means that if any new framework wants to be used by a bit wider dev. community it should follow the main stream. 
The Spring Framework is inside such main stream. It provides quite easy way how to prototype and use the latest technologies. Companies are putting some effort here ! :)

1. Wiring up Robo4J and Spring Contexts 
Img.1: Spring Framework and Robo4J Framework runtimes

For quite a long Robo4J didn't have it's own spring-plugin. The upcoming version is coming up with "
robo4j-spring-configuration-starter" plugin. The plugin allows to expose the RoboContext to the spring ApplicationContext. 
It means Robo4J context can be easily injected into the appropriated Spring managed component ( @Component, @Service or @Configuration ). The both contexts Spring and Robo4J are separated from each other (see Img.1).
When the robo4j configuration starter plugin has been added to the application classpath it tries to search by default for the descriptor files "robo4jSystem.xml" and "robo4jContext.xml". 
The "robo4jContext.xml" file is mandatory by default
When the file is not present in resources directory the Robo4j exception will be thrown. 
It doesn't make sense to continue without having anything to wire. 

The starter plugin can be added to the build system descriptor file, for example Gradle: 

...
dependencies {
   implementation "com.robo4j:robo4j-spring-configuration-starter"
   implementation "com.robo4j:robo4j-core"
   implementation "com.robo4j:robo4j-hw-rpi"
   implementation "com.robo4j:robo4j-units-rpi"
   implementation "org.springframework.boot:spring-boot-starter"
   implementation "org.springframework.boot:spring-boot-starter-web"
    ...
}
...

2. Starting Robo4J and Spring together
Having a robo4j starter plugin present together with robo4j context descriptor file ("robo4jContext.xml") we are ready to start the Spring application. The plugin will automatically configure appropriate bean. Following output can be observed in the log file:

: Initializing Spring embedded WebApplicationContext
: Root WebApplicationContext: initialization completed in 1024 ms
: Robo4J context will be initiated
: Initializing ExecutorService 'applicationTaskExecutor'
: Tomcat started on port(s): 8080 (http) with context path ''

The log snippet shows that Robo4J context will be initiated, which means all relevant hardware units and units will be configured and started. 

3. Building up distributed system with hardware 
Following section describes how to build a simple distributed system. The distributed system consists from two parts: Robo4J Spring application and pure Robo4J application. The both applications are configured over Robo4J auto-discovery mechanism. The auto discovery mechanism allows automatic Robo4J units configuration but more details are over the scope of this article (see Img.2.)
Img.2.: Example distributed application using Robo4J & Spring with auto-discovery mechanism
3a. Robo4J Spring application 
The Img.2. shows how the spring application has the access to the Rob4J context. The Robo4J context is instantiated and configured by the starter plugin. The rest of spring application is just standard, we create one @RestController and @Service. 

@RestController
public class MessageRestController {

    private final LcdService lcdService;

    @Autowired
    public MessageRestController(LcdService lcdService) {
        this.lcdService = lcdService;
    }

    /**
     * Hello world get
     *
     * @return default message
     */
    @GetMapping("/")
    public String index() {
        return "Hello Robo4J Spring World!";
    }

    /**
     * POST end-point for sending message to the Robo4J context
     *
     * @param message simple DTO object
     * @return message content
     */
    @PostMapping("/lcd")
    public String sendMessage(@RequestBody SimpleMessage message) {
        return lcdService.displayMessage(message);
    }
}

The LcdService provides the access to the Robo4J context and propagates the message deeper to the Robo4J context. Inside the RoboContext is found the appropriate unit (example: RemoteLcdUnit). The unit contains the method that sends the message to the remote Robo4J system over the TCP/IP through the Robo4J Unit.

public class RemoteLcdUnit extends RoboUnit<LcdMessage> {
...
    @Override
    public void onMessage(LcdMessage message) {
        RoboContext discoveredContext = LookupServiceProvider
                    .getDefaultLookupService()
                    .getContext(remoteContext);
        if (discoveredContext != null) {
            discoveredContext
                .getReference(unitName)
                .sendMessage(message);
        }
    }
...
}

3b. Robo4J Application
The remote Robo4J application receives the message, deserializes it and sends it to the consumer unit (AdafruitLcdUnit). This unit is registered inside the Robo4J context. 
The application context descriptor ("robo4jContext.xml") looks very simple as the rest is managed by the framework itself.
See the descriptor file:

<robo4j>
    <roboUnit id="lcd">
        <class>com.robo4j.units.rpi.lcd.AdafruitLcdUnit</class>
        <config name="com.robo4j.root">
            <value name="bus" type="int">1</value>
            <value name="address" type="int">0x20</value>
        </config>
    </roboUnit>
</robo4j>

It uses the hardware abstraction provided by the "robo4J-units-rpi" module. 

4. Running both Applications
We have prepared both parts of our simple distributed system. Let's run them. 
Both of them are using OpenJDK 11! In case of RaspberryPi it's the Liberica JDK 11.0.2. 
The Img.2. shows that the communication with the LCD Hardware is possible through Spring @RestController. The RestController provides the POST method, that accepts JSON format of the SimpleMessage class. 

url: POST http://localhost:8080/lcd
body: {"content" : "Java 11, Robo4J \n SpringBoot"}

5. Conclusion
The described example shows how simple it is to build up and run the distributed system using the real hardware units on pure Java. 
The Robo4J Framework allows a simple connection with the Spring kind of applications. It is not necessary to write any line of code in other than JVM language (Java, Kotlin, Scala, JRuby ;). It is also not necessary to use some difficult data transformations written in C in order to put the sensors outputs on the various message queues. 
Robo4J allows to build an event driven systems that are communicating between them selves by passing messages. 
The example additionally shows that  the final system may be distributed over many different JVMs and Robo4J framework takes care about the communication under the hood. This allows to be fully concentrated on the system logic development. 

The following approach supports, nowadays often discussed, "evolutionary architecture" one, which goal is to propose the way how to utilize a different kind of design patterns and desired technologies. 

Happy Coding!

No comments: