Microservices with Spring Boot : Service Discovery Using Apache ZooKeeper

In this article, we will implement service discovery for two microservices developed using Spring Boot that will synchronously communicate with each other using Spring's RestTemplate.

A microservice needs to know the location (IP address and port) of every service it communicates with. If we don’t employ a Service Discovery mechanism, service locations become coupled, leading to a system that’s difficult to maintain. We could wire the locations or inject them via configuration in a traditional application, but it isn’t recommended in a modern cloud-based application of this kind.

The Service Discovery mechanism helps us know where each instance is located. In this way, a Service Discovery component acts as a registry in which the addresses of all instances are tracked. The instances have dynamically assigned network paths. Consequently, if a client wants to request a service, it must use a Service Discovery mechanism.

We will be registering on the Apache ZooKeeper server and other microservices (discovery clients) will use it to register and discover services to call their APIs.

Let us set up the ZooKeeper

Follow this guide to set up the Zookeeper

Download

To get a ZooKeeper distribution, download a recent stable release from one of the Apache Download Mirrors.

Standalone Operation

Setting up a ZooKeeper server in standalone mode is straightforward. The server is contained in a single JAR file, so installation consists of creating a configuration.

Once you've downloaded a stable ZooKeeper release unpack it and cd to the root

To start ZooKeeper you need a configuration file. Here is a sample, create it in conf/zoo.cfg:

tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181

This file can be called anything, but for the sake of this discussion call it conf/zoo.cfg. Change the value of dataDir to specify an existing (empty to start with) directory. Here are the meanings for each of the fields:

  • tickTime: the basic time unit in milliseconds used by ZooKeeper. It is used to do heartbeats and the minimum session timeout will be twice the tickTime.

  • dataDir: the location to store the in-memory database snapshots and, unless specified otherwise, the transaction log of updates to the database.

  • clientPort: the port to listen for client connections

Now that you created the configuration file, you can start ZooKeeper:

bin/zkServer.sh start

Our ZooKeeper instance will be up and running on port 2181.

Build the user service

Go to start.spring.io

Note: For this article, we will use maven.

Add the following dependencies :

  • Spring Web

  • Lombok

  • Spring Data JPA

  • H2 Database

  • Apache Zookeeper Discovery

For this article, we are using Spring Boot version 2.7.9 and Java 11.

Click on Generate and open the project in an IDE (IntelliJ, Eclipse, VSCode, etc)

Create a User Entity

Create an entities package and inside it create a User.java class

User.java

import lombok.*;

import javax.persistence.*;

@Entity
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Table(name = "users")
public class User
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    long id;
    String firstName;
    String lastName;
    String email;
}

Create a JPA Repository for User

Create a package named repositories and create an interface for the user JPA repository.

UserRepository.java

import com.umang345.userservicezookeeperdemo.entities.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

Add database properties and discovery client configuration in the application.yml file

Add H2 Database properties, server port and discovery client configuration in the application.yml file

user-service/application.yml

server:
  port: 8081
spring:
  application:
    name: USER-SERVICE
  cloud:
    zookeeper:
      discovery:
        enabled: true
  datasource:
    url: "jdbc:h2:mem:testdb"
    driverClassName: org.h2.Driver
    username: sa
    password: password
    jpa:
      database-platform: org.hibernate.dialect.H2Dialect
    h2:
      console:
        enabled: true
        path: /h2

logging:
  level:
    org.apache.zookeeper.ClientCnxn: WARN

Create custom exception

We will create a ResourceNotFoundException to deal with situations when the user that is requested is not present in the database.

We will create our exception classes in our exceptions package

user-service/ResourceNotFoundException.java

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends Exception
{
    public ResourceNotFoundException(String message){
        super(message);
    }

    public ResourceNotFoundException(){
        super("The requested resource could not be found");
    }
}

Create a custom error message

To handle the exception globally we will define a custom error message in our exceptions package.

user-service/ErrorMessage.java

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@AllArgsConstructor
@Getter
@Setter
@Builder
public class ErrorMessage
{
    private String message;
    private String details;
}

Create a global exception handler

We will implement a global exception handler class that will handle our ResourceNotFoundException and also any generic exception.

user-service/GlobalExceptionHandler.java

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;

@ControllerAdvice
public class GlobalExceptionHandler
{

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<?> resourceNotFoundExceptionHandler(ResourceNotFoundException ex, WebRequest request){
        ErrorMessage errorMessage = ErrorMessage
                                       .builder()
                                       .message(ex.getMessage())
                                       .details(request.getDescription(false))
                                       .build();
        return new ResponseEntity<>(errorMessage, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> globalExceptionHandler(Exception ex, WebRequest request){
        ErrorMessage errorMessage = ErrorMessage
                .builder()
                .message(ex.getMessage())
                .details(request.getDescription(false))
                .build();
        return new ResponseEntity<>(errorMessage, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Define the methods in the UserService interface

We will create a service layer over the JPA layer. Create a service package and add a UserService interface.

user-service/UserService.java

import com.umang345.userservicezookeeperdemo.entities.User;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public interface UserService
{
    User createUser(User newUser);

    User getUserById(long userId);

    User updateUser(User user, long userId);

    List<User> getAllUser();

    void deleteUser(long userId);
}

Implement the UserService interface

We will add an implementation for the UserService interface.

user-service/UserServiceImpl.java

import com.umang345.userservicezookeeperdemo.entities.User;
import com.umang345.userservicezookeeperdemo.exceptions.ResourceNotFoundException;
import com.umang345.userservicezookeeperdemo.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public User createUser(User newUser) {
        User savedUser = userRepository.save(newUser);
        return savedUser;
    }

    @Override
    public User getUserById(long userId)  {
        User fetchedUser = null;
        try {
            fetchedUser = userRepository.findById(userId)
                    .orElseThrow(() -> new ResourceNotFoundException("User not found with id : "+userId));
        } catch (ResourceNotFoundException e) {
            e.printStackTrace();
        }
        return fetchedUser;
    }

    @Override
    public User updateUser(User user, long userId) {
        User currentUser = null;
        try {
            currentUser = userRepository.findById(userId)
                    .orElseThrow(() -> new ResourceNotFoundException("User not found with id : "+userId));
            currentUser.setFirstName(user.getFirstName());
            currentUser.setLastName(user.getLastName());
            currentUser.setEmail(user.getEmail());

        } catch (ResourceNotFoundException e) {
            e.printStackTrace();
            return null;
        }

        User updateUser = userRepository.save(currentUser);
        return updateUser;
    }

    @Override
    public List<User> getAllUser() {
        List<User> users = userRepository.findAll();
        return users;
    }

    @Override
    public void deleteUser(long userId) {
        User currentUser = null;
        try {
            currentUser = userRepository.findById(userId)
                    .orElseThrow(() -> new ResourceNotFoundException("User not found with id : "+userId));
        } catch (ResourceNotFoundException e) {
            e.printStackTrace();
        }

        userRepository.delete(currentUser);
    }
}

Add the Controller for the User

We will implement a UserController that will expose the endpoints for the CRUD operations.

user-service/UserController.java

import com.umang345.userservicezookeeperdemo.entities.User;
import com.umang345.userservicezookeeperdemo.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/users")
public class UserController
{
    @Autowired
    private UserService userService;

    @GetMapping("/{userId}")
    public ResponseEntity<?> getUserById(@PathVariable Long userId)
    {
        User user = userService.getUserById(userId);
        Map<String, Object> response = new HashMap<>();
        if(user==null){
            User nullUser = User.builder().id(0).firstName(null).lastName(null).email(null).build();
            response.put("status", HttpStatus.NOT_FOUND.value());
            response.put("data", nullUser);
            return ResponseEntity.status(HttpStatus.OK).body(response);
        }
        response.put("status", HttpStatus.OK.value());
        response.put("data", user);
        return ResponseEntity.ok().body(response);
    }

    @GetMapping
    public ResponseEntity<?> getAllUsers(){
        List<User> users = userService.getAllUser();
        Map<String, Object> response = new HashMap<>();
        response.put("status", HttpStatus.OK.value());
        response.put("data", users);
        return ResponseEntity.ok().body(response);
    }

    @PostMapping
    public ResponseEntity<?> createUser(@RequestBody User newUser) {
        User createdUser = userService.createUser(newUser);
        Map<String, Object> response = new HashMap<>();
        response.put("status", HttpStatus.CREATED.value());
        response.put("data", createdUser);
        return ResponseEntity.ok().body(response);
    }

    @PutMapping("/{userId}")
    public ResponseEntity<?> updateUser(@RequestBody User user, @PathVariable Long userId){

        User updateUser = userService.updateUser(user,userId);
        Map<String, Object> response = new HashMap<>();
        if(updateUser==null){
            User nullUser = User.builder().id(0).firstName(null).lastName(null).email(null).build();
            response.put("status", HttpStatus.NOT_FOUND.value());
            response.put("data", nullUser);
            return ResponseEntity.status(HttpStatus.OK).body(response);
        }
        response.put("status", HttpStatus.OK.value());
        response.put("data", updateUser);
        return ResponseEntity.ok().body(response);
    }

    @DeleteMapping("/{userId}")
    public ResponseEntity<?> deleteUser(@PathVariable Long userId)
    {
         userService.deleteUser(userId);
         return ResponseEntity.ok().body("User deleted successfully with Id : "+userId);
    }
}

pom.xml

The pom.xml for the user service must contain the following dependencies :

user-service/pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

Add the @ EnableDiscoveryClient annotation

Add the @ EnableDiscoveryClient annotation in the UserServiceZookeeperDemoApplication.java class.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceZookeeperDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserServiceZookeeperDemoApplication.class, args);
    }

}

With this, we complete our user service.

Build the Runner Service

Now we will build the runner service that is directly called by the client.

Go to https://start.spring.io/

Add the following dependencies :

  • Spring Web

  • Lombok

  • Apache ZooKeeper Discovery

For this article, we are using Spring Boot version 2.7.9 and Java 11.

Click on Generate and open the project in an IDE (IntelliJ, Eclipse, VSCode, etc)

Create the User entity

We will create the same user entity for the runner class by adding the database properties.

runner-service/User.java

@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User
{
    long id;
    String firstName;
    String lastName;
    String email;
}

Add a Bean for the RestTemplate

We will create a separate configuration class and add a Bean for the RestTemplate there.

MyConfiguration.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class MyConfiguration
{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

Note: Make sure you add the @ LoadBalanced annotation to the RestTemplate Bean

Add discovery client configuration in the application.yml file

Add the server port and discovery client configuration in the application.yml file

user-service/application.yml

server:
  port: 8080

spring:
  application:
    name: RUNNER-SERVICE
  cloud:
    zookeeper:
      discovery:
        enabled: true

logging:
  level:
    org.apache.zookeeper.ClientCnxn: WARN

pom.xml

pom.xml of the runner service should contain the following dependencies.

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

Add the Controller for the Runner Service

We will add the RunnerController that shall contain the endpoints for the client to call and the methods shall make a synchronous call to the user service to get the data.

RunnerController.java

import com.umang345.runnerservicezookeeperdemo.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

@RestController
@RequestMapping("/simulate/users")
public class RunnerController
{
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    public ResponseEntity<?> getAllUsers(){
        ResponseEntity<Map> response = restTemplate.exchange("http://localhost:8081/users",HttpMethod.GET,new HttpEntity<>(new HttpHeaders()),Map.class);
        return ResponseEntity.ok().body(response.getBody().get("data"));
    }

    @GetMapping("/{userId}")
    public ResponseEntity<?> getUserById(@PathVariable Long userId) {

        ResponseEntity<Map> response = null;
        try {
            response = restTemplate.exchange("http://localhost:8081/users/"+userId,HttpMethod.GET,new HttpEntity<Map>(new HttpHeaders()), Map.class);
            Map<String,Object> res = response.getBody();
            if((Integer)res.get("status") != HttpStatus.OK.value())
            {
                 throw new Exception("User not found with Id : "+userId);
            }

            return ResponseEntity.status(HttpStatus.OK).body(res.get("data"));
        }
        catch (Exception e){
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
        }
    }

    @PostMapping
    public ResponseEntity<?> createUser(@RequestBody User newUser){
        ResponseEntity<Map> response = null;
        try {
            response = restTemplate.exchange("http://localhost:8081/users",HttpMethod.POST,new HttpEntity<>(newUser),Map.class);

            Map<String,Object> res = response.getBody();
            if((Integer)res.get("status") != HttpStatus.CREATED.value())
            {
                throw new Exception("Error while creating user");
            }

            return ResponseEntity.status(HttpStatus.OK).body(res.get("data"));
        }catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
        }

    }

    @PutMapping("/{userId}")
    public ResponseEntity<?> updateUser(@RequestBody User user, @PathVariable Long userId){
        ResponseEntity<Map> response = null;
        try{
            response = restTemplate.exchange("http://localhost:8081/users/"+userId,HttpMethod.PUT,new HttpEntity<>(user),Map.class);

            Map<String,Object> res = response.getBody();
            if((Integer)res.get("status") != HttpStatus.OK.value())
            {
                throw new Exception("User not found with Id : "+userId);
            }

            return ResponseEntity.status(HttpStatus.OK).body(res.get("data"));
        }catch (Exception e){
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
        }

    }

    @DeleteMapping("/{userId}")
    public ResponseEntity<?> deleteUser(@PathVariable Long userId)
    {
         try {
             ResponseEntity<String> response = restTemplate.exchange("http://localhost:8081/users/"+userId, HttpMethod.DELETE, new HttpEntity<User>(new HttpHeaders()), String.class);

             return ResponseEntity.status(HttpStatus.OK).body("User deleted successfully with id : "+userId);
         }catch (Exception e) {
             return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found with Id : "+userId);
         }
    }
}

In this case, the IP address and port of USER-SERVICE are hard-coded, we would replace it with the name of the USER SERVICE which would then dynamically be mapped from the consul server.

RunnerController.java

import com.umang345.runnerservicezookeeperdemo.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

@RestController
@RequestMapping("/simulate/users")
public class RunnerController
{
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    public ResponseEntity<?> getAllUsers(){
        ResponseEntity<Map> response = restTemplate.exchange("http://USER-SERVICE/users",HttpMethod.GET,new HttpEntity<>(new HttpHeaders()),Map.class);
        return ResponseEntity.ok().body(response.getBody().get("data"));
    }

    @GetMapping("/{userId}")
    public ResponseEntity<?> getUserById(@PathVariable Long userId) {

        ResponseEntity<Map> response = null;
        try {
            response = restTemplate.exchange("http://USER-SERVICE/users/"+userId,HttpMethod.GET,new HttpEntity<Map>(new HttpHeaders()), Map.class);
            Map<String,Object> res = response.getBody();
            if((Integer)res.get("status") != HttpStatus.OK.value())
            {
                 throw new Exception("User not found with Id : "+userId);
            }

            return ResponseEntity.status(HttpStatus.OK).body(res.get("data"));
        }
        catch (Exception e){
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
        }
    }

    @PostMapping
    public ResponseEntity<?> createUser(@RequestBody User newUser){
        ResponseEntity<Map> response = null;
        try {
            response = restTemplate.exchange("http://USER-SERVICE/users",HttpMethod.POST,new HttpEntity<>(newUser),Map.class);

            Map<String,Object> res = response.getBody();
            if((Integer)res.get("status") != HttpStatus.CREATED.value())
            {
                throw new Exception("Error while creating user");
            }

            return ResponseEntity.status(HttpStatus.OK).body(res.get("data"));
        }catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
        }

    }

    @PutMapping("/{userId}")
    public ResponseEntity<?> updateUser(@RequestBody User user, @PathVariable Long userId){
        ResponseEntity<Map> response = null;
        try{
            response = restTemplate.exchange("http://USER-SERVICE/users/"+userId,HttpMethod.PUT,new HttpEntity<>(user),Map.class);

            Map<String,Object> res = response.getBody();
            if((Integer)res.get("status") != HttpStatus.OK.value())
            {
                throw new Exception("User not found with Id : "+userId);
            }

            return ResponseEntity.status(HttpStatus.OK).body(res.get("data"));
        }catch (Exception e){
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
        }

    }

    @DeleteMapping("/{userId}")
    public ResponseEntity<?> deleteUser(@PathVariable Long userId)
    {
         try {
             ResponseEntity<String> response = restTemplate.exchange("http://USER-SERVICE/users/"+userId, HttpMethod.DELETE, new HttpEntity<User>(new HttpHeaders()), String.class);

             return ResponseEntity.status(HttpStatus.OK).body("User deleted successfully with id : "+userId);
         }catch (Exception e) {
             return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found with Id : "+userId);
         }
    }
}

Add the @ EnableDiscoveryClient

Add the @ EnableDiscoveryClient annotation in the RunnerServiceZookeeperDemoApplication.java class.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class RunnerServiceZookeeperDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(RunnerServiceZookeeperDemoApplication.class, args);
    }

}

This completes our runner service.

Note: Our runner service will run on port 8080 and user service will run on port 8081

Now let's run both services. We are using Postman for testing our services.

POST

We will create two users

{
    "firstName" : "Umang",
    "lastName" : "Agarwal",
    "email" : "ua@test.com"
},
{
    "firstName" : "John",
    "lastName" : "Doe",
    "email" : "jd@test.com"
}

GET ALL

Let's fetch all the users.

Get User By Id

Let's get the user with Id 2

PUT

Let's update the user with id 1

{
    "id": 1,
    "firstName" : "Umang",
    "lastName" : "Agarwal",
    "email" : "ua2@gmail.com"
}

Delete

Let's delete the user with id 2

We have tested all our endpoints here.

Find the source code of the project on GitHub.

Do star the repository to access the source code of all the articles.

I hope you found the article useful.

Let's connect :

Happy Coding :)