Riki--Java-Spring-Boot-Microservices-Rickshaw-Booking-Application-Guide

  • Blog
  • /
  • Riki--Java-Spring-Boot-Microservices-Rickshaw-Booking-Application-Guide
on 2020-11-20 14:32:57.442313
  • technology
  • software

Riki

Running Riki

from the root directory riki mvn clean install
docker-compose build
docker-compose up -d docker-compose -p riki logs --tail=500

Endpoints

Register Rickshaws
request: curl -XPOST http://localhost/rickshaws -d '{"rickshawType": "NANO"}' -H "Content-Type: application/json" response: {"rickshawId":"7ab7567d-db48-442e-aa14-b9d3a16bc031"}

Submitting Location to Update Rickshaw Location Endpoint request:

response:

PUT to Update Rickshaw Status request: curl -X PUT http://localhost/rickshaws/aa2cce52-da38-497b-8cec-d0cffea3174a/status?status=OCCUPIED response: {"rickshawId":"aa2cce52-da38-497b-8cec-d0cffea3174a","status":"OCCUPIED"}

GET Rickshaw Status request: curl http://localhost/rickshaws/aa2cce52-da38-497b-8cec-d0cffea3174a/status response: {"rickshawId":"aa2cce52-da38-497b-8cec-d0cffea3174a","status":"OCCUPIED"}

Get Available Rickshaws request:curl -XGET http://localhost/rickshaws?type=MINI&latitude=10.000&longitude=10.1000&radius=1.0

response:

POST to Book Rickshaw Endpoint

Request: curl -X POST http://localhost/rickshaws -H "Content-Type: application/json" -d '{"start": {"latitude": 12.00, "longitude": 2.34}, "end": {"latitude": 2.00, "longitude": 4.34}, "customerId": 101, "rickshawType": "NANO"}' Response: {"rickshawId":"0abd85ab-0aed-4e3c-8a01-34837027fc57"}

PUT to Accept Rickshaw Booking Endpoint Request: curl -X PUT http://localhost/rickshawbookings/0abd85ab-0aed-4e3c-8a01-34837027fc57/accpet

PUT to Cancel Rickshaw Booking Endpoint Request: curl -X PUT http://localhost/rickshawbookings/0abd85ab-0aed-4e3c-8a01-34837027fc57/cancel

GET Rickshaw Bookings request:curl -XGET http://localhost/rickshawbookings?type=MINI&latitude=10.000&longitude=10.1000&radius=1.0

Table of Contents

Technical requirements Getting started - Microservices architecture - The requirements of microservices architecture - The use case diagram - The project structure to develop microservices Using Spring Data Redis for persistence - Understanding Redis - Understanding Spring Data Redis - Class diagram for the domain model - Implementation of domain model using Spring Data Redis annotations - Setting up dependencies and configuration - Implementing the domain model - Implementation of Spring Data Redis repositories - Using a Service to encapsulate business logic  Using Spring WebFlux for a controller - Implementation of controllers Using asynchronous data transfer for cross-microservice communication - Asynchronous data transfer using Redis Using Docker to support microservices - Understanding Docker - Using Maven to build Docker images - Building a system of microservices with Docker - Deploying microservices with Docker Demonstrating Rickshaw - Submiting Request to Riki's Endpoints

Summary


1.0. Technical requirements

Building an API with Reactive Microservices The following build tools need to be downloaded and installed: - To install Java Development Kit (JDK) 8, download it from its official page at http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html - To install Maven 3, download it from its official page at https://maven.apache.org/download.cgi - To install IntelliJ IDEA, download it from its official page at https://www.jetbrains.com/idea/download/ - To install Spring Tool Suite (STS), download it from its official page at https://spring.io/tools - To install Docker, download it from its official page at https://www.docker.com/get-docker - The source code for this project can be found at https: TODO

Getting started

First, the requirements, architecture, design, and imlementation details will be discussed.

Microservices architecture

Microservices architecture has become a buzzword within the last five years with the emergence of cloud-based hosting services. There is no fixed definition of this architecture, but in general terms, microservices architecture is a way of designing and implementing software as a collection of independently deployable services that are highly coherent and loosely coupled. Each of these services will be designed, implemented, deployed, and maintained by a team of usually 5 to 10 members with complete ownership and accountability. Each microservice will address a particular domain of a system (user, sales, and others), can be developed using different programming languages, and can have its own persistence and an API to enable synchronous communication and/or a publisher/subscriber model for asynchronous communication.

Microservices architecture was preceded by monolithic architecture, where all the functionality was grouped into a single application that loaded and ran inside a single process. This monolithic software was really heavy and took a lot of time to load, and scaling required the entire application to be scaled by replicating it in multiple servers. There was no way to scale a particular functionality alone.

There are some characteristics that can be found in any Microservices architecture-based software application, which are as follows:

  • Service components: The components in a microservice architecture are services that can communicate via a web request, RPC call, and so on. Unlike library components in a monolithic application where communication happens between library components via in-memory, in-process calls internally without an external API.
  • Organized by business domain: In a monolithic application, layering of the application was done using techniques such as user interface, business logic, and database. But in a microservices architecture, layering is not like that; instead, it is based on a business domain such as user, or transaction.
  • Building products, not projects: A monolithic application is considered to be a project, where it will be developed by a groups of engineers, deployed by another group of engineers, and maintained by another group, whereas microservices architecture-based software applications are built as products, which are owned by the group of engineers that developed them. They are accountable for the deployment, smooth running, and maintenance of that microservice product.
  • Smart Microservices, dumb queues: Instead of using an Enterprise Service Bus (ESB), which is capable of doing complex tasks such as transforming, filtering, routing, and aggregating of the messages being communicated, microservices rely on using dumb queues, which are just to communicate between two microservices asynchronously. All heavy lifting should be done in microservices.
  • Decentralized persistence: Polyglot persistence, as it is commonly known, lets each microservice use its own persistence (Database, Key/Value store—whichever suits it well), instead of relying on a single persistence store.
  • Fail tolerance: Microservices should be able to work under predictable and unpredictable failures.
  • Automation: Microservices should be able to be deployed using continuous integration and continuous deployment tools.
  • Future-proof with the ability to evolve.

The requirements of microservices architecture

The use case diagram

The project structure to develop microservices

To enablle code reuse and to avoid duplicating code the following project structure is used, which follows the maven module approach: |--riki |----docker.compose.yml |----nginx |----pom.xml |----booking |------pom.xml |----config |------pom.xml |----model |------pom.xml |----service |------pom.xml

Using Spring Data Redis for persistence

Riki uses Redis and Spring Data Redis repositories to provide CRUD (create, read, update, delete) operations on Redis and to use the reactive capabilitie of Spring Data with Redis.

Riki uses Redis because the data in a Rickshaw domain is highly volatile and tends to change often (e.g., rickshaws move around). Furthermore, Redis provides out-of-the-box geo data support. Because Redis holds in memory most of the time, it is a good choice for both tasks.

Understanding Redis

Redis is a distributed, in-memory, key-value store that provides high scalability, reliability, and performance. Redis is much more than a distributed cache; it stores not only key-value pairs but also collections such as lists, sets, sorted sets, maps, and many more. Redis also provides a set of algorithms that can be performed on those collections. Redis supports scalability by enabling client-side sharing and server-side master/slave replication. Redis stores encoded data in memory, so a large amount of data can be stored with a minimal memory footprint. Redis can also be configured to write data to a file for fault tolerance based on the timing and frequency of data writing.

Understanding Spring Data Redis

Spring Data Redis is intended to bring the concepts of Spring Data repositories to enable easy development of Redis repositories. It provides an abstraction layer on top of Redis to successfully store, retrieve, and modify documents available in Redis transparently. Spring Data Redis eases CRUD operations by allowing the CrudRepository interface, which extends from the repository. This hides the complexities of plain Redis implementations, which need to be implemented and tested by developers. Using Spring Data Redis could reduce the development time dramatically because of this. Furthermore, Spring Data Redis provides a set of templates to enable reactive programming in the form of ReactiveRedisTemplate and ReactiveRedisOperations. In coming chapters, CrudRepository with default methods and ReactiveRedisTemplate will be used extensively to implement business logic and to write Spring Data Redis repositories and test them. The following sections will discuss how to use a domain model designed using a class diagram as a base to implement Spring Data Redis-based documents and repositories.

Class diagram for the domain model

Implementation of domain model using Spring Data Redis annotations

With the domain model implemented successfully, CrudRepository for those can be implemented using Spring Data Redis. The specialty here is that there is no need to implement anything. Just writing an interface that extends from the CrudRepository interface would be sufficient to expose methods to find one, find all, save, delete, and so on. The following code shows the RickshawRepository, which is available in the spring-boot-2-rickshaw-service: The @Repository annotation is used to mark this interface as a data repository component of Spring. The following code shows RickshawBookingRepository, which is available in spring-boot-2-rickshaw-book-service:

Setting up dependencies and configuration

Implementing the domain model

Implementation of Spring Data Redis repositories

Using a Service to encapsulate business logic 

It is a good practice to encapsulate business logic inside Service methods so that controllers and repositories are loosely coupled. The following is a Service written for encapsulating business logic for Rickshaw, and is available in spring-boot-2-rickshaw-service: The preceding register method saves Rickshaw in the system so that it can fulfill rides. This will return a result that is a reactive single object: The preceding getAvailableRickshaws method will return all the Rickshaw IDs falling inside of a circle which has a center geo coordinate depicted by latitude, longitude, and radius in kilometers. This will return a result that is a reactive collection of objects: The preceding getRickshawStatus method will return RickshawStatus of a rickshaw identified by rickshawId. This will return a result that is a reactive single object: The preceding updateRickshawStatus method will update the RickshawStatus of a rickshaw identified by the rickshawId. This will return a result that is a reactive single object. The RickshawService class in the preceding code is annotated with the @Service stereotype annotation to mark it as a Spring Service. All methods of this service return either a Mono or Flux, enabling those to be used Reactively. When a Rickshaw specified by an ID does not exist, RickshawIdNotFoundException is thrown. The following is the implementation for it: RickshawIdNotFoundException extends from the Exception class. Likewise, the following RickshawBookingService class is used for RickshawBooking, which is available in spring-boot-2-rickshaw-book-service: The preceding book method will enable a passenger to save a RickshawBooking in Redis based on the supplied RickshawBookedEventDTO, and will use the reactiveRedisTemplate.opsForGeo().add() method to add the rickshaw booking by its type, to be listed by its starting location and rickshawBookingId so that it can be queried using geo-location search queries: The preceding cancel method retrieves a Rickshaw Booking by its rickshawBookingId and updates the booking status to canceled along with the reason and canceled time: The preceding accept method will enable a driver to accept a RickshawBooking to fulfill the ride. After updating the rickshawId and acceptedTime the rickshawBooking will be saved and a Booking Accepted Event will be triggered to notify any listeners: The preceding getBookings method will return RickshawBooking by rickshawType, geo-location, and radius. So any rickshawBookings that match the type and fall inside the circle whose center is marked by the geo-coordinates and has the same radius: The preceding updateBookingStatus will update the bookingStatus of a RickshawBooking identified by the rickshawBookingId passed in: The RickshawBookingService in the preceding code is also annotated with @Service stereotype annotation to mark it as a Spring Service. All methods of this service return either a Mono or Flux, enabling those to be used Reactively. When a Rickshaw booking specified by an ID does not exist, RickshawBookingIdNotFoundException is thrown. Following is the implementation:

Using Spring WebFlux for a controller

Implementation of controllers

The following code is the RickshawController, which caters to the registering, searching, status updating, and so on, of Rickshaws; it is available in spring-boot-2-rickshaw-service: We can understand the following from the previous code:

The getAvailableRickshaws function is mapped to the URL /rickshaws and accepts rickshaw type, latitude, longitude, and radius in kilometers, and returns Rickshaws available in that geographical area The getRickshawStatus function is mapped to the URL /rickshaws/{rickshawId}/status and returns the status of the Rickshaw identified by the rickshawId path variable The updateRickshawStatus function is mapped to the URL /rickshaws/{rickshawId}/status with the request method PUT and updates the status of the Rickshaw identified by the rickshawId path variable The updateLocation function is mapped to the URL /rickshaws/{rickshawId}/location with the request method PUT and updates the location of the Rickshaw identified by the rickshawId path variable The register function is mapped to the URL /rickshaws with request method POST and registers a new Rickshaw into the system The following code is RickshawBookingController, which caters to the registering, searching, status updating, and so on, of Rickshaws; it is available in spring-boot-2-rickshaw-book-service: We infer the following from the previous code:

The book function is mapped to the URL /rickshawbookings with request method POST and creates a Rickshaw booking for a particular Rickshaw type with start and end location The cancel function is mapped to the URL /rickshawbookings/{rickshawBookingId}/cancel and cancels a Rickshaw booking identified by the rickshawBookingId path variable The accept function is mapped to the URL /rickshawbookings/{rickshawBookingId}/accept and enables a driver to accept a Rickshaw booking identified by the rickshawBookingId path variable The getBookings function is mapped to the URL /rickshawbookings and accepts rickshaw type, latitude, longitude, radius in a kilometers, and returns Rickshaw bookings available in that geographical area

Using asynchronous data transfer for cross-microservice communication

Microservices need to communicate with each other from time to time. The HTTP APIs that microservices expose are usually reserved for the external systems invoking them, but when they need to talk internally, it is best to use an asynchronous way to communicate so that they can still communicate even when one microservice is down or not functioning properly.

Asynchronous data transfer using Redis

Redis offers asynchronous data transfer between an application using a publisher/subscriber model, which enables one application to publish to a channel and another application to subscribe to that channel and perform actions when an event is received. The publishers need not know about the subscribers, and vice versa in this model, which enables loose coupling and high scalability. In the case of these two microservices, the Rickshaw Microservice needs to know when a Rickshaw Booking is accepted in the Rickshaw Booking Microservice so that it can update the status of a Rickshaw. For this reason, the Rickshaw Booking microservice will publish a Rickshaw Booking Accepted Event, and the Rickshaw Microservice will subscribe to it. The following code snippet in the RickshawBookingService.accept function is responsible for publishing that event to the Redis Pub/Sub Channel: The following bean configuration snippet in the Rickshaw Microservice in SpringBoot2RickshawServiceApplication is required to set up the Subscriber as a listener: The following Listener implementation is required to take action when a Rickshaw Booking Accepted Event is sent from the Rickshaw Booking Microservice to the Rickshaw Microservice: The preceding listener's onMessage method will be activated whenever an event is received and will update the status of the Rickshaw.

Using Docker to support microservices

Because mircoservice architectures need to be able to scale on demand, Riki uses the Docker container platform.

Understanding Docker

Docker is a very popular container platform. Containerization, as opposed to virtualization, is the process of deploying applications in a portable and predictable manner by packaging components along with their dependencies into isolated, standard process environments called containers. Docker is used by many developers and IT operations staff to provide independence from the underlying infrastructure and applications they run. Docker can be run on on-premise hardware, in the cloud, or in a hybrid setup. Docker containers are lightweight and ideal for microservices development. Docker provides the following features for microservices: - Accelerated development of microservices - Ease of deployment - Ease of rollback - Lightweight - Portability - Predictability Docker uses a Dockerfile with the steps to initialize a container that can be deployed. Also, Docker provides Docker Compose in order to compose multiple Docker images to work together to create a system.

Using Maven to build Docker images

Since the projects already use Maven as the dependency management and build tool, it makes sense to use a Maven plugin that can be used to build Docker images. For this purpose, Spotify's open source dockerfile-maven plugin is used in both the spring-boot-2-rickshaw-service and spring-boot-2-rickshaw-booking-service projects as follows: This is with the Dockerfile, which is placed inside /src/resources/docker/Dockefile: A Docker image can be built by issuing the following usual Maven build command while being inside the spring-boot-2-rickshaw project:

The preceding Dockerfile does the following code:

Downloads alpine-oraclejdk8 and uses it to run the Spring Boot app Goes into the /tmp volume Adds the file mentioned by the placeholder PROJECT_JAR (will be explained later) as app.jar Creates an empty file by the name app.jar in the root Sets the environment variable JAVA_OPTS Creates the entry point into the Docker image with the Spring Boot 2.0 application, which is instructed to start app.jar using the java command This Dockerfile is available for both spring-boot-2-rickshaw-service and spring-boot-2-rickshaw-booking-service. There are two more plugins used to help with this Dockerfile, which are listed as follows: The maven-resource-plugin will copy the Dockerfile from the /src/resources/docker directory to the /target directory in order to create a Docker image successfully, while the replacer plugin will replace the PROJECT_JAR placeholder in the file /target/Dockerfile to have the Maven project build the final JAR name (an example is spring-boot-2-rickshaw-service-0.0.1-SNAPSHOT.jar).

Building ... In the preceding diagram, everything resides inside of Docker as containers. Each small rectangle is a Docker container with a process of its own. There is one Redis container that acts as the in-memory data store for replication to have multiple instances of the Rickshaw Service Apps and Rickshaw Booking Service Apps. All the apps are running behind an NGinx Load Balancer so that it can handle the load by distributing traffic among different apps. The following docker-compose.yml file is used to define the preceding layout in Docker's understandable configuration: The services section lists all the services required to be defined, while the networks section lists the networks connecting those services together. The db service, in this case, the Redis data store, is the first service to be initialized, as both the rickshaw-service-app and rickshaw-booking-service-app services depend on it. The db service will be listening on port 6379 both inside and outside of the network backend. This service is created using an already existing Docker image from the Docker repository named redis:alpine.

After that, either the rickshaw-service-app or rickshaw-booking-service-app service can be initialized. It will be created from the Docker image that was created in the previous step and placed inside the respective /target directory. Both of these services depend on db as mentioned earlier and connect to the network backend. Finally, the nginx-lb service will be initialized when both the rickshaw-service-app and rickshaw-booking-service-app services are up and running. This service will expose port 80 for non-secure connections and 443 for secure connections. This service will also use the app.conf configuration file available in the /nginx/conf.d directory, which is inside the /spring-boot-2-rickshaw project. The app.conf file does the following: It will listen on port 80, as mentioned in the preceding code explanation, and will route any requests that come into http:///rickshawbookings to http://rickshaw-booking-service-app:9090 (9090 is the port for the Rickshaw Bookings service and is configured with server.port in application.properties file for that project). It will route any requests that come into http:///rickshaws to http://rickshaw-service-app:8080. Also, it will send some headers along with the forwarded requests. This is all there for the composing of the final system.

Deploying microservices with Docker

Now, with Docker images being built and the Docker composing layout defined, it is possible to build the composing layout and start it so that it can cater requests. The following command, issued from inside the spring-boot-2-rickshaw project, composes everything:

$ docker-compose build The preceding command will generate a somewhat familiar output:

db uses an image, skipping Building rickshaw-booking-service-app ... Successfully built 8e77b030d54c Successfully tagged spring-boot-2-rickshaw_rickshaw-booking-service-app:latest Building rickshaw-service-app ... Successfully built a2f9c5cf28af Successfully tagged spring-boot-2-rickshaw_rickshaw-service-app:latest nginx-lb uses an image, skipping The preceding output shows that db and nginx-lb use an existing image, so they are not built from scratch, whereas rickshaw-booking-service-app and rickshaw-service-app are built from scratch using the Dockerfile and initialize everything.

Next, the following command can be used to start all the Docker containers of the system:

$ docker-compose up -d The preceding command will create all the containers in daemon mode (will run in the background) and will generate the following familiar-looking output:

Creating network "spring-boot-2-rickshaw_backend" with driver "bridge" Creating spring-boot-2-rickshaw_db_1 ... done Creating spring-boot-2-rickshaw_rickshaw-service-app_1 ... done Creating spring-boot-2-rickshaw_rickshaw-booking-service-app_1 ... done Creating nginx-lb ... done As explained earlier, the output shows that the network is being created first, followed by db, followed by the apps, and finally nginx load balancer.

Individual apps can be scaled up or down using the following command:

docker-compose up --scale <APP_NAME>=2 -d APP_NAME can be either rickshaw-service-app or rickshaw-booking-service-app based on the requirement. The number after the equals sign depicts the number of containers required to be running for that app. Consider the following example: docker-compose up --scale rickshaw-service-app=2 -d Also, in order to stop and bring down all the services started, the following command can be used: docker-compose down

Building a system of microservices with Docker

Deploying microservices with Docker

Demonstrating Rickshaw

Submitting Request to Riki's Endpoints

Summary

  • Maven modules enable modularizing large projects into smaller subprojects to optimize and speed up build and deployment.
  • Redis is an in-memory, key/value store that povides high availability, performance, and scalability.
  • Microservices architecture is a way of designing and implementing software as a collection of independently deployable services that are highly coherent and loosely coupled.
  • Idependent isolated teams, domain-specific laying, ability to automate deployments, fault tolerance, and the ability to scale indiviual features.
  • Containerization is the process of deploying applications in a portable and predictable manner by packaging components along with their dependencies into isolated, standard process environments called containers.
  • Docker is a container platform used to provide independence from the underlying infrastructure and applications they run. Docker can be run on-premise hardware, in the cloud, or in a hybrid setup. Because Docker containers are lightweight, they are ideal for microservices development.
  • Nginx is a load balancer/proxy server to create clusters of webservices.

Comments(30)

Sean on 23 May 2015, 10:40AM

Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin commodo. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis.

Strong Strong on 21 May 2015, 11:40AM

Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin commodo. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis.

Emma Stone on 30 May 2015, 9:40PM

Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin commodo. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis.

Nick Nilson on 30 May 2015, 9:40PM

Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin commodo. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis.

Leave A Comment