Binary (native) communication
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. Here we will discuss native protocol.
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":
Communication diagram for multi node architecture communication, "Microservice A" is calling "Microservice B":
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.configuration;
@Configuration
@ComponentScan("com.example")
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.
Servlet microservice calls native microservice
Communication diagram for single node architecture communication, "WebApp1" is calling "Microservice A":
Communication diagram for multi node architecture communication, "WebApp1" is calling "Microservice B":
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.configuration;
@Configuration
@ComponentScan("com.example")
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":
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":
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.configuration;
@Configuration
@ComponentScan("com.example")
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.