1. The Stack of Frameworks / Specs
Well, even any application can be built and used for this experiment, but why not build an interesting one: a real world RESTful service by saying hello to someone? Alright, that’s just another statement for a hello world application;)
Here we go:
- Sprint Boot: The fundamental framework for the services.
- Jersey: The RI of JAX-RS specs.
- Swagger: The API documentation specs and tooling.
- For testing, we’re going to use
- Spring Boot Test Framework (spring-boot-starter-test) with Junit, Hamcrest
- Rest Assured
2. Git Server: Gogs
Even we can setup Git Server later, but as version control is important, please do it as early as possible.
Note: I did try GitLab but: 1) it’s too slow running in my laptop; and 2) I was facing several permission issues being thrown from embedded Nginx…so I changed my mind and had Gogs instead as it’s really “a painless self-hosted Git service” as advertised.
2.1 Spin Up the Docker Container
Login to the Ubuntu VM, our logical host and execute below commands:
1 2 3 4 5 | $ cd ~ $ mkdir cd_demo $ cd cd_demo $ touch docker-compose.yml $ vim docker-compose.yml |
1 2 3 4 5 6 7 | Gogs: image: 'gogs/gogs:latest' ports: - '3000:3000' - '1022:22' volumes: - '/data/gogs:/data' |
Note: it’s a good practice to mount a local path (here is /data) for the container so all valuable data like settings, database will not be gone even we upgrade our container.
$ docker-compose up
Now, we have spun up our first Docker container for Gogs, our Git server.
2.2 Configuration
In just a few seconds, the Gogs container should have been up and running and we can access the app by: http://192.168.56.118:3000
It would guide us for the “Install Steps For First-time Run”.
Below were my inputs, for your reference:
- Database Settings
- Database Type: SQLite3
- Path: /data/db/gogs.db
- Application General Settings
- Application Name: Gogs: Go Git Service
- Repository Root Path: /data/git/gogs-repositories
- Run User: git
- Domain: 192.168.56.118
- SSH Port: 22
- HTTP Port: 3000
- Application URL: http://192.168.56.118:3000/
- Log Path: data/log
- Optional Settings
- Admin Account Settings: omitted
Once done, Gogs will persistent the settings and redirect to the home page.
2.3 Create Our First Repository
All these can be done via Gogs UIs:
2.4 Clone the Git Repository to Kick Off Development
By command line, it’s very simple and straightforward.
Open Git Bash and key in:
$ git clone http://192.168.56.118:3000/bright/springboot-jersey-swagger.git
Or we can do it by using Eclipse if you prefer to.
3. Create Maven-based Project Structure
Since development part is my strongest part and code says more than words, let’s make it short but clear during the experiment.
Apparently, as we follow Maven’s best practices to build the project so the folder structure is exactly Maven-driven:
---------------------------------------------
/
│ LICENSE
│ pom.xml
│ README.md
├───src
│ ├───main
│ │ ├───java
│ │ │ └───bright
│ │ │ └───zheng
│ │ │ └───poc
│ │ │ └───api
│ │ │ │ ApiApplication.java
│ │ │ ├───config
│ │ │ │ JerseyConfig.java
│ │ │ ├───model
│ │ │ │ Hello.java
│ │ │ └───resources
│ │ │ HelloResource.java
│ │ └───resources
│ │ │ application.yml
│ │ │ bootstrap.yml
│ │ │ log4j.xml
│ │ ├───static
│ └───test
│ └───java
│ └───bright
│ └───zheng
│ └───poc
│ └───api
│ ApplicationTests.java
---------------------------------------------
Meanwhile,
I’ll highlight some important files with necessary comments so that we can
easily understand how to develop a RESTful application by using abovementioned
frameworks.
3.1 pom.xml
/pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | <?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> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.5.RELEASE</version> <relativePath /> </parent> <groupId>bright.zheng.poc.api</groupId> <artifactId>springboot-jersey-swagger</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>jar</packaging> <name>springboot-jersey-swagger-docker</name> <description>POC - Restful API by Spring Boot, Jersey, Swagger</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <!-- project related properties --> <start-class>bright.zheng.poc.api.ApiApplication</start-class> <swagger-jersey2-jaxrs>1.5.3</swagger-jersey2-jaxrs> <rest-assured>3.0.0</rest-assured> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jersey</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-jersey2-jaxrs</artifactId> <version>${swagger-jersey2-jaxrs}</version> </dependency> <!-- testing related dependencies --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <version>${rest-assured}</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
3.2 ApiApplication.java
\src\main\java\bright\zheng\poc\api\ApiApplication.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package bright.zheng.poc.api; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.web.SpringBootServletInitializer; /** * This is a typical boostrap class for Spring Boot based application * * @author bright.zheng * */ @SpringBootApplication public class ApiApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(ApiApplication.class); } public static void main(String[] args) { SpringApplication.run(ApiApplication.class, args); } } |
3.3 HelloResource.java
\src\main\java\bright\zheng\poc\api\resources\HelloResource.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | package bright.zheng.poc.api.resources; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import bright.zheng.poc.api.model.Hello; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; @Component @Path("/v1/hello") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Api(value = "Hello API - say hello to the world", produces = "application/json") public class HelloResource { private static final Logger LOGGER = LoggerFactory.getLogger(HelloResource.class); @GET //JAX-RS Annotation @Path("/{name}") //JAX-RS Annotation @ApiOperation( //Swagger Annotation value = "Say hello by providing the name by URI, via standard json header", response = Hello.class) @ApiResponses(value = { //Swagger Annotation @ApiResponse(code = 200, message = "Resource found"), @ApiResponse(code = 404, message = "Resource not found") }) public Response sayHelloByGet(@ApiParam @PathParam("name") String name) { LOGGER.info("v1/hello/{} - {}", name, MediaType.APPLICATION_JSON); return this.constructHelloResponse(name, MediaType.APPLICATION_JSON); } /////////////////////////////////////////////////////////////// private Response constructHelloResponse(String name, String via) { //for testing how we handle 404 if ("404".equals(name)) { return Response.status(Status.NOT_FOUND).build(); } Hello result = new Hello(); result.setMsg(String.format("Hello %s - %s", name, via)); return Response.status(Status.OK).entity(result).build(); } } |
3.4 JerseyConfig.java
\src\main\java\bright\zheng\poc\api\config\JerseyConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | package bright.zheng.poc.api.config; import javax.annotation.PostConstruct; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.wadl.internal.WadlResource; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import bright.zheng.poc.api.resources.HelloResource; import io.swagger.jaxrs.config.BeanConfig; import io.swagger.jaxrs.listing.ApiListingResource; import io.swagger.jaxrs.listing.SwaggerSerializers; /** * Jersey Configuration * * <p> * * The endpoint will expose not only the real APIs, * but also two important documents: * <li> - swagger spec: /swagger.json</li> * <li> - WADL spec: /application.wadl</li> * * </p> * * @author bright.zheng * */ @Component public class JerseyConfig extends ResourceConfig { @Value("${spring.jersey.application-path:/api}") private String apiPath; public JerseyConfig() { this.registerEndpoints(); } @PostConstruct public void init() { this.configureSwagger(); } private void registerEndpoints() { this.register(HelloResource.class); this.register(WadlResource.class); } private void configureSwagger() { this.register(ApiListingResource.class); this.register(SwaggerSerializers.class); BeanConfig config = new BeanConfig(); config.setTitle("POC - Restful API by Spring Boot, Jersey, Swagger"); config.setVersion("v1"); config.setContact("Bright Zheng"); config.setSchemes(new String[] { "http", "https" }); config.setBasePath(this.apiPath); config.setResourcePackage("bright.zheng.poc.api.resources"); config.setPrettyPrint(true); config.setScan(true); } } |
4. Swagger UI Integration
It would be great to have Swagger UI integration for automatic API documentation.
Simply do below steps:
- Download it from https://github.com/swagger-api/swagger-ui/archive/master.tar.gz. As of writing, the latest version is v2.1.4.
- Unzip it and copy all the files to folder: /src/main/resources/static/
5. Testing
Programmers write unit tests, of course. Here we go:
5.1 ApplicationTests.java
\src\test\java\bright\zheng\poc\api\ApplicationTests.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | package bright.zheng.poc.api; import static io.restassured.RestAssured.expect; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import io.restassured.RestAssured; /** * Application level testing * * @author bright.zheng * */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = ApiApplication.class) @WebIntegrationTest({"server.port=0"}) public class ApplicationTests { @Value("${local.server.port}") private int port; @Before public void setupURL() { RestAssured.baseURI = "http://localhost"; RestAssured.port = port; RestAssured.basePath = "/"; } /** * * {"app":{"name":"springboot-jersey-swagger"},"build":{"version":"1.0.0-SNAPSHOT"}} * * @throws Exception */ @Test public void springroot_info() throws Exception { expect(). body("app.name", equalTo("springboot-jersey-swagger")). body("build.version", equalTo("1.0.0-SNAPSHOT")). when(). get("/info"); } /** * {"msg":"Hello Bright - application/json"} * * @throws Exception */ @Test public void hello_get() { expect(). body("msg", containsString("Hello Bright")). when(). get("/api/v1/hello/Bright"); } } |
6. Run It
6.1 Start It Up
By Maven command line in Windows, it would be:
We can even try the services. Simply key in the parameter (name here) and click the “Try it out!” button, we can see everything in the same page including:
1 2 3 | > cd /d D:\development\workspaces\java\POC\springboot-jersey-swagger > mvn package > java -jar target/springboot-jersey-swagger-1.0.0-SNAPSHOT.jar |
By Eclipse, simply open ApiApplication.java and click Run menu -> Run As -> Java Application, it would be booted up within seconds.
6.2 Take a Look at the API Documentation UI provided by Swagger
Simply open browser and key in: http://localhost:8000/
We can even try the services. Simply key in the parameter (name here) and click the “Try it out!” button, we can see everything in the same page including:
- Curl command if we want to reproduce it on Curl
- URL we just requested
- Response Code
- Response Body
- Response Headers
7. Commit and Push to Git
Commit and push our first draft of Hello Service to our local Git Server.
By Git Bash:
1 2 3 4 | $ git status $ git add –A $ git commit –m “first draft of Hello Service” $ git push |
Note: I also synced the code to GitHub, follow below steps:
- Create a GitHub repository: itstarting/springboot-jersey-swagger
- Execute commands like:
1 2 3 4 5 6 7 8 $ cd /D/development/workspaces/java/POC/springboot-jersey-swagger $ git remote add github https://github.com/brightzheng100/springboot-jersey-swagger.git $ git remote -v github https://github.com/brightzheng100/springboot-jersey-swagger.git (fetch) github https://github.com/brightzheng100/springboot-jersey-swagger.git (push) origin http://192.168.56.118:3000/bright/springboot-jersey-swagger.git (fetch) origin http://192.168.56.118:3000/bright/springboot-jersey-swagger.git (push) $ git push -u github master
By Eclipse it's very simple:
- Right click project -> team -> Commit…
- Choose all items in “Unstaged Changes”, right click -> Add to Index, which will add all items into “Staged Changes”
- Key in comments in “Commit Message” and click “Commit and Push” button and eventually we have below result.
Well, the development works are done!
For complete source code, please visit my GitHub. Enjoy coding!