Here we go with our demo application. This time, we're going to build a simple and small REpresentational State Transfer (REST) service to compute, on demand, a concrete Nth position of Fibonacci's sequence. We will keep track of enabled/disabled features using a file. For simplicity, we will use Spring Boot as our framework of choice and Thymeleaf as a template engine. This is also included in the Spring Boot dependency. Find more information about Spring Boot and related projects at http://projects.spring.io/spring-boot/. Also, you can visit http://www.thymeleaf.org/ to read more about the template engine.
This is how the build.gradle file looks:
apply plugin: 'java' apply plugin: 'application' sourceCompatibility = 1.8 version = '1.0' mainClassName = "com.packtpublishing.tddjava.ch09.Application" repositories { mavenLocal() mavenCentral() } dependencies { compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: '1.2.4.RELEASE' testCompile group: 'junit', name: 'junit', version: '4.12' }
Note that application plugin is present because we want to run the application using the Gradle command run. Here is the application's main class:
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
We will create the properties file. This time, we are going to use YAML Ain't Markup Language (YAML) format, as it is very comprehensive and concise. Add a file called application.yml in the src/main/resources folder, with the following content:
features: fibonacci: restEnabled: false
Spring offers a way to load this kind of property file automatically. Currently, there are only two restrictions: the name must be application.yml and/or the file should be included in the application's class path.
This is our implementation of the feature's config file:
@Configuration @EnableConfigurationProperties @ConfigurationProperties(prefix = "features.fibonacci") public class FibonacciFeatureConfig { private boolean restEnabled; public boolean isRestEnabled() { return restEnabled; } public void setRestEnabled(boolean restEnabled) { this.restEnabled = restEnabled; } }
This is the fibonacci service class. This time, the computation operation will always return -1, just to simulate a partially done feature:
@Service("fibonacci") public class FibonacciService { public int getNthNumber(int n) { return -1; } }
We also need a wrapper to hold the computed values:
public class FibonacciNumber { private final int number, value; public FibonacciNumber(int number, int value) { this.number = number; this.value = value; } public int getNumber() { return number; } public int getValue() { return value; } }
This is the FibonacciRESTController class, responsible for handling the fibonacci service queries:
@RestController public class FibonacciRestController { @Autowired FibonacciFeatureConfig fibonacciFeatureConfig; @Autowired @Qualifier("fibonacci") private FibonacciService fibonacciProvider; @RequestMapping(value = "/fibonacci", method = GET) public FibonacciNumber fibonacci( @RequestParam( value = "number", defaultValue = "0") int number) { if (fibonacciFeatureConfig.isRestEnabled()) { int fibonacciValue = fibonacciProvider .getNthNumber(number); return new FibonacciNumber(number, fibonacciValue); } else throw new UnsupportedOperationException(); } @ExceptionHandler(UnsupportedOperationException.class) public void unsupportedException(HttpServletResponse response) throws IOException { response.sendError( HttpStatus.SERVICE_UNAVAILABLE.value(), "This feature is currently unavailable" ); } @ExceptionHandler(Exception.class) public void handleGenericException( HttpServletResponse response, Exception e) throws IOException { String msg = "There was an error processing " + "your request: " + e.getMessage(); response.sendError( HttpStatus.BAD_REQUEST.value(), msg ); } }
Note that the fibonacci method is checking whether the fibonacci service should be enabled or disabled, throwing an UnsupportedOperationException for convenience in the last case. There are also two error-handling functions; the first one is for processing UnsupportedOperationException and the second is for generic exceptions handling.
Now that all the components have been set, all we need to do is execute Gradle's
run command:
$> gradle run
The command will launch a process that will eventually set a server up on the following address: http://localhost:8080. This can be observed in the console output:
... 2015-06-19 03:44:54.157 INFO 3886 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2015-06-19 03:44:54.160 INFO 3886 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2015-06-19 03:44:54.319 INFO 3886 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2015-06-19 03:44:54.495 INFO 3886 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2015-06-19 03:44:54.649 INFO 3886 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http) 2015-06-19 03:44:54.654 INFO 3886 --- [ main] c.p.tddjava.ch09.Application : Started Application in 6.916 seconds (JVM running for 8.558) > Building 75% > :run
Once the application has started, we can perform a query using a regular browser. The URL of the query is http://localhost:8080/fibonacci?number=7.
This gives us the following output:
As you can see, the error received corresponds to the error sent by the REST API when the feature is disabled. Otherwise, the return should be -1.