Deploy Microservices with Function Compute: Visitor Card of Cloud Customer Service
By Du Wan
Alibaba Cloud Function Compute is a fully hosted and serverless running environment that takes away the need to manage infrastructure and enables businesses to focus on software development. You can deploy microservices on Alibaba Cloud with Function Compute; in this article, we will be doing this with the Cloud Customer Service product as an example.
Note: At the time of writing, Cloud Customer Service is only available for Mainland China Alibaba Cloud accounts.
Alibaba Cloud’s Cloud Customer Service is a complete intelligent service system that can be easily integrated into websites, applications, public accounts, and other systems. Cloud Customer Service provides complete hotline and online service functions for users to easily access other systems such as CRM. It dynamically manages the centralized knowledge bases and knowledge documents used by customers and agents. Based on Alibaba Cloud’s intelligent algorithms, chatbots can accurately understand your intention and answer any questions. In addition, it collects and analyzes the data in the customer service center in real time, helping enterprise decision-makers understand the most common issues and service bottlenecks from a global perspective.
As a function of Cloud Customer Service, Visitor Card associates the users of Cloud Customer Service with those in the CRM system to help the customer service personnel understand the customers’ basic information for better support.
The Visitor Card Integration Guide provided by Cloud Customer Service is a web project implemented based on Spring MVC. For users who use Node.js, they can migrate from Java to Function Compute and provide function as a service for core business calls implemented with Node.js.
Challenges
Users have tried to migrate Java by themselves but encountered the following technical challenges:
- JAR provided by Cloud Customer Service is a private Maven warehouse and cannot be accessed by the external network. Therefore, JAR must be copied to Maven for integration.
- How should the Maven plug-in be properly configured to generate a JAR package that is supported by Function Compute?
Dependencies on Local JAR Packages
fccsplatform-common-crypto-1.0.0.20161108.jar is a package in the Maven warehouse in Alibaba Cloud’s internal network. It is only available to external network customers through Cloud Customer Service. The following XML fragment is a common way in which Maven depends on local JAR packages. Because this is not a typical scenario, additional information is required to complete it.
<dependency>
<groupId>com.alipay.fc.csplatform</groupId>
<artifactId>fccsplatform-common-crypto</artifactId>
<version>1.0.0.20161108</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/fccsplatform-common-crypto-1.0.0.20161108.jar</systemPath>
</dependency>
<scope>system</scope>
indicates that the JAR package is searched from the system path rather than the warehouse. This command must be used with <systemPath/>
. <systemPath/>
indicates the actual path of a JAR package. This command is normally used with ${project.basedir}
(namely, it is relative to the project directory).
Encapsulate the Handler function
Spring MVC exposes a service as a Controller. For Function Compute, only a Handler API is required.
/**
* This is the interface for any none event based function handler
*/
public interface StreamRequestHandler { /**
* The interface to handle a function compute invoke request
*
* @param input The function compute input data wrapped in a stream
* @param output The function compute output data wrapped in a stream
* @param context The function compute execution environment context object.
* @throws IOException IOException during I/O handling
*/
void handleRequest(InputStream input, OutputStream output, Context context) throws IOException;
}
Visitor Card services are simple, and only require an encryption API and a decoding API without calling any third-party services.
public class Encryptor implements StreamRequestHandler { private CustomerInfo customerInfo = new CustomerInfo(); public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException {
String cInfo = CharStreams.toString(new InputStreamReader(
input, Charset.defaultCharset()));
try {
output.write(customerInfo.encrypt(cInfo).getBytes(Charset.defaultCharset()));
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
}
}
public class Decryptor implements StreamRequestHandler { private CustomerInfo customerInfo = new CustomerInfo(); @Override
public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException {
String jsonString = CharStreams.toString(new InputStreamReader(
input, Charset.defaultCharset())); JSONObject jsonObject = JSON.parseObject(jsonString);
try {
String cInfo = customerInfo.decrypt(jsonObject.getString("params"), jsonObject.getString("key"));
output.write(cInfo.getBytes(Charset.defaultCharset())); } catch (GeneralSecurityException e) {
e.printStackTrace();
} }
}
The preceding Encryptor.java and decryptor.java implement the StreamRequestHandler API, while the CustomerInfo.java class encapsulates the service logic.
public class CustomerInfo { # Retrieve PUB_KEY from Cloud Customer Service
private static String PUB_KEY = "your PUB_KEY";
public String encrypt(String cInfo) throws GeneralSecurityException, UnsupportedEncodingException {
PublicKey publicKey = getPubKey(PUB_KEY);
Map<String, String> res = CustomerInfoCryptoUtil.encryptByPublicKey(cInfo, publicKey); JSONObject jsonObject = new JSONObject();
jsonObject.put("cinfo", res.get("text"));
jsonObject.put("key", res.get("key")); return jsonObject.toJSONString();
} public String decrypt(String params, String key) throws UnsupportedEncodingException, GeneralSecurityException {
PublicKey publicKey = getPubKey(PUB_KEY);
return CustomerInfoCryptoUtil.decryptByPublicKey(params, key, publicKey);
} private PublicKey getPubKey(String pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64Util.decode(pubKey));
KeyFactory keyFactory;
keyFactory = KeyFactory.getInstance("RSA");
PublicKey key = keyFactory.generatePublic(keySpec);
return key;
}
Package
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/classes/lib</outputDirectory>
<includeScope>compile</includeScope>
</configuration>
</execution>
</executions>
</plugin>
The official Function Compute documentation recommends two packaging methods, namely, maven-assembly-plugin and maven-dependency-plugin.
- The maven-assembly-plugin method extracts all dependent class files and repackages them into a smaller JAR package. However, this may introduce several problems.
- The maven-dependency-plugin method packages all dependent JAR packages to the internal lib/ directory and then packages them as nested JAR packages. During runtime, Function Compute decompresses the top-layer JAR packages.
In this example, maven-assembly-plugin is not feasible because it cannot package the transitive dependency specified by systemPath, resulting in missing classes during the runtime. Therefore, the maven-dependency-plugin method is used instead.
Deploy Fun
Configure the template.yml file on Fun for description, and then run mvn package && fun deploy
.
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
YunkefuSign:
Type: 'Aliyun::Serverless::Service'
Properties:
Description: 'yun ke fu Sign'
Encryptor:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: com.aliyun.fc.Encryptor::handleRequest
Runtime: java8
CodeUri: './target/yunkefu-sign-1.0-SNAPSHOT.jar'
Timeout: 60
Decryptor:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: com.aliyun.fc.Decryptor::handleRequest
Runtime: java8
CodeUri: './target/yunkefu-sign-1.0-SNAPSHOT.jar'
Timeout: 60
Conclusion
Using a language as a service boundary is one of the most common ways to define the boundaries for a service architecture. Function Compute supports multi-language environments and provides high scalability and availability, making it an ideal solution for supporting the multi-language architecture as a service.
Normally, developers have accumulated rich experience in familiar languages and have established a complete architecture and deployment O&M system. However, to expand the business boundary, they sometimes have to select an unfamiliar programming language and architecture. To address this challenge, Function Compute uses a single simple language to develop businesses, resolving your concerns about architecture and O&M and mitigating the developers’ workload.