Skip to content

Spring Boot Starter kit running in AppEngine Standard Environment

License

Notifications You must be signed in to change notification settings

iwag/spring-boot-appengine-starter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Overview

Build Status MIT License

  • CRUD RESTFul API
  • Spring Boot
  • Google cloud datastore
  • Google appengine Standard environment
  • Gradle

how to get started

$ gcloud init 
# ...
$ gradle appengineRun # run locally
$ gradle appengineDeploy # deploy on GCP

First step, Simple RestController

Firstly, create HelloController which returns only Hello, World! text by GET Request. This is same as spring boot's guide

@RestController
public class HelloController {
    @GetMapping(path = "/hello")
    public String hello() {
        return "Hello, World!";
    }
}

In order to run as spring-boot, ServletInitializer and SpringApplication requires.

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(SpringBootApplication.class);
    }

}
@org.springframework.boot.autoconfigure.SpringBootApplication
public class SpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootApplication.class, args);
    }
}

To allow the application run in appengine, we need appengine-web.xml in src/main/webapp/WEB-INF/appengine-web.xml

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <threadsafe>true</threadsafe>
  <runtime>java8</runtime>
</appengine-web-app>

Run this in appengine. You can see something like below.

$ mvn appengine:run
$ curl -i 'localhost:8080/hello'
HTTP/1.1 200 OK
Date: Thu, 31 Aug 2017 21:13:13 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 13
Server: Jetty(9.3.3.v20150827)

Hello, World!

Handle CRUD by a RestController

For example, TaskController(which is kind of RestController) handles GET/POST/DELETE/PUT HTTP Request where datastore (or persistansis) is not assumed to simplify. TaskController(which is RestController) handles GET/POST/DELETE/PUT HTTP Request. Take advantage of spring's annotations to work as RestController.

@RestController
public class TaskController {

    // Use RequestMapping annotation to map a specific path to a function
    @RequestMapping(path = "/tasks", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public List<TaskEntity> gets() {
        return Arrays.asList(new TaskEntity("0", "TEST", 0, "2017/08/31"));
    }

    // Use placeholder in path and PathVariable annotation
    // in order to pass id inside path through an argument
    @RequestMapping(method = RequestMethod.GET, path = "/task/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public TaskEntity get(@PathVariable("id") String id) {
        return new TaskEntity("0", "TEST", 0, "2017/08/31");
    }

    // RequestBody annotation indicates an argument `task` is supposed to receive requst body
    // Of course, to convert TaskEntity class from JSON/request_body automatically
    @RequestMapping(path = "/task", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
    public void create(@RequestBody TaskEntity task) {
        logger.info("TaskEntity: "+task);
        return;
    }
}

Here's an entity class.

public class TaskEntity {
    private String id;
    private String description;
    private Integer priority;
    private String untilDate;

    // a constructor with no arguments is necesary
    public TaskEntity() {
    }

    public TaskEntity(String id, String description, Integer priority, String untilDate) {
        this.id = id;
        this.description = description;
        this.priority = priority;
        this.untilDate = untilDate;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    // define all getters and setters
}

Handle an error

Let's think about error handling. App should be supposed to be received wrong incorrect input or go with unexpected behaivor. Then make app return 400, 404, 503 and among other arbitary response code.

First, create our tailored exception class.

public class ApplicationException extends RuntimeException {
}

Next implement a part handling an exception. It needed to implement in all handlers but BaseController will be introduce, which becomes a base class of all of them.

public class BaseController {

    protected final Logger logger = LogManager.getLogger(getClass());

    @ExceptionHandler({ ApplicationException.class })
    public ResponseEntity<Object> handleError(
            ApplicationException ex, WebRequest request) {
        return new ResponseEntity<Object>(new ErrorResponseModel(ex.getStatus().toString(), ex.getMessage()), new HttpHeaders(), ex.getStatus());
    }
}

All controllers should be like here.

public class TaskController extends BaseController {

See this article if you want to know more.

Append the following dependency into dependency part in pom.xml.

<dependency>
   <groupId>com.google.appengine</groupId>
   <artifactId>appengine-api-1.0-sdk</artifactId>
   <version>1.9.50</version>
</dependency>

We use datastore through appengine-api library instead of following cloud library because cloud library can't be worked with local devserver's datastore.

<dependency>   <!-- Google Cloud Client Library for Java -->
   <groupId>com.google.cloud</groupId>
   <artifactId>google-cloud</artifactId>
   <version>0.25.0-alpha</version>
</dependency>

Second of all, let's create interface and service class for access to datastore. You can see TaskSercie and DatastoreService.

Next, introduce Autowired member into TaskController which is [DI] variable. It means that it can be changeable in runtime. Autowired annotation shows this member is supposed to be accept any TaskService instances. Spring boot's DI is described here.

@RestController
public class TaskController extends BaseController {

    @Autowired
    TaskService datastoreService;

At same time there's one more autowired member in Datastore class. This datastore member is an actual client for appengine to communicate with the datastore.

@Service
public class DatastoreService implements TaskService {

    @Autowired
    com.google.appengine.api.datastore.DatastoreService datastore;

In addition, This configulation in SpringApplication required to run google cloud datastore.

    @Bean
    public DatastoreService cloudDatastoreService() {
        return DatastoreServiceFactory.getDatastoreService();
    }

use push-queue

AppEngine has task-based push/pull queue.

First of all, we need to specify a queue in queue.xml.

<?xml version="1.0" encoding="UTF-8"?>
  <queue-entries>
    <queue>
      <name>queue-blue</name>
      <target>v2.task-module</target>
    </queue></target></name>
</queue>
<queue-entries>

About

Spring Boot Starter kit running in AppEngine Standard Environment

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages