Understanding Microservices with basic Cab Hailing Design

Mehul
5 min readMay 25, 2021
Fig 1. Microservices Architecture

Requirements

  1. Basic Spring boot and Java knowledge
  2. PostgreSQL
  3. Hibernate
  4. IntelliJ
  5. Maven
  6. JWT(refer the link)
  7. Retrofit

Getting Clear with Requirements

Document Link

In this document, there are some requirements which are necessarily required for a cab hailing platform to work. We have put a mock inside requirements which we will not be building for now. As the system and users grows we can work on different modules of this system. Also, we will deploy this system on AWS(will be published in near future).

So, we will go ahead with:

  1. User can register and login.
  2. Driver can register and login.
  3. User will be able to request a ride and cancel a ride.
  4. Drivers would be able to search all the rides in available pincode(some time after this will be converted to lat-long data), they will be allowed to book and cancel the booked ride.
  5. Drivers will start a ride and end the ride once reach destination.
Fig 2. Basic Flow Diagram

These Requirements will be covered.

WHY MICROSERVICE?

So, everyone would be wondering why do we need microservices for this. We could directly make a whole new service which could do all of these things covered in the requirements. Answer for this question would be yes, yes we can do that and everything will work in case of single service too.

Now we remain at the same question of why.

Fig 3. Monoliths and Microservices

It is because of several reasons:

  1. Scaling: let’s say we are receiving too much load on one part of code and it is used too often. So, when we need to scale up that, then we don’t need to scale whole service, we can make that as one new service and make multiple instances of that.
  2. Debugging: If you are using a single system to do everything, then in case of a bug you will need to debug whole system. In case of microservices, you will get to know the problematic micro-service.
  3. Changes: In a complex system, you will need to make changes often and doing so in a monolith would be very untidy work, sometimes, you would also be required to rewrite whole part of that code. In microservices, we will be able to add new feature really easily.

Discover more at link.

Dividing into Microservices

We has now understood why microservices are needed in general, but how do we divide just these requirements? We will go by the idea one microservice one responsibility.

There seems to be 3 logic partitions of these features in microservices, so the microservices I have decided to go with are:

  1. user-authentication : This microservice deals with all the requests which will be coming from rider users.(Login, SingUp, Ride booking)
  2. driver-authentication : This microservice deals with all the requests which will be coming from driver users.(Login, SingUp, Ride acceptance, starting ride, ending ride etc.)
  3. booking : This microservice will deal with all the ride data, this would work as transactional data as of now. We would update the ride for a user.

Lets Get Started

Lets jump on the coding and designing part.

We will be using retrofit to call different microservices. You can learn more about retrofit on link. As of now we are not going to use any libraries for common code like JWT for user and driver or common entities.

What properties looks likes:

These are to define datasource, driver for sql, port of application.

spring.datasource.url=jdbc:postgresql://localhost:5432/uber
spring.datasource.username=postgres
spring.datasource.password=root

# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
server.port=7010
logging.level.root=INFO

We will be using JWT for authenticating the user.

Once the login is done we will receive a token which will be validated at any other request. Authenticating tokens:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);
String userIdString = request.getHeader("userId");
if(!request.getRequestURL().toString().contains("/auth") && userIdString == null){
throw new RuntimeException("error user id not present");
}
Optional<DriverEntity> driverEntityOptional = driverRepository.findFirstById(Long.valueOf(userIdString));
if(!driverEntityOptional.isPresent() || !driverEntityOptional.get().getUserName().equals(username)){
throw new RuntimeException("error : driver user not present");
}
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: ", e);
}

filterChain.doFilter(request, response);
}

Once the user is validated we will be able to make request to our main Controller class.

Creating Retrofit Client

import com.project.driverauthentication.clients.DriverBookingService;
import okhttp3.OkHttpClient;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.Retrofit;

public class DriverBookingServiceGenerator {

private static final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

private static final Retrofit retrofitDriver = new Retrofit.Builder().baseUrl("http://localhost:7030/")
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();

public static DriverBookingService getService(){
return retrofitDriver.create(DriverBookingService.class);
}

}

We will have other validations like when user requests a new ride then user should not have any ongoing rides and other check for different use cases.

Different states a ride can be in :

  1. REQUESTED : When user has request the ride. When driver cancels the ride after accepting it.
  2. CANCELED : When user cancels the ride.
  3. BOOKED : When driver accepts the ride.
  4. IN_PROGRESS : When driver starts the ride.
  5. COMPLETED : When driver ends the ride.

When driver accepts a ride the row in ride row will be filled with driver_id and if they cancel it, it will again goes to null and REQUESTED state. A driver can only accept a ride with the same vehicle(SUV, Sedan etc) user has requested. Drivers will be given the option of choosing between the rides.

You can play around with this postman collection.

Also you can find the whole code at :- user-authentication, driver-authentication, booking.

--

--