preface

After the last article was shared, some fans responded that the content was too theoretical and abstract to see the actual appearance.

Therefore, I will write a step-by-step tutorial on how to deploy a SpringBoot project to Serverless and test it successfully.

The following link is the article I published to the official, but the official article will be considered comprehensively, so there will be no such detailed steps. This article is the most detailed step.

Mp.weixin.qq.com/s/0rIkGjYiC…

This article to Tencent cloud Serverless cloud function as an example, will be divided into event function and Web function two kinds of tutorial.

An event function is a function that is triggered by an event.

Web functions are functions that trigger functions by sending HTTP requests directly. You can see the difference here.

The differences in Spring project migration and transformation are as follows:

  • The event function needs to add an entry class.
  • The port of the Web function needs to be fixed to 9000.
  • Event functions need to manipulate more console configurations.
  • Web functions need to add an SCf_bootstrap startup file, and a different way of packaging.

Event functions

Spring Project preparation

Event function example code download address: github.com/woodyyan/sc…

Introduction to sample code

The @SpringBootApplication class remains unchanged.

package com.tencent.scfspringbootjava8; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ScfSpringbootJava8Application { public static void main(String[] args) { SpringApplication.run(ScfSpringbootJava8Application.class, args); }}Copy the code

The Controller class will stay the same as it was written before. Take the Todo application as an example.

Remember the /todos path here, which will be used later.

The code is as follows:

package com.tencent.scfspringbootjava8.controller;

import com.tencent.scfspringbootjava8.model.TodoItem;
import com.tencent.scfspringbootjava8.repository.TodoRepository;
import org.springframework.web.bind.annotation.*;

import java.util.Collection;

@RestController
@RequestMapping("/todos")
public class TodoController {
    private final TodoRepository todoRepository;

    public TodoController() {
        todoRepository = new TodoRepository();
    }

    @GetMapping
    public Collection<TodoItem> getAllTodos() {
        return todoRepository.getAll();
    }

    @GetMapping("/{key}")
    public TodoItem getByKey(@PathVariable("key") String key) {
        return todoRepository.find(key);
    }

    @PostMapping
    public TodoItem create(@RequestBody TodoItem item) {
        todoRepository.add(item);
        return item;
    }

    @PutMapping("/{key}")
    public TodoItem update(@PathVariable("key") String key, @RequestBody TodoItem item) {
        if (item == null || !item.getKey().equals(key)) {
            return null;
        }

        todoRepository.update(key, item);
        return item;
    }

    @DeleteMapping("/{key}")
    public void delete(@PathVariable("key") String key) {
        todoRepository.remove(key);
    }
}
Copy the code

Add a ScfHandler class with the following project structure:

The Scfhandle class is primarily used to receive event triggers, forward messages to Spring Application, and then return the results to the caller upon receiving the return from Spring Application.

The default port number is 8080.

Its code content is as follows:

package com.tencent.scfspringbootjava8; import com.alibaba.fastjson.JSONObject; import com.qcloud.services.scf.runtime.events.APIGatewayProxyRequestEvent; import com.qcloud.services.scf.runtime.events.APIGatewayProxyResponseEvent; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import java.util.HashMap; import java.util.Map; public class ScfHandler { private static volatile boolean cold_launch; // initialize phase, initialize cold_launch static { cold_launch = true; } // function entry, use ApiGatewayEvent to get request // send to localhost:8080/hello as defined in helloSpringBoot.java public String mainHandler(APIGatewayProxyRequestEvent req) { System.out.println("start main handler"); if (cold_launch) { System.out.println("start spring"); ScfSpringbootJava8Application.main(new String[]{""}); System.out.println("stop spring"); cold_launch = false; } // From API geteway event -> spring Request -> spring boot port // system.out.println ("request: "+ req); // path to request String path = req.getPath(); System.out.println("request path: " + path); String method = req.getHttpMethod(); System.out.println("request method: " + method); String body = req.getBody(); System.out.println("Body: " + body); Map<String, String> reqHeaders = req.getHeaders(); // construct request HttpMethod httpMethod = HttpMethod.resolve(method); HttpHeaders headers = new HttpHeaders(); headers.setAll(reqHeaders); RestTemplate client = new RestTemplate(); HttpEntity<String> entity = new HttpEntity<>(body, headers); String url = "<http://127.0.0.1:8080>" + path; System.out.println("send request"); ResponseEntity<String> response = client.exchange(url, httpMethod ! = null ? httpMethod : HttpMethod.GET, entity, String.class); // Wait for spring business to return processing structure -> API Geteway Response. APIGatewayProxyResponseEvent resp = new APIGatewayProxyResponseEvent(); resp.setStatusCode(response.getStatusCodeValue()); HttpHeaders responseHeaders = response.getHeaders(); resp.setHeaders(new JSONObject(new HashMap<>(responseHeaders.toSingleValueMap()))); resp.setBody(response.getBody()); System.out.println("response body: " + response.getBody()); return resp.toString(); }}Copy the code

Gradle

Gradle, for example, differs from traditional development in that build.gradle requires a fully packaged plugin to ensure that all dependencies are stored in jars.

  1. addId 'com. Making. Johnrengelman. Shadow' version '7.0.0'This plugin.
  2. addid 'application'
  3. addId 'IO. Spring. The dependency - management' version '1.0.11. RELEASE'
  4. The specifiedmainClass.

Build. Gradle:

Plugins {id 'org.springFrameframe. boot' version '2.5.5' id 'io.spring. Dependency -management' version '1.0.11.RELEASE' id 'Java - library' id 'application' id 'com. Making. Johnrengelman. Shadow' version '7.0.0} group =' com. Tencent 'version = '0.0.2-SNAPSHOT' sourceCompatibility = '1.8' Repositories {mavenCentral()} Dependencies {API 'org.springframework.boot:spring-boot-starter-web' api group: 'com.tencentcloudapi', name: 'tencentCloud-sdK-java ', version: '3.1.356' API group:' com.tencentCloudAPI ', name: 'SCf-java-events ', version: '0.0.4' testImplementation 'org. Springframework. The boot: spring - the boot - starter - test'} the test {useJUnitPlatform ()} application  { // Define the main class for the application. mainClass = 'com.tencent.scfspringbootjava8.ScfSpringbootJava8Application' }Copy the code

Maven

Using Maven as an example, the main difference with traditional development is that Pom.xml requires the addition of maven-shade-plugin to ensure that all dependencies used are put into jar packages. You also need to specify mainClass. The mainClass in the following code needs to be your own mainClass path.

The content of POM.xml is as follows:

<? The 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 > < https://maven.apache.org/xsd/maven-4.0.0.xsd >" > < modelVersion > 4.0.0 < / modelVersion > < the parent > < groupId > org. Springframework. Boot < / groupId > The < artifactId > spring - the boot - starter - parent < / artifactId > < version > 2.5.5 < / version > < relativePath / > <! -- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>1.0</version> <name> Demo </name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <! -- Build an executable JAR --> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.0</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>com.mypackage.MyClass</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> The < artifactId > spring - the boot - maven - plugin < / artifactId > < version > 2.1.1. RELEASE < / version > < / dependency > < / dependencies > <configuration> <keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope> <createDependencyReducedPom>true</createDependencyReducedPom> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer"> <resource>META-INF/spring.factories</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" /> </transformers> </configuration>  </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build> </project>Copy the code

Compile the JAR package

After downloading the code, go to the root directory of the project and run the compile command:

  • Gradle project runs:gradle build
  • Maven project runs:mvn package

Once compiled, the packaged JAR packages will be found in the output directory of the current project.

  • Gradle project: inbuild/libsIf you see the jar package packaged in the directory, select the suffix is-allThe JAR package. The diagram below.
  • Maven Project: intargetThe jar package can be seen in the directory. You need to select the prefixDon’t takeorginal-The jar package.

This JAR package will be used when you deploy the function later.

Cloud function preparation

Cloud function creation

In the function service, click New to start creating the function.

The following figure

  1. Select Custom Creation

  2. Select event function

  3. Enter a function name

  4. The runtime environment is Java8

  5. Upload the ZIP package locally

  6. The execution method is specified as the package name. Class name :: Name of the entry function

    1. Here, for example:com.tencent.scfspringbootjava8.ScfHandler::mainHandler
  7. Upload the jar package with the -all suffix selected previously compiled there.

Then click Finish create function.

Cloud Function Configuration

Once created, go to Function Management-Function Configuration-Edit. The diagram below.

After clicking Edit, in the environment configuration:

  1. Change the memory to 1024MB
  2. Change the execution timeout to 15 seconds

Trigger configuration

In Trigger Management, create triggers.

When creating a trigger, in the following image:

  1. Trigger mode API gateway trigger.
  2. Select integrated response.
  3. And then submit

Some API gateway parameters need to be modified after creation. Click the API service name to enter the modification.

Click the edit button on the right to modify.

In the first front-end configuration, change the path to the default path in your Spring project. The diagram below.

Then click Finish Now.

Then click Publish Services.

Return to the cloud functions console after publishing.

To begin testing

Here we use the first GET method written in Controller as an example, as shown below, to GET all the Todo items.

In function management, select function code, you can easily test. The diagram below.

  1. Test event Select API Gateway Event Template.
  2. Request mode selectionGET
  3. The Path to fill/todos
  4. Finally, you can click the test button.

Test results and logs are displayed directly in the lower right corner of the screen. The diagram below.

If you want to get the full access URL, you can find the API gateway trigger you just created in the Trigger Management section. The URL is available below. There is a copy button after the URL. The diagram below.


Web function

Spring Project preparation

Introduction to sample code

Web function example code download address: github.com/woodyyan/sc…

The project code for the Web function is simpler than the event function. Code changes cost almost nothing. Changes to the original code have only one port number.

Web functions do not require the ScfHandler entry class, and the project structure is as follows:

Because the Web function must ensure that the project listens on port 9000, you need to change the port that Spring listens on to 9000. The diagram below:

Code deployment package ready

Refer to “Compiling JAR Packages” above for compilation of code packages.

Then create a new scF_bootstrap startup file. The file name must be SCF_bootstrap without a suffix.

  1. The first line must have#! /bin/bash.
  2. The Java startup command must be an absolute path. The absolute path of Java is:/var/lang/java8/bin/java
  3. Please make sure your SCF_bootstrap file has 777 or 755 permissions, otherwise it will not be able to execute due to insufficient permissions.

Therefore, the startup file contents are as follows:

#! /bin/bash /var/lang/java8 /bin/java-dserver. port=9000 -jar SCF -springboot-java8-0.0.2- snapshot-all.jarCopy the code

Next, run the following command in the directory where the scf_bootstrap file resides to ensure that the scf_bootstrap file is executable.

chmod 755 scf_bootstrap
Copy the code

Then package the scf_bootstrap file together with the SCf-springboot-java8-0.0.2-snapshot-all. jar file into a zip file. The diagram below.

The packaged ZIP file is our deployment package.

Cloud function creation

In the function service, click New to start creating the function.

The following figure

  1. Select Custom Creation
  2. Select Web functions
  3. Enter a function name
  4. The runtime environment is Java8
  5. Upload the ZIP package locally
  6. Upload there to select the previous compressedscf_spring_boot.zipThe package.

Then, in the advanced configuration section below, write the startup command. The jar file in the command should be the name of the jar file you compiled.

Because the Web function must ensure that the project listens on port 9000, you specify these ports in the command.

For more information about how to write the startup command, see the startup file description.

The diagram below:

Then configure the environment there and change the memory to 512MB. The timeout period is set to 15 seconds.

Use the default Settings for everything else. Then click Finish.

If there is no response after clicking Finish, it will wait for the ZIP file to be uploaded before creating the function.

Because Web functions create API gateway triggers by default, we do not need to configure triggers separately.

To begin testing

Here we use the first GET method written in Controller as an example, as shown below, to GET all the Todo items.

In the function code of the function console, we can directly test our cloud functions.

According to the code above, we select GET, enter path /todos, then click the test button and see our results in the lower right corner.

If you want to test somewhere else, you can copy the access path shown in the figure below.

The last

Mirroring functions are not covered in this tutorial because mirroring deployment is the same as the original deployment. The project code doesn’t need to change. In theory, this is the best approach for microservice projects.

In the next article, I’ll take a closer look at the following topics in Serverless.

  • Inter-service invocation in Serverless
  • Database access in Serverless
  • Registration and discovery of services in Serverless
  • Service fuses and degradation in Serverless
  • Service split in Serverless