Communication patterns

Calling remote services which runs on JLupin Platform can be done using two main protocol types: NATIVE and HTTP. Both of them are translated into proper JAVA objects and passed into method invocation.

NATIVE communication

NATIVE communication uses JLupin Remote Method Call interface and is performed through built-in JLupin's Software Load Balancers.

JLupin Remote Method Call - JLRMC (with built-in load balancers) interfaces allows users to transparently call remote services through proxy objects which implements intended service interface. In this way nothing changes from developer's point of view, because (s)he still uses just an interface. JLRMC is using it's own protocol (IT IS NOT A STANDARD JAVA RMI MECHANISM). There are producer (JLupinProxyObjectProducer) available to create these proxy stubs (please refer to JLupin Javadoc):

  • JLupinRemoteProxyObjectSupportsExceptionProducerImpl

JLupinProxyObjectProducer exposes produceObject methods which returns object implementing remote service interface. Their constructors and methods are described in JLupin Javadoc section. But every proxy object producers required JLupinDelegator which is responsible for actual executing request on proper server. JLupin delegators have built-in software load balancers. Proper delegator can be created with JLupinClientUtil class.

There are two load balancer types: - INNER_MICROSERVICE for communication between JLupin microservices - OUTER_CLIENT for communication between external clients (Tomcat, Jetty, other Java application servers) and JLupin microservices.

The detailed description of particular balancers is below. In all code snippets below the same interface is used as an example:

public interface ExampleService {
    Integer exampleMethod(Integer i);
}

And its implementation:

public class ExampleServiceImpl implements ExampleService {
    @Override
    public Integer exampleMethod(Integer i) {
        return 2 * i;
    }
}

Inner microservice communication

Inner microservice communication is defined as a communication between two microservices running on JLupin Platform (both are managed by Main Server).

A native microservice calls a native microservice

Communication diagram for single node architecture communication, "Microservice A" is calling "Microservice B":

Figure 1. Inner communication - a native microservice calls other native microservice. Single node.

Communication diagram for multi node architecture communication, "Microservice A" is calling "Microservice B":

Figure 2. Inner communication - a native microservice calls other native microservice. Multi node.

As you can see that call from "Microservice A" is load balanced to proper instance of "Microservice B".

JLupin Platform automatically updates microservice's service repository (you can read more about service repository here). When call is executed load balancer checks it service repository and decide where to make the request through JLRMC port. To enable such communication, first you must create JLupinDelegator.

Let's see how it works in code.

Inside "Microservice A" you should create one JLupinDelegator and share it with other objects (like for example proxy objects).

You can use JLupinClientUtil for this task:

package com.example.jlupin.configuration;

@Configuration
@ComponentScan("com.example.jlupin")
public class SampleSpringConfiguration {
    @Bean
    public JLupinDelegator getJLupinDelegator() {
        return JLupinClientUtil.generateInnerMicroserviceLoadBalancerDelegator(PortType.JLRMC);
    }
    ...

When have your JlupinDelegator you can create your proxy object which will be bridge to "Microservice B" (the rest of code class above):

   @Bean(name = "exampleService")
   public ExampleService getExampleService() {
       return JLupinClientUtil.generateRemote(getJLupinDelegator(), "microservice-b", "exampleService", ExampleService.class);
   }
}

And in your service to execute method named exampleMethod with one Integer argument which returns Integer - just use an interface:

public class ExampleServiceInvoker implements ExampleService {
    @Autowired
    private ExampleService exampleService;

    @Override
    public Integer getExampleInteger(Integer i) throws Throwable {
        return exampleService.exampleMethod(i);
    }
}

And that is all. When you call remote method by an interface, built-in JLupin's load balancer is being used in background to find proper microservice and execute your request.

Good practice is to configure JLupinDelegator as an internal bean. Every remote service should also be configured as a bean. It will be easier for example to change local service to a remote proxy.

Serlvet microservice calls native microservice

Communication diagram for single node architecture communication, "WebApp1" is calling "Microservice A":

Figure 3. Inner communication - a servlet microservice calls a native microservice. Single node.

Communication diagram for multi node architecture communication, "WebApp1" is calling "Microservice B":

Figure 4. Inner communication - a servlet microservice calls a native microservice. Multi node.

As you can see that call from "WebApp1" is load balanced to proper instance of "Microservice B".

JLupin Platform automatically updates microservice's service repository (you can read more about service repository here). When call is executed load balancer checks it service repository and decide where to make the request through JLRMC port.

To enable such communication, first you must create JLupinDelegator.

Let's see how it works in code.

Inside "WebApp1" you should create one JLupinDelegator and share it with other objects (like for example proxy objects).

You can use JLupinClientUtil for this task:

package com.example.jlupin.configuration;

@Configuration
@ComponentScan("com.example.jlupin")
public class SampleSpringBootConfiguration {
    @Bean
    public JLupinDelegator getJLupinDelegator() {
        return JLupinClientUtil.generateInnerMicroserviceLoadBalancerDelegator(PortType.JLRMC);
    }
    ...

When have your JlupinDelegator you can create your proxy object which will be bridge to "Microservice B" (the rest of code class above):

   @Bean(name = "exampleService")
   public ExampleService getExampleService() {
       return JLupinClientUtil.generateRemote(getJLupinDelegator(), "microservice-b", "exampleService", ExampleService.class);
   }
}

On client side to execute method named exampleMethod with one Integer argument which returns Integer - just use your interface:

@RestController
public class ExampleServiceInvokerController {
    @Autowired
    private ExampleService exampleService;

    @PostMapper("/example/invoke")
    public Integer getExampleInteger(@RequestBody Integer i) throws Throwable {
        return exampleService.exampleMethod(i);
    }
}

And that is all. When you call remote method by an interface, built-in JLupin's load balancer is being used in background to find proper microservice and execute your request.

Good practice is to configure JLupinDelegator as an internal bean. Every remote service should also be configured as a bean. It will be easier for example to change local service to a remote proxy.

Outer client communication

Outer client type is reserved for external access for microservices running on different infrastructure. For outer client servers list must be provided. This is because load balancers need to know where to connect (where to ask if microservice is available).

Communication diagram for single node architecture communication, "Microservice A" is calling "Microservice B":

Figure 5. Outer client calling other inner microservice. Single node.

Main servers list for this configuration should be:

final JLupinMainServerInZoneConfiguration[] mainServerInZoneConfigurations = new JLupinMainServerInZoneConfiguration[] {
        new JLupinMainServerInZoneConfiguration("NODE_1", "10.0.0.1", 9090, 9095, 9096, 9097)
};

Communication diagram for multi node architecture communication, "Microservice A" is calling "Microservice B":

Figure 6. Outer client calling other inner microservice. Multi node.

As you can see that call from "Microservice A" is load balanced to proper instance of "Microservice B". Main servers list for this configuration should be:

final JLupinMainServerInZoneConfiguration[] mainServerInZoneConfigurations = new JLupinMainServerInZoneConfiguration[] {
        new JLupinMainServerInZoneConfiguration("NODE_1", "10.0.0.1", 9090, 9095, 9096, 9097),
        new JLupinMainServerInZoneConfiguration("NODE_2", "10.0.0.2", 9090, 9095, 9096, 9097)
};

Communicatation scheme is same as for the inner microservice communication. At first you must create JLupinDelegator. Again JLupinClientUtil is used for this task (different method is called this time):

package com.example.jlupin.configuration;


@Configuration
@ComponentScan("com.example.jlupin")
public class SampleSpringConfiguration {
    private JLupinMainServerInZoneConfiguration[] mainServerInZoneConfigurations = new JLupinMainServerInZoneConfiguration[] {
            new JLupinMainServerInZoneConfiguration("NODE_1", "10.0.0.1", 9090, 9095, 9096, 9097),
            new JLupinMainServerInZoneConfiguration("NODE_2", "10.0.0.2", 9090, 9095, 9096, 9097)
    };

    @Bean
    public JLupinDelegator getJLupinDelegator() {
        JLupinDelegator jLupinDelegator = JLupinClientUtil.generateOuterMicroserviceLoadBalancerDelegator(PortType.JRMC, jLupinMainServerInZoneConfigurations);

        try {
            jLupinDelegator.start();
        } catch (JLupinDelegatorException e) {
           throw new IllegalStateException("can not start jlupin delegator caused by:", e);
        }
        return jLupinDelegator;
    }

    @preDestroy
    public void destroy() {
        getJLupinDelegator.stop();
        JLupinClientUtil.closeResources();
    }

    ...

IMPORTANT: Remember to call method start() on JLupinDelegator on start up to turn on internal load balancer thread. If you forget about it load balancer won't know where to route your executions. Also remember to call method stop() on JLupinDelegator and JLupinClientUtil.closeResources() method when closing your application.

When have your JlupinDelegator you can create your object producer:

    @Bean(name = "exampleService")
    public ExampleService getExampleService() {
        return JLupinClientUtil.generateRemote(getJLupinDelegator(),"example-microservice","exampleService", ExampleService.class);
    }
}

On client side to execute method named exampleMethod with one Integer argument which returns Integer - just use your interface:

public class ExampleServiceInvoker implements ExampleService {

    @Autowired
    private exampleService exampleService;


    @Override
    public Integer getExampleInteger(Integer i) throws Throwable {
        return exampleService.exampleMethod(i);
    }
}

Good practice is to configure JLupinDelegator as an internal bean. Also every remote service should be configured as a bean. It will be easier for example to change local service to the remote proxy.

HTTP communication

HTTP communication to native microservices is performed through HTTP Elastic API, see this chapter to get more details.

ELASTIC API as well as HTTP communication to servlet microservices (user defined API) is accessible through JLupin Edge Balance, see how it works.