Let us create our first synchronous, asynchronous and reactive microservices by following these steps:
- Using Eclipse STS, create a Maven project for a Spring Boot application named ch10-deptservice that will represent a microservice for the department domain. Then, create a POM configuration which includes all the needed Spring Boot 2.0.0.M2 starter POM libraries, such as WebFlux, Spring Context, JDBC, HikariCP connection pool, Ehcache, JPA, embedded reactive Tomcat container, Reactor Netty container, FreeMarker and Thymeleaf. Also include some required support libraries, such as MySQL 5.x connector and Rx Java 2.0:
<project xmlns="..."> <modelVersion>4.0.0</modelVersion> .... <packaging>war</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.M2</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding> UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding> UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <startClass>org.packt.spring.boot.HRDeptBootApplication </startClass> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> ... <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <dependency> <groupId>io.reactivex.rxjava2</groupId> <artifactId>rxjava</artifactId> <version>2.1.0</version> </dependency> </dependencies> .... <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> <finalName>ch10-dept</finalName> </build> </project>
- Create the core package, org.packt.microservice.core, and drop a bootstrap class inside it named as HRDeptBootApplication. Update the <startClass> property of pom.xml:
@SpringBootApplication public class HRDeptBootApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources( HRDeptBootApplication.class); } public static void main(String[] args) throws Exception { SpringApplication.run(HRDeptBootApplication.class, args); } }
- Create another package, org.packt.microservice.core.config, for the reactive ApplicationContext configuration details. Drop the following @Configuration classes inside this package:
@Configuration @EnableCaching public class CachingConfig { // empty } @Configuration @EnableJpaRepositories( basePackages="org.packt.microservice.core.dao") @Import(RepositoryRestMvcConfiguration.class) @EnableTransactionManagement public class SpringDataConfig { // empty } @EnableAsync @Configuration public class SpringAsynchConfig implements AsyncConfigurer { private static Logger logger = LoggerFactory.getLogger(SpringAsynchConfig.class); @Bean("mvcTaskexecutor") @Override public Executor getAsyncExecutor() { ConcurrentTaskExecutor executor = new ConcurrentTaskExecutor( Executors.newFixedThreadPool(100)); executor.setTaskDecorator(new TaskDecorator() { @Override public Runnable decorate (Runnable runnable) { return () -> { long t = System.currentTimeMillis(); runnable.run(); logger.info("creating ConcurrentTaskExecutor ...."); System.out.printf("Thread %s has a processing time: %s%n", Thread.currentThread().getName(), (System.currentTimeMillis() - t)); }; } }); return executor; } } @Configuration @EnableWebFlux public class HttpServerConfig { @Bean public NettyContext nettyContext(ApplicationContext context) { HttpHandler handler = DispatcherHandler.toHttpHandler(context); ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler); HttpServer httpServer = HttpServer.create("localhost", Integer.valueOf("9003")); return httpServer.newHandler(adapter).block(); } }
All of these @Configuration are just the same as the previous chapters.
- Copy the ehcache.xml and logback.xml configuration files of the previous chapter and drop them to this project's src/main/resources folder. Update the new Ehcache's diskStore and Logger's log file path for this project.
- Also in src/main/resources, create the required application.properties for the auto-configuration of the embedded reactive Tomcat, JDBC and HikariCP data source setup with JPA and Hibernate 5 data persistency configuration:
server.port=8090 server.servlet.context-path=/ch10-dept spring.datasource.driverClassName=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/hrs?autoReconnect=true&useSSL=true&serverSslCert=classpath:config/spring5packt.crt spring.datasource.username=root spring.datasource.password=spring5mysql spring.datasource.hikari.connection-timeout=60000 spring.jpa.hibernate.use-new-id-generator-mappings=false
- Building the proper RESTful services starts with the correct JPA entities. Copy and drop the Department entity model used in ch09 to a new package org.packt.microservice.core.model.data.
- Together with the entity models, create an auxiliary POJO that will contain aggregate and simple data value such as the total number of departments which is a Long object type. The main reason behind this is that JSON and XML marshallers do not directly convert wrapper objects (for example, Integer, Double, Float) into JSON objects or XML entities:
public class CountDept implements Serializable{ private Long count; public Long getCount() { return count; } public void setCount(Long count) { this.count = count; } }
- Then, implement the @Repository class for DepartmentDao using Spring Data JPA. Apply Ehcache caching to each data retrieval operation. Drop this class inside org.packt.microservice.core.dao package:
@Repository public interface DepartmentRepository extends JpaRepository<Department, Integer>{ @Cacheable("departmentCache") public List<Department> findByName(String name); @Cacheable("departmentCache") public List<Department> findByDeptid(Integer deptId); }
- Next, create a DepartmentService, that will compose the RESTful services to be exposed by the microservice. The template includes both asynchronous and synchronous service signatures:
public interface DepartmentService { public Department findDeptByid(Integer id); public List<Department> findAllDepts(); public List<Department> findDeptsByName(String name); public List<Department> findDeptsByDeptId(Integer deptid); public void saveDeptRec(Department dept); public CompletableFuture<List<Department>> readDepartments(); public Future<Department> readDepartment(Integer id); }
- Implement the service class using the preceding DepartmentRepository class. The implementations include blocking transactions and non-blocking ones that return Future and CompletableFuture tasks. And, since the @EnableTransactionManagement has been invoked by the configuration class SpringDataConfig, the @Transactional annotation must be applied per service implementation class for JPA persistency and commit/rollback management:
@Service @Transactional public class DepartmentServiceImpl implements DepartmentService{ @Autowired private DepartmentRepository departmentRepository; @Override public List<Department> findAllDepts() { return departmentRepository.findAll(); } @Override public List<Department> findDeptsByName(String name) { return departmentRepository.findByName(name); } // refer to sources @Override public CompletableFuture<List<Department>> readDepartments() { return CompletableFuture.completedFuture( departmentRepository.findAll()); } @Async public Future<Department> readDepartment(Integer id) { return new AsyncResult<>(departmentRepository.findById(id) .orElse(new Department())); } }
- Spring 5's RouterFunction<?> has a separate set of services, which are written as a handler class. A handler class contains all the HandlerFunction<?> implementation that can be mapped later with the corresponding URL. Below is a handler class for this microservice that must be placed inside the org.packt.microservice.core.handler package:
@Component public class DeptDataHandler { // refer to sources public Mono<ServerResponse> deptList(ServerRequest req) { Flux<Department> flux = Flux.fromIterable( departmentServiceImpl.findAllDepts()); return ok().contentType(MediaType.APPLICATION_STREAM_JSON) .body(flux, Department.class); } public Mono<ServerResponse> chooseDeptById(ServerRequest req) { Scheduler subWorker = Schedulers.newSingle("sub-thread"); Mono<Department> emp = Mono.defer(() -> Mono.justOrEmpty( departmentServiceImpl.findDeptByid( Integer.parseInt(req.pathVariable("id"))))) .subscribeOn(subWorker); return ok().contentType(MediaType.APPLICATION_STREAM_JSON) .body(emp, Department.class) .switchIfEmpty(ServerResponse.notFound().build()); } public Mono<ServerResponse> chooseFluxDepts(ServerRequest req) { return ok().contentType(MediaType.APPLICATION_STREAM_JSON) .body(req.bodyToFlux(Integer.class) .flatMap((id) -> Mono.justOrEmpty( departmentServiceImpl.findDeptByid(id))), Department.class) .switchIfEmpty(ServerResponse.notFound().build()); } public Mono<ServerResponse> saveDepartmentMono( ServerRequest req) { Scheduler subWorker = Schedulers.newSingle("sub-thread"); Mono<Department> department = req.bodyToMono( Department.class).doOnNext( departmentServiceImpl::saveDeptRec) .subscribeOn(subWorker); return ok().contentType( MediaType.APPLICATION_STREAM_JSON) .build(department.then()); } public Mono<ServerResponse> countDepts(ServerRequest req) { Mono<Long> count = Flux.fromIterable(departmentServiceImpl .findAllDepts()) .count(); CountDept countDept = new CountDept(); countDept.setCount(count.block()); Mono<CountDept> monoCntDept = Mono.justOrEmpty(countDept); return ok().contentType( MediaType.APPLICATION_STREAM_JSON) .body(monoCntDept, CountDept.class) .switchIfEmpty(ServerResponse.notFound().build()); } }
- At this point, synchronous, asynchronous, and reactive RESTful services can now be implemented to build this microservice for the department domain. Let us start by implementing the GET and POST synchronous REST web services using the @RestController, @GetMapping, and @PostMapping of Spring 5:
@RestController public class DeptBlockingController { @Autowired private DepartmentService departmentServiceImpl; @GetMapping(value="/selectDept/{id}", produces= MediaType.APPLICATION_JSON_VALUE) public Department blockDepartment( @PathVariable("id") Integer id) { return departmentServiceImpl.findDeptByid(id); } @GetMapping(value="/listDept", produces= MediaType.APPLICATION_JSON_VALUE) public List<Department> blockListDept() { return departmentServiceImpl.findAllDepts(); } @PostMapping(value="/saveDeptRec", consumes= MediaType.APPLICATION_JSON_VALUE) public Boolean blockSaveDept(@RequestBody Department dept) { try{ departmentServiceImpl.saveDeptRec(dept); return true; }catch(Exception e){ return false; } } }
- Now, build the asynchronous REST services that use WebAsyncTask, Callable, and DeferredResult tasks that return the Future<T> and CompletableFuture<T> objects:
@RestController public class DeptAsyncController { @Autowired private DepartmentService departmentServiceImpl; @GetMapping(value="/webSyncDeptList.json", produces ="application/json", headers = {"Accept=text/xml, application/json"}) public WebAsyncTask<List<Department>> websyncDeptList(){ Callable<List<Department>> callable = new Callable<List<Department>>() { public List<Department> call() throws Exception { return departmentServiceImpl .readDepartments().get(500, TimeUnit.MILLISECONDS); } }; return new WebAsyncTask<List<Department>>(500, callable); } @GetMapping(value="/deferSelectDept/{id}.json", produces ="application/json", headers = {"Accept=text/xml, application/json"}) public DeferredResult<Department> deferredSelectDept( @PathVariable("id") Integer id) { DeferredResult<Department> deferredResult = new DeferredResult<>(); CompletableFuture.supplyAsync(()->{ try { return departmentServiceImpl .readDepartment(id).get(); } catch (InterruptedException e) { } catch (ExecutionException e) { } return null; }).thenAccept((msg)->{ deferredResult.setResult(msg); }); return deferredResult; } @GetMapping(value="/callSelectDept/{id}.json", produces ="application/json", headers = {"Accept=text/xml, application/json"}) public Callable<Department> jsonSoloEmployeeCall( @PathVariable("id") Integer id){ Callable<Department> task = new Callable<Department>() { @Override public Department call () throws Exception { Department dept = departmentServiceImpl .readDepartment(id).get(); return dept; } }; return task; } }
- The last set of REST services are the reactive ones which can be implemented in two ways: using the typical @RestController and applying the functional and web framework APIs, namely the RouterFunction<T> and HandlerFunction<T> of Spring 5. The following are the reactive services for the department microservice implemented by @RestController:
@RestController public class DeptReactiveController { @Autowired private DepartmentService departmentServiceImpl; @GetMapping("/selectReactDepts") public Flux<Department> selectReactDepts() { return Flux.fromIterable( departmentServiceImpl.findAllDepts()); } @GetMapping("/selectReactDept/{id}") public Mono<Department> selectReactDept ( @PathVariable("id") Integer id) { return Mono.justOrEmpty( departmentServiceImpl.findDeptByid(id)) .defaultIfEmpty(new Department()); } @PostMapping("/saveReactDept") public Mono<Void> saveReactDept( @RequestBody Department dept) { return Mono.justOrEmpty(dept) .doOnNext(departmentServiceImpl::saveDeptRec).then(); } }
- The following are the reactive REST services built by Spring 5's reactive functional web framework written inside @Configuration class:
import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.POST; import static org.springframework.web.reactive.function.server.RouterFunctions.route; @Configuration @EnableWebFlux public class DeptReactFuncControllers { @Autowired private DeptDataHandler dataHandler; @Bean public RouterFunction<ServerResponse> departmentServiceBox(){ return route(GET("/listFluxDepts"), dataHandler::deptList) .andRoute(GET("/selectDeptById/{id}"), dataHandler::chooseDeptById) .andRoute(POST("/selectFluxDepts"), dataHandler::chooseFluxDepts) .andRoute(POST("/saveFluxDept"), dataHandler::saveDepartmentMono) .andRoute(GET("/countFluxDepts"), dataHandler::countDepts); } }
- Save all files. Build and deploy the reactive web project by running the Maven command clean spring-boot:run -U -e. Open a browser and execute all the GET services. If no errors and exceptions are found, name this application as the Department microservice.
- Copy the Login and Employee domains from the previous chapter and perform again all the processes in this recipe to build two more service boxes namely the Employee microserviceas ch10-empservice and Login microserviceas ch10-loginservice project.