
- Rating:
- Views: 648
- Author: JLupin
- Skill level: medium
- Comments: 0
Overview
In previous tutorial exchange project was created. It was accessed through HTTP protocol with data serialized into JSON format. As an underlying technology Spring REST was used. This tutorial shows how to use JLupin Platform native microservice to make the same requests or how to directly call microservice method with multiple arguments. New microservice will be added (just for this tutorial) called exchange-elastic-api.

Requirements
To complete this tutorial you will need JLupin Platform version 1.5.0.3. You can download it from here. You should also take codes from communication tutorial as they are base for this tutorial (it is not made from scratch).
Configure project
You should already have base structure shown below.
+--common-pojo | | | +--[...] | | | +--pom.xml | +--currency-converter | | | +--additional-files | | | +--implementation | | | | | +--[...] | | | | | +--pom.xml | | | +--interfaces | | | +--[...] | | | +--pom.xml | +--exchange | | | +--additional-files | | | +--implementation | | | +--[...] | | | +--pom.xml | +--exchange-rates | | | +--additional-files | | | +--implementation | | | | | +--[...] | | | | | +--pom.xml | | | +--interfaces | | | +--[...] | | | +--pom.xml | +--pom.xml
Now add structure for new microservice (native type) called exchange-elastic-api. Your structure should look like this:
+--common-pojo | | | +--[...] | | | +--pom.xml | +--currency-converter | | | +--additional-files | | | +--implementation | | | | | +--[...] | | | | | +--pom.xml | | | +--interfaces | | | +--[...] | | | +--pom.xml | +--exchange | | | +--additional-files | | | +--implementation | | | +--[...] | | | +--pom.xml | +--exchange-elastic-api | | | +--additional-files | | | +--implementation | | | | | +--src | | | | | | | +--main | | | | | | | +--java | | | | | +--pom.xml | | | +--interfaces | | | +--src | | | | | +--main | | | | | +--java | | | +--pom.xml | +--exchange-rates | | | +--additional-files | | | +--implementation | | | | | +--[...] | | | | | +--pom.xml | | | +--interfaces | | | +--[...] | | | +--pom.xml | +--pom.xml
Now configure new pom files and modify main project pom by adding new modules to the list.
exchange-elastic-api/implementation/pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <artifactId>exchange-project</artifactId> <groupId>com.example.exchange</groupId> <version>1.0.0</version> <relativePath>./../../pom.xml</relativePath> </parent> <name>exchange-elastic-api</name> <artifactId>exchange-elastic-api-implementation</artifactId> <properties> <jlupin.skipDeploy>false</jlupin.skipDeploy> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.context.version}</version> </dependency> <dependency> <groupId>com.example.exchange</groupId> <artifactId>exchange-elastic-api-interfaces</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>com.example.exchange</groupId> <artifactId>common-pojo</artifactId> <version>1.0.0</version> </dependency> <!-- JLupin dependencies --> <dependency> <groupId>com.jlupin</groupId> <artifactId>jlupin-client-assembly</artifactId> <version>${jlupin.platform.client.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>${log4j.slf4j.bridge.version}</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>com.jlupin</groupId> <artifactId>jlupin-platform-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
exchange-elastic-api/interfaces/pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <artifactId>exchange-project</artifactId> <groupId>com.example.exchange</groupId> <version>1.0.0</version> <relativePath>./../../pom.xml</relativePath> </parent> <artifactId>exchange-elastic-api-interfaces</artifactId> <dependencies> <dependency> <groupId>com.example.exchange</groupId> <artifactId>common-pojo</artifactId> <version>1.0.0</version> </dependency> </dependencies> </project>
Change module section inside pom.xml.
[...] <modules> <module>common-pojo</module> <module>currency-converter/interfaces</module> <module>currency-converter/implementation</module> <module>exchange/implementation</module> <module>exchange-rates/interfaces</module> <module>exchange-rates/implementation</module> <module>exchange-elastic-api/interfaces</module> <module>exchange-elastic-api/implementation</module> </modules> [...]
If you configure everything you are ready to start adding implementation.
Implementation
At first create suitable package structure. In this tutorial com.example.exchange will be used as main package same as in previous tutorial. Recommended structure is presented below.
+--[...] | +--exchange-elastic-api | | | +--additional-files | | | +--implementation | | | | | +--src | | | | | | | +--main | | | | | | | +--java | | | | | | | +--com | | | | | | | +--example | | | | | | | +--exchange | | | | | | | +--bean | | | | | | | | | +--impl | | | | | | | | | +--interfaces | | | | | | | | | +--pojo | | | | | | | +--configuration | | | | | | | +--dao | | | | | | | | | +--impl | | | | | | | | | +--interfaces | | | | | | | | | +--pojo | | | | | | | +--service | | | | | | | +--impl | | | | | +--pom.xml | | | +--interfaces | | | +--src | | | | | +--main | | | | | +--java | | | | | +--com | | | | | +--example | | | | | +--exchange | | | | | +--service | | | | | +--interfaces | | | | | +--pojo | | | +--pom.xml | +--[...]
Now add configuration files. They are same as the one in previous tutorial. You can copy them from below. Remember to put them in implementation module.
package com.example.exchange.configuration; import com.example.exchange.service.interfaces.CurrencyConverterService; import com.jlupin.impl.client.util.JLupinClientUtil; import com.jlupin.interfaces.client.delegator.JLupinDelegator; import com.jlupin.interfaces.common.enums.PortType; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan("com.example.exchange") public class ExchangeElasticApiSpringConfiguration { @Bean public JLupinDelegator getJLupinDelegator() { return JLupinClientUtil.generateInnerMicroserviceLoadBalancerDelegator(PortType.JLRMC); } @Bean(name = "currencyConverterService") public CurrencyConverterService getCurrencyConverterService() { return JLupinClientUtil.generateRemote(getJLupinDelegator(), "currency-converter", CurrencyConverterService.class); } }
package com.example.exchange.configuration; import com.jlupin.impl.container.application.spring.JLupinAbstractSpringApplicationContainer; import com.jlupin.interfaces.configuration.microservice.container.application.JLupinAbstractApplicationContainerProducer; import com.jlupin.interfaces.container.application.JLupinApplicationContainer; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.AbstractApplicationContext; public class ExchangeElasticApiJLupinConfiguration extends JLupinAbstractApplicationContainerProducer { @Override public JLupinApplicationContainer produceJLupinApplicationContainer() { return new JLupinAbstractSpringApplicationContainer() { @Override public AbstractApplicationContext getAbstractApplicationContext() { return new AnnotationConfigApplicationContext(ExchangeElasticApiSpringConfiguration.class); } }; } }
Also update modoule dependencies and add currency-converter-interfaces to pom.xml:
<project> [...] <dependencies> [...] <dependency> <groupId>com.example.exchange</groupId> <artifactId>currency-converter-interfaces</artifactId> <version>1.0.0</version> </dependency> [...] </dependencies> [...] </project>
Configuration is done. Let's implement microservice. It will do the same what exchange microservice. Let's start with copying ConvertIn and ConvertOut classes from exchange microservice to interfaces module. Put them in com.example.exchange.service.pojo package.
package com.example.exchange.service.pojo; import com.example.exchange.pojo.Currency; import java.math.BigDecimal; public class ConvertIn { private BigDecimal value; private Currency currency; public BigDecimal getValue() { return value; } public void setValue(BigDecimal value) { this.value = value; } public Currency getCurrency() { return currency; } public void setCurrency(Currency currency) { this.currency = currency; } }
package com.example.exchange.service.pojo; import com.example.exchange.pojo.Currency; import java.math.BigDecimal; public class ConvertOut { private BigDecimal value; private Currency currency; public BigDecimal getValue() { return value; } public void setValue(BigDecimal value) { this.value = value; } public Currency getCurrency() { return currency; } public void setCurrency(Currency currency) { this.currency = currency; } }
Then create service. It will have one method which accepts ConvertIn object and second one which will accept BigDecimal and Currency parameters. Implementation is shown below. Remember to put interface in interfaces module.
package com.example.exchange.service.interfaces; import com.example.exchange.pojo.Currency; import com.example.exchange.service.pojo.ConvertIn; import com.example.exchange.service.pojo.ConvertOut; import java.math.BigDecimal; import java.util.List; public interface ExchangeService { List<ConvertOut> convertROA(ConvertIn in); List<ConvertOut> convertRMC(BigDecimal value, Currency currency); }
package com.example.exchange.service.impl; import com.example.exchange.pojo.Currency; import com.example.exchange.service.interfaces.CurrencyConverterService; import com.example.exchange.service.interfaces.ExchangeService; import com.example.exchange.service.pojo.ConvertIn; import com.example.exchange.service.pojo.ConvertOut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.LinkedList; import java.util.List; @Service("exchangeService") public class ExchangeServiceImpl implements ExchangeService { @Autowired private CurrencyConverterService currencyConverterService; @Override public List<ConvertOut> convertROA(ConvertIn in) { return convertRMC(in.getValue(), in.getCurrency()); } @Override public List<ConvertOut> convertRMC(BigDecimal value, Currency currency) { List<ConvertOut> result = new LinkedList<>(); for (final Currency curr : Currency.values()) { final ConvertOut out = new ConvertOut(); out.setCurrency(curr); if (curr.equals(currency)) { out.setValue(value); } else { out.setValue(currencyConverterService.convert(value, currency, curr)); } result.add(out); } return result; } }
This two methods are going to return same result but two are created just to show you two ways for calling methods with elastic API. As you can see data for ROA invocation are packed into object. This is because ROA requires method to have only one argument, because body is deserialized into this one method argument (there is no multiple body value in HTTP specification). On the other hand RMC call requires body to be an array. Then it is sliced into proper array elements and they are deserialized to appropriate method argument. The mapping algorithm is simple: first array element is first method argument, second array element is second array argument, and so on.
Remember to enable microservice for remote invocation by modifying configuration file.
package com.example.exchange.configuration; import com.example.exchange.service.interfaces.CurrencyConverterService; import com.jlupin.impl.client.util.JLupinClientUtil; import com.jlupin.interfaces.client.delegator.JLupinDelegator; import com.jlupin.interfaces.common.enums.PortType; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import java.util.ArrayList; import java.util.List; @Configuration @ComponentScan("com.example.exchange") public class ExchangeElasticApiSpringConfiguration { @Bean public JLupinDelegator getJLupinDelegator() { return JLupinClientUtil.generateInnerMicroserviceLoadBalancerDelegator(PortType.JLRMC); } @Bean(name = "currencyConverterService") public CurrencyConverterService getCurrencyConverterService() { return JLupinClientUtil.generateRemote(getJLupinDelegator(), "currency-converter", CurrencyConverterService.class); } @Bean(name = "jLupinRegularExpressionToRemotelyEnabled") public List getRemotelyBeanList() { List<String> list = new ArrayList<>(); list.add("exchangeService"); return list; } }
Ok, implementation is done. Just add configuration files to additional-files directory.
configuration.yml
SERVERS: JLRMC: #JLupin Remote Method Calls Fast Protocol readTimeout: 480000 isWaitForFinishExecuteAllRequests: true waitToShutdownThreadsOnStop: 60000 backlog: 256 receiveBufferSize: 256 isReuseAddress: false threadPoolSize: 128 isLogPeriodicOnDebug: true isDestroyThreadOnTimeout: false threadExecutingTimeOut: 240000 isStartOnMainServerInitialize: true TRANSMISSION: readTimeout: 480000 isWaitForFinishExecuteAllRequests: false waitToShutdownThreadsOnStop: 60000 backlog: 0 receiveBufferSize: 0 isReuseAddress: false threadPoolSize: 8 isLogPeriodicOnDebug: true isDestroyThreadOnTimeout: false threadExecutingTimeOut: 3600000 isStartOnMainServerInitialize: true QUEUE: readTimeout: 480000 isWaitForFinishExecuteAllRequests: true waitToShutdownThreadsOnStop: 60000 backlog: 256 receiveBufferSize: 256 isReuseAddress: false threadPoolSize: 128 isLogPeriodicOnDebug: true isDestroyThreadOnTimeout: false threadExecutingTimeOut: 240000 isStartOnMainServerInitialize: true ENTRY_POINTS: QUEUE: threadAmount: 128 howOftenCheckingServerInMillis: 5000 repeatsAmount: 4 timeToWaitBetweenRepeatProbeInMillis: 1000 TRANSMISSION: MICROSERVICES_GRANT_ACCESS: MICROSERVICES_LIST: #- microserviceName: 'sampleMicroservice' # serviceName: 'sampleServiceName' # methodName: 'sampleMethodName' #- microserviceName: 'sampleMicroservice2' # serviceName: 'sampleServiceName2' # methodName: 'sampleMethodName2' PROPERTIES: platformVersion: '1.5.0.3' #jvmOptions1: '-Xms128M -Xmx256M -agentlib:jdwp=transport=dt_socket,address=12998,server=y,suspend=n' jvmOptions1: '-Xms128M -Xmx256M' #jvmOptions_2 - default the same as jvmOptions_1 #jvmOptions2: '-Xms128M -Xmx256M' externalPort: '8000' version: '1.0.0' switchDelayTime: 0 connectionSocketTimeoutInMillis: 1000 readTimeoutInMillis: 30000 isKeepAlive: false isOOBInline: false isTcpNoDelay: false isReuseAddress: false sendBufferSize: 0 receiveBufferSize: 0 soLinger: 0 trafficClass: 0 #javaExecutablePath: 'c:\\jvm\\bin\\java.exe' #additionalClassPath: 'c:\\temp\\*' isStartOnMainServerInitialize: true priorityStartOnMainServerInitialize: 4 waitForProcessInitResponseTimeInMillis: 90000 waitForProcessStartResponseTimeInMillis: 90000 waitForProcessDestroyResponseTimeInMillis: 30000 isAllFilesToJVMAppClassLoader: false isArchiveOnStart: false startLogMode: INFO isInitErrorCauseWithNetworkInformation: true isJmxEnabled: true jmxOptions: '-Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false' jmxPrimaryPort: -1 jmxSecondaryPort: -1 checkAvailableScript: 'function isAvailable(checkResponseTimeInMillis, jrmcActiveThreads, jrmcMaxThreads, queueActiveThreads, queueMaxThreads, servletActiveThreads, servletMaxThreads, jvmMaxMemoryInBytes, jvmTotalMemoryInBytes, jvmFreeMemoryInBytes, jvmProcessCpuLoadInPercentage, userAvailableFlag) { var isAvailableByUser = Boolean(userAvailableFlag); if(checkResponseTimeInMillis > 20000 || !isAvailableByUser) { return false; } return true; }' APPLICATION: applicationContainerProducerClassName: 'com.example.exchange.configuration.ExchangeElasticApiJLupinConfiguration' INITIALIZING_LOGGER: #directoryPath: '/logs/server' #fileName: 'file_name' fileExtension: 'log' fileSizeInMB: 20 maxFiles: 10 MEMORY_ERRORS: isRestartOnError: true howManyTimes: 4 percentageGrowth: 15 isHeapDump: true THREAD_POOLS: THREAD_POOL_1: size: 8 waitingTimeForTasksCompletionInMillis: 10000 #THREAD_POOL_2: # size: 8 # waitingTimeForTasksCompletionInMillis: 10000
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?> <!-- ===================================================================== --> <!-- --> <!-- Log4j2 Configuration --> <!-- --> <!-- ===================================================================== --> <!-- | For more configuration information and examples see the Apache Log4j2 | website: https://logging.apache.org/log4j/2.x/index.html --> <Configuration status="WARN" dest="errors/exchange-elastic-api_log4j2_status.log"> <!-- Extract log directory and file name into variables --> <Properties> <Property name="logDirectory">../logs/microservice/exchange-elastic-api</Property> <Property name="logFileName">microservice</Property> </Properties> <Appenders> <!-- RollingFileAppender configured to role every day --> <RollingFile name="FILE"> <FileName>${logDirectory}/${logFileName}.log</FileName> <FilePattern>${logDirectory}/${logFileName}.%d{yyyy-MM-dd}.log</FilePattern> <!-- Compress log files to gzip --> <!-- More configuration https://logging.apache.org/log4j/2.x/manual/appenders.html#DefaultRolloverStrategy --> <!-- <FilePattern>/.%d{yyyy-MM-dd}.log.gz</FilePattern> --> <!-- Do not truncate file --> <Append>true</Append> <!-- The default pattern: Date Priority [Category] (Thread) Message\n --> <PatternLayout pattern="%d %-5p [%c] (%t) %m%n"/> <Policies> <!-- Rollover every microservice start - very useful for debugging --> <!-- <OnStartupTriggeringPolicy /> --> <!-- Rollover at the top of each day --> <TimeBasedTriggeringPolicy interval="1" modulate="true"/> <!-- Rollover if file size is greater than 200 MB --> <!-- <SizeBasedTriggeringPolicy size="200 MB"/> --> </Policies> <CreateOnDemand>true</CreateOnDemand> <!-- Keep last 10 log files --> <!-- More configuration https://logging.apache.org/log4j/2.x/manual/appenders.html#DefaultRolloverStrategy --> <!-- <DefaultRolloverStrategy max="10" /> --> </RollingFile> <!-- AsyncAppender for high performance --> <Async name="ASYNC_FILE"> <BufferSize>1000</BufferSize> <AppenderRef ref="FILE"/> </Async> </Appenders> <Loggers> <!-- Setup for root logger with AsyncAppender --> <Root level="info"> <AppenderRef ref="ASYNC_FILE"/> </Root> </Loggers> </Configuration>
Now everything is ready for deploying.
Deploying
Run server (start/start.sh or start/start.cmd in main server directory). Then run maven commands to deploy your microservices:
mvn clean package jlupin-platform:deploy@jlupin-deploy --projects exchange-rates/implementation --also-make mvn clean package jlupin-platform:deploy@jlupin-deploy --projects currency-converter/implementation --also-make mvn clean package jlupin-platform:deploy@jlupin-deploy --projects exchange-elastic-api/implementation --also-make
Check microservice's list to see your microservices (start/control.sh):
./control.sh microservice list
Zone Node Microservice Type Version #Services default NODE_1 currency-converter native 1.0.0 1 default NODE_1 exchange-elastic-api native 1.0.0 1 default NODE_1 queueMicroservice native 1.5.0.3 1 default NODE_1 exchange-rates native 1.0.0 1 default NODE_1 channelMicroservice native 1.5.0.3 1 default NODE_1 currency-converter-gbp native 1.5.0.3 1 default NODE_1 exchange servlet 1.5.0.3 4 default NODE_1 http-session-repository native 1.5.0.2 2 default NODE_1 currency-converter-chf native 1.5.0.3 1 default NODE_1 currency-converter-eur native 1.5.0.3 1
You can see that all microservices are on the list. Now you can call newly created service.
ROA (Remote Object API)
Request with use of ROA is almost the same as the one made to Spring REST implementation. The difference is only in URL address. The URL address structure is simple:
[serverAddress]:[elasticApiPort]/[microserviceName]/[serviceName]/[methodName]
We are going to call convertROA method in exchangeService service in exchange-elastic-api microservice. Server is running on your local machine so its address is localhost and default elastic API port is 8082. So the address we need to call is:
localhost:8082/exchange-elastic-api/exchangeService/convertROA
Input and output objects are serialized with Jackson library. So the input object structure is:
{ "value": number, "currency": string }
Remember to also send content type header and that's all. Try it yourself:
curl -X POST -H "Content-Type: application/json" -d "{\"value\":12.50, \"currency\":\"USD\"}" localhost:8082/exchange-elastic-api/exchangeService/convertROA
As a result you should see:
[{"value":11.17,"currency":"EUR"},{"value":12.50,"currency":"USD"},{"value":9.77,"currency":"GBP"}]
RMC (Remote Method Call)
Request with use of RMC is different. In opposite to ROA and Spring REST called method can have more than 1 argument (arguments don't have to be packed into class) - first element in passed array is mapped to first method argument, second array element is mapped to second argument, and so on. Because server by default is configured to handle requests as ROA, additional header is required to tell it that this request is RMC, not ROA. URL address is build same as the one for ROA, that means:
[serverAddress]:[elasticApiPort]/[microserviceName]/[serviceName]/[methodName]
We are going to call convertRMC method in exchangeService service in exchange-elastic-api microservice. Server is running on your local machine so its address is localhost and default elastic API port is 8082. So the address we need to call is:
localhost:8082/exchange-elastic-api/exchangeService/convertRMC
BigDecimal serialized into json format is a number, and enum serialized to json is enum as a string. Additional header is X-JLNS-Api-Id. You must set it to RMC value to tell server that the call is different than default (set in main.yml server configuration file). You can try it yourself:
curl -X POST -H "Content-Type: application/json" -H "X-JLNS-Api-Id: RMC" -d "[12.50, \"USD\"]" localhost:8082/exchange-elastic-api/exchangeService/convertRMC
As a result you should see:
[{"value":11.17,"currency":"EUR"},{"value":12.5,"currency":"USD"},{"value":9.77,"currency":"GBP"}]
Summary
You can read more about Elastic API in our documentation. In next tutorials for Elastic API will be shown how to use XML as a data format and how serializers can be configured by user.
Done tutorial
You can download project which is result of making this tutorial from GitHub: https://github.com/jlupin/elastic-api-jlp-1503
RATE & DISCUSS (0)