diff --git a/blog/getting-started-with-microservices/index.md b/blog/getting-started-with-microservices/index.md
index 0889831f7..f7a79081b 100644
--- a/blog/getting-started-with-microservices/index.md
+++ b/blog/getting-started-with-microservices/index.md
@@ -1,81 +1,84 @@
---
-title: 'Getting started with Microservices'
+title: "Getting started with Microservices"
sidebar_label: Microservices
-authors: [dharshibalasubramaniyam]
+authors: [dharshibalasubramaniyam, ajay-dhangar]
tags: [microservices, springboot, mongodb]
date: 2024-06-17
hide_table_of_contents: true
---
-# 1. Understanding the importance Microservices
-- In the world of software development, architecture is like the blueprint for a building. It’s the plan that shows how everything will fit together and work. Having a good architecture is crucial because it determines how easy it is to add new features, make changes, and keep the software running smoothly.
+## 1. Understanding the importance Microservices
-## 1.1. Monolithic vs Microservices
+- Microservices are an architectural style that structures an application as a collection of small, loosely coupled services. Each service is self-contained, focused on a specific business functionality, and can be developed, deployed, and scaled independently. This modular approach to software design offers several benefits, including increased agility, scalability, and resilience.
+- Microservices architecture is a design pattern that breaks down complex applications into smaller, more manageable services. Each service is responsible for a specific functionality and can communicate with other services through well-defined APIs. This modular approach enables faster development cycles, easier maintenance, and better scalability.
-
+### 1.1. Monolithic vs Microservices
-Monolithic Architecture:
-- Imagine all your items (toys, books, clothes) are packed into one giant box. Finding something specific requires searching through the entire box. Changes or updates to one item may affect the entire box.
+
-Microservices Architecture:
-- Your items are organized into several smaller boxes, each containing similar items (toys in one box, books in another, clothes in another). Finding something specific is easier because you know which box to look in. Changes or updates to one type of item (e.g., toys) don’t affect the other box.
+**Monolithic Architecture:**
-Same going to happen in software development. Instead of all-in-one (one big chunk of code) software designs called monolithic architectures, microservices break things down into smaller, more manageable parts, making it easier to develop, update, and scale software.
+- Imagine all your items (toys, books, clothes, etc.) are stored in one big box. Finding something specific can be challenging because everything is jumbled together. If you need to update or change something, you have to dig through the entire box, which can be time-consuming and error-prone.
+- Same going to happen in software development. Monolithic architectures are like one big chunk of code where all components of an application are tightly integrated. Making changes or updates to one part of the code can have unintended consequences on other parts, leading to complex and risky development and maintenance processes.
-Let’s deep dive into the differences of these 2 patterns.
+**Microservices Architecture:**
+
+- Now, imagine your items are stored in separate, labeled containers (toys in one box, books in another, clothes in a third, etc.). Finding something specific is much easier because everything is organized and accessible. If you need to update or change something, you only need to focus on the relevant container, making the process faster and more efficient.
+- Similarly, microservices architecture breaks down an application into small, independent services, each responsible for specific functionalities. This modular approach allows for faster development cycles, easier maintenance, and better scalability. Each service can be developed, deployed, and scaled independently, promoting agility and resilience in software development.
-### 1.1.1. Size and Structure
+Let’s deep dive into the differences of these 2 patterns.
-- Monolithic: One large, interconnected structure where all components of an application are tightly integrated.
-- Microservices: Composed of small, independent services, each responsible for specific functionalities of an application.
+#### 1.1.1. Size and Structure
-### 1.1.2. Development and Deployment
+- **Monolithic:** One large, interconnected structure where all components of an application are tightly integrated.
+- **Microservices:** Composed of small, independent services, each responsible for specific functionalities of an application.
-- Monolithic: Typically developed and deployed as a single unit.
-- Microservices: Each service can be developed and deployed independently, allowing for faster iteration and updates.
+#### 1.1.2. Development and Deployment
-### 1.1.3. Modification
+- **Monolithic:** Typically developed and deployed as a single unit.
+- **Microservices:** Each service can be developed and deployed independently, allowing for faster iteration and updates.
-- Monolithic: Making changes often requires modifying the entire codebase. This can be time-consuming and risky, as a change in one part of the code may inadvertently affect other parts.
-- Microservices: Each service is focused on a specific functionality, making it easier to modify and update. Changes can be made to individual services without impacting the entire application. This modular approach allows for faster development cycles and easier maintenance.
+#### 1.1.3. Modification
-### 1.1.4. Scaling
+- **Monolithic:** Making changes often requires modifying the entire codebase. This can be time-consuming and risky, as a change in one part of the code may inadvertently affect other parts.
+- **Microservices:** Each service is focused on a specific functionality, making it easier to modify and update. Changes can be made to individual services without impacting the entire application. This modular approach allows for faster development cycles and easier maintenance.
-- Monolithic: Scaling a monolithic application usually involves replicating the entire application, including components that may not require additional resources. This can lead to inefficient resource utilization.
-- Microservices: Enables granular scaling, where only the services experiencing high demand need to be scaled. This results in more efficient resource utilization and better performance scalability.
+#### 1.1.4. Scaling
-### 1.1.5. Technology Stack
+- **Monolithic:** Scaling a monolithic application usually involves replicating the entire application, including components that may not require additional resources. This can lead to inefficient resource utilization.
+- **Microservices:** Enables granular scaling, where only the services experiencing high demand need to be scaled. This results in more efficient resource utilization and better performance scalability.
-- Monolithic: Usually built using a single technology stack (e.g., one programming language, framework).
-- Microservices: Services can be built using different technologies best suited for their specific functionalities.
+#### 1.1.5. Technology Stack
-### 1.1.6. Fault Isolation and Resilience
+- **Monolithic:** Usually built using a single technology stack (e.g., one programming language, framework).
+- **Microservices:** Services can be built using different technologies best suited for their specific functionalities.
-- Monolithic: A failure in one part of the application can bring down the entire system.
-- Microservices: Faults are isolated to individual services, so a failure in one service does not necessarily impact the entire application, enhancing resilience.
+#### 1.1.6. Fault Isolation and Resilience
-### 1.1.7. Data Management
+- **Monolithic:** A failure in one part of the application can bring down the entire system.
+- **Microservices:** Faults are isolated to individual services, so a failure in one service does not necessarily impact the entire application, enhancing resilience.
-- Monolithic: Typically uses a single database shared by all components, which can lead to data coupling and scalability challenges.
-- Microservices: Each service can have its own database, allowing for better data isolation and scalability.
+#### 1.1.7. Data Management
-### 1.1.8. Testing
+- **Monolithic:** Typically uses a single database shared by all components, which can lead to data coupling and scalability challenges.
+- **Microservices:** Each service can have its own database, allowing for better data isolation and scalability.
-- Monolithic: Testing can be complex and time-consuming, as changes may impact multiple functionalities.
-- Microservices: Testing can be more focused and granular, with each service tested independently, facilitating easier debugging and maintenance.
+#### 1.1.8. Testing
-### 1.1.9. Team Organization
+- **Monolithic:** Testing can be complex and time-consuming, as changes may impact multiple functionalities.
+- **Microservices:** Testing can be more focused and granular, with each service tested independently, facilitating easier debugging and maintenance.
-- Monolithic: Development teams often work on the same codebase, leading to potential conflicts and dependencies.
-- Microservices: Teams can be organized around individual services, allowing for greater autonomy and faster development cycles.
+#### 1.1.9. Team Organization
-### 1.1.10. Communication and Integration
+- **Monolithic:** Development teams often work on the same codebase, leading to potential conflicts and dependencies.
+- **Microservices:** Teams can be organized around individual services, allowing for greater autonomy and faster development cycles.
-- Monolithic: Communication between different components typically occurs through function or method calls within the same codebase.
-- Microservices: Communication between services usually happens over network protocols like HTTP or message queues, promoting loose coupling and interoperability.
+#### 1.1.10. Communication and Integration
+- **Monolithic:** Communication between different components typically occurs through function or method calls within the same codebase.
+- **Microservices:** Communication between services usually happens over network protocols like HTTP or message queues, promoting loose coupling and interoperability.
-# 2. Developing a Microservice application
+## 2. Developing a Microservice application
- Great! Now that we have a clear understanding of microservices, let’s embark on developing a simple microservice project.
@@ -83,7 +86,8 @@ Let’s deep dive into the differences of these 2 patterns.
- Creating a microservice application involves multiple steps. Let’s navigate through them methodically, one step at a time.
-## 2.1. Developing service registry
+### 2.1. Developing service registry
+
- A service registry, like Eureka Server, plays a pivotal role in managing the dynamic nature of microservice architectures.
- The Service Registry serves as a centralized repository for storing information about all the available services in the microservices architecture. This includes details such as IP addresses, port numbers, and other metadata required for communication.
- When a microservice instance (like expense or category) starts up, it registers itself with the Service Registry.
@@ -94,11 +98,11 @@ Let’s deep dive into the differences of these 2 patterns.
- So, Let’s create a service registry for our application.
-### 2.1.1. Create a spring boot project as below.
+#### 2.1.1. Create a spring boot project as below.
-
+
-### 2.1.2. Make Changes in Your application.properties File.
+#### 2.1.2. Make Changes in Your application.properties File.
```
# application.properties
@@ -110,21 +114,19 @@ spring.application.name=service-registry
# If you want you can change
server.port=8761
-# Disables the Eureka client's capability to fetch the registry
+# Disables the Eureka client's capability to fetch the registry
# of other services from the Eureka server, as it is not acting as a Eureka client.
eureka.client.fetch-registry=false
-# Disables the Eureka client's registration with the Eureka server.
-# Since this application is the Eureka server itself,
+# Disables the Eureka client's registration with the Eureka server.
+# Since this application is the Eureka server itself,
# it does not need to register with any other Eureka server.
eureka.client.register-with-eureka=false
```
-### 2.1.2. Update ServiceRegistryApplication.java file
-
-```
-// ServiceRegistryApplication.java
+#### 2.1.2. Update ServiceRegistryApplication.java file
+```java title="ServiceRegistryApplication.java"
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistryApplication {
@@ -136,28 +138,26 @@ public class ServiceRegistryApplication {
}
```
-@EnableEurekaServerannotation
is used to enable the Eureka Server functionality in the Spring Boot application. It configures the application to act as a Eureka Server, allowing it to register and manage services within the microservices architecture.
-
+`@EnableEurekaServerannotation` is used to enable the Eureka Server functionality in the Spring Boot application. It configures the application to act as a Eureka Server, allowing it to register and manage services within the microservices architecture.
-### 2.1.3. Run the application
+#### 2.1.3. Run the application
-To see the Spring Eureka dashboard visit https://localhost:8761 url in your browser.
+To see the Spring Eureka dashboard visit `https://localhost:8761` url in your browser.
You can see the dashboard as below.
-
+
You can see that under ‘Instances currently registered with Eureka’, it displays a message “No instances available”. Because none of the microservices in our architecture have registered themselves with the Eureka Server.
+### 2.2. Create our first microservice: CATEGORY-SERVICE
-## 2.2. Create our first microservice: CATEGORY-SERVICE
+#### 2.2.1. Create a spring boot project as below.
-### 2.2.1. Create a spring boot project as below.
+
-
+#### 2.2.2. Add @EnableDiscoveryClient in CategoryServiceApplication class
-### 2.2.2. Add @EnableDiscoveryClient in CategoryServiceApplication class
-
-```
+```java title="CategoryServiceApplication.java"
@SpringBootApplication
@EnableDiscoveryClient
public class CategoryServiceApplication {
@@ -168,407 +168,403 @@ public class CategoryServiceApplication {
}
```
+
@EnableDiscoveryClient
annotation is used to enable service discovery functionality in the Spring Boot application. It signifies that this microservice will register itself with a service registry (like Eureka, Consul, etc.) upon startup and will be discoverable by other microservices within the same architecture.
-### 2.2.3. Configure application.yml file
+#### 2.2.3. Configure application.yml file
-```
+```yml title="application.yml"
spring:
-# This name is used for identifying the application in the environment
- application:
- name: category-service
-
-# MongoDB database configuration.
-# Make sure you have created the database, before running the application
- data:
- mongodb:
- host: 127.0.0.1
- port: 27017
- database: category_service
+ # This name is used for identifying the application in the environment
+ application:
+ name: category-service
+
+ # MongoDB database configuration.
+ # Make sure you have created the database, before running the application
+ data:
+ mongodb:
+ host: 127.0.0.1
+ port: 27017
+ database: category_service
# The port on which the Spring Boot application will listen for incoming HTTP requests
server:
- port: 9000
-
+ port: 9000
+
# The URL of the Eureka Server where the application will register itself for service discovery.
eureka:
- client:
- serviceUrl:
- defaultZone: http://localhost:8761/eureka/
+ client:
+ serviceUrl:
+ defaultZone: http://localhost:8761/eureka/
```
-### 2.2.4. Create category entity
+#### 2.2.4. Create category entity
-```
-@Data
-@Document(collection = "categories")
-@Builder
-public class Category {
+ ```java title="Category.java"
+ @Data
+ @Document(collection = "categories")
+ @Builder
+ public class Category {
+ @Id
+ private String id;
+ private String categoryName;
+ }
+ ```
- @Id
- private String id;
+#### 2.2.5. Create dtos for send Api responses and accept category details
- private String categoryName;
+ ```java title="CategoryDto.java"
+ @Data
+ @Builder
+ public class ApiResponseDto {
+ private boolean isSuccess;
+ private String message;
+ private T response;
+ }
-}
-```
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public class CategoryRequestDto {
+ private String name;
+ }
+ ```
-### 2.2.5. Create dtos for send Api responses and accept category details
+#### 2.2.6. Create category repository
-```
-@Data
-@Builder
-public class ApiResponseDto {
- private boolean isSuccess;
- private String message;
- private T response;
-}
+ ```java title="CategoryRepository.java"
+ @Repository
+ public interface CategoryRepository extends MongoRepository {
+ boolean existsByCategoryName(String categoryName);
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-public class CategoryRequestDto {
- private String name;
-}
-```
+ }
+ ```
-### 2.2.6. Create category repository
+#### 2.2.7. Create category service
-```
-@Repository
-public interface CategoryRepository extends MongoRepository {
- boolean existsByCategoryName(String categoryName);
+ ```java title="CategoryService.java"
+ @Service
+ public interface CategoryService {
-}
-```
+ ResponseEntity> getAllCategories();
-### 2.2.7. Create category service
+ ResponseEntity> getCategoryById(String categoryId);
-```
-@Service
-public interface CategoryService {
-
- ResponseEntity> getAllCategories();
+ ResponseEntity> createCategory(CategoryRequestDto categoryRequestDto);
+ }
+ ```
- ResponseEntity> getCategoryById(String categoryId);
+ ```java title="CategoryServiceImpl.java"
+ @Component
+ public class CategoryServiceImpl implements CategoryService {
- ResponseEntity> createCategory(CategoryRequestDto categoryRequestDto);
-}
-```
+ @Autowired
+ CategoryRepository categoryRepository;
-```
-@Component
-public class CategoryServiceImpl implements CategoryService {
-
- @Autowired
- CategoryRepository categoryRepository;
-
- @Override
- public ResponseEntity> getAllCategories(){
- List categories = categoryRepository.findAll();
- try {
- return ResponseEntity.ok(
- ApiResponseDto.builder()
- .isSuccess(true)
- .response(categories)
- .message(categories.size() + " results found!")
- .build()
- );
- }catch (Exception e) {
-// Try to create a custom exception and handle them using exception handlers
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
- ApiResponseDto.builder()
- .isSuccess(false)
- .response("Unable to process right now. Try again later!")
- .message("No results found!")
- .build()
- );
+ @Override
+ public ResponseEntity> getAllCategories(){
+ List categories = categoryRepository.findAll();
+ try {
+ return ResponseEntity.ok(
+ ApiResponseDto.builder()
+ .isSuccess(true)
+ .response(categories)
+ .message(categories.size() + " results found!")
+ .build()
+ );
+ }catch (Exception e) {
+ // Try to create a custom exception and handle them using exception handlers
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
+ ApiResponseDto.builder()
+ .isSuccess(false)
+ .response("Unable to process right now. Try again later!")
+ .message("No results found!")
+ .build()
+ );
+ }
}
- }
- @Override
- public ResponseEntity> getCategoryById(String categoryId) {
+ @Override
+ public ResponseEntity> getCategoryById(String categoryId) {
- try {
- Category category = categoryRepository.findById(categoryId).orElse(null);
- return ResponseEntity.ok(
- ApiResponseDto.builder()
+ try {
+ Category category = categoryRepository.findById(categoryId).orElse(null);
+ return ResponseEntity.ok(
+ ApiResponseDto.builder()
+ .isSuccess(true)
+ .response(category)
+ .build()
+ );
+ }catch (Exception e) {
+ // Try to create a custom exception and handle them using exception handlers
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
+ ApiResponseDto.builder()
+ .isSuccess(false)
+ .response("Unable to process right now. Try again later!")
+ .message("No results found!")
+ .build()
+ );
+ }
+ }
+
+ @Override
+ public ResponseEntity> createCategory(CategoryRequestDto categoryRequestDto) {
+ try {
+ if (categoryRepository.existsByCategoryName(categoryRequestDto.getName())) {
+ // Try to create a custom exception and handle them using exception handlers
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(
+ ApiResponseDto.builder()
+ .isSuccess(false)
+ .response("Category name already exists: " + categoryRequestDto.getName())
+ .message("Unable to create Category.")
+ .build()
+ );
+ }
+
+ Category category = Category.builder()
+ .categoryName(categoryRequestDto.getName())
+ .build();
+
+ categoryRepository.insert(category);
+ return ResponseEntity.status(HttpStatus.CREATED).body(
+ ApiResponseDto.builder()
.isSuccess(true)
- .response(category)
+ .message("Category saved successfully!")
.build()
- );
- }catch (Exception e) {
-// Try to create a custom exception and handle them using exception handlers
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
- ApiResponseDto.builder()
- .isSuccess(false)
- .response("Unable to process right now. Try again later!")
- .message("No results found!")
- .build()
- );
- }
- }
+ );
- @Override
- public ResponseEntity> createCategory(CategoryRequestDto categoryRequestDto) {
- try {
- if (categoryRepository.existsByCategoryName(categoryRequestDto.getName())) {
- // Try to create a custom exception and handle them using exception handlers
- return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(
+ }catch (Exception e) {
+ // Try to create a custom exception and handle them using exception handlers
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
ApiResponseDto.builder()
.isSuccess(false)
- .response("Category name already exists: " + categoryRequestDto.getName())
+ .response("Unable to process right now. Try again later!")
.message("Unable to create Category.")
.build()
);
}
-
- Category category = Category.builder()
- .categoryName(categoryRequestDto.getName())
- .build();
-
- categoryRepository.insert(category);
- return ResponseEntity.status(HttpStatus.CREATED).body(
- ApiResponseDto.builder()
- .isSuccess(true)
- .message("Category saved successfully!")
- .build()
- );
-
- }catch (Exception e) {
-// Try to create a custom exception and handle them using exception handlers
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
- ApiResponseDto.builder()
- .isSuccess(false)
- .response("Unable to process right now. Try again later!")
- .message("Unable to create Category.")
- .build()
- );
}
- }
-}
-```
+ }
+ ```
-### 2.3.8. Create category controller
+#### 2.3.8. Create category controller
-```
-@RestController
-@RequestMapping("/category")
-public class CategoryController {
+ ```java title="CategoryController.java"
+ @RestController
+ @RequestMapping("/category")
+ public class CategoryController {
- @Autowired
- private CategoryService categoryService;
+ @Autowired
+ private CategoryService categoryService;
- @PostMapping("/new")
- public ResponseEntity> createCategory(@RequestBody CategoryRequestDto categoryRequestDto){
- return categoryService.createCategory(categoryRequestDto);
- }
+ @PostMapping("/new")
+ public ResponseEntity> createCategory(@RequestBody CategoryRequestDto categoryRequestDto){
+ return categoryService.createCategory(categoryRequestDto);
+ }
- @GetMapping("/all")
- public ResponseEntity> getAllCategories() {
- return categoryService.getAllCategories();
- }
+ @GetMapping("/all")
+ public ResponseEntity> getAllCategories() {
+ return categoryService.getAllCategories();
+ }
- @GetMapping("/{id}")
- public ResponseEntity> getCategoryById(@PathVariable String id) {
- return categoryService.getCategoryById(id);
- }
+ @GetMapping("/{id}")
+ public ResponseEntity> getCategoryById(@PathVariable String id) {
+ return categoryService.getCategoryById(id);
+ }
-}
-```
+ }
+ ```
-### 2.3.9. Run category-service application
+#### 2.3.9. Run category-service application
- After running the service refresh eureka server in your browser. Now you can see that CATEGORY-SERVICE is displaying under available instances.
-
+ 
Then check the endpoints using postman to ensure that everything is working cool.
-
-
+
-## 2.3. Create our second microservice: EXPENSE-SERVICE
+
-### 2.3.1. Create a spring boot project as below.
+### 2.3. Create our second microservice: EXPENSE-SERVICE
-
+#### 2.3.1. Create a spring boot project as below.
-### 2.3.2. Add @EnableDiscoveryClient in CategoryServiceApplication class
+
-```
-@SpringBootApplication
-@EnableDiscoveryClient
-public class ExpenseServiceApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(ExpenseServiceApplication.class, args);
- }
+#### 2.3.2. Add @EnableDiscoveryClient in CategoryServiceApplication class
-}
-```
-
-### 2.3.3. Configure application.yml file
-
-```
-spring:
-# This name is used for identifying the application in the environment
- application:
- name: expense-service
-
-# MongoDB database configuration.
-# Make sure you have created the database, before running the application
- data:
- mongodb:
- host: 127.0.0.1
- port: 27017
- database: expense_service
-
-# The port on which the Spring Boot application will listen for incoming HTTP requests
-server:
- port: 9000
-
-# The URL of the Eureka Server where the application will register itself for service discovery.
-eureka:
- client:
- serviceUrl:
- defaultZone: http://localhost:8761/eureka/
-```
+ ```java title="ExpenseServiceApplication.java"
+ @SpringBootApplication
+ @EnableDiscoveryClient
+ public class ExpenseServiceApplication {
-### 2.3.4. Create expense entity
+ public static void main(String[] args) {
+ SpringApplication.run(ExpenseServiceApplication.class, args);
+ }
-```
-@Document(collection = "expenses")
-@Data
-@Builder
-public class Expense {
-
- @Id
- private String id;
- private String description;
- private double amount;
- private LocalDate date;
- private String categoryId;
+ }
+ ```
+
+#### 2.3.3. Configure application.yml file
+
+ ```yml title="application.yml"
+ spring:
+ # This name is used for identifying the application in the environment
+ application:
+ name: expense-service
+
+ # MongoDB database configuration.
+ # Make sure you have created the database, before running the application
+ data:
+ mongodb:
+ host: 127.0.0.1
+ port: 27017
+ database: expense_service
+
+ # The port on which the Spring Boot application will listen for incoming HTTP requests
+ server:
+ port: 9000
+
+ # The URL of the Eureka Server where the application will register itself for service discovery.
+ eureka:
+ client:
+ serviceUrl:
+ defaultZone: http://localhost:8761/eureka/
+ ```
+
+#### 2.3.4. Create expense entity
+
+ ```java title="Expense.java"
+ @Document(collection = "expenses")
+ @Data
+ @Builder
+ public class Expense {
+ @Id
+ private String id;
+ private String description;
+ private double amount;
+ private LocalDate date;
+ private String categoryId;
-}
-```
+ }
+ ```
-### 2.3.5. Create dtos for send Api responses and accept expense details
+#### 2.3.5. Create dtos for send Api responses and accept expense details
-```
-@Data
-@Builder
-public class ApiResponseDto {
- private boolean isSuccess;
- private String message;
- private T response;
-}
+ ```java title="ExpenseDto.java"
+ @Data
+ @Builder
+ public class ApiResponseDto {
+ private boolean isSuccess;
+ private String message;
+ private T response;
+ }
-@Data
-@AllArgsConstructor
-public class ExpenseRequestDto {
- private String description;
- private double amount;
- private LocalDate date;
- private String categoryId;
-}
-```
+ @Data
+ @AllArgsConstructor
+ public class ExpenseRequestDto {
+ private String description;
+ private double amount;
+ private LocalDate date;
+ private String categoryId;
+ }
+ ```
-### 2.3.6. Create expense repository
+#### 2.3.6. Create expense repository
-```
-@Repository
-public interface ExpenseRepository extends MongoRepository {
-}
-```
+ ```java title="ExpenseRepository.java"
+ @Repository
+ public interface ExpenseRepository extends MongoRepository {
+ }
+ ```
-### 2.3.7. Create expense service
+#### 2.3.7. Create expense service
-```
-@Service
-public interface ExpenseService {
- ResponseEntity> addExpense(ExpenseRequestDto requestDto);
- ResponseEntity> getAllExpenses();
-}
+ ```java title="ExpenseService.java"
+ @Service
+ public interface ExpenseService {
+ ResponseEntity> addExpense(ExpenseRequestDto requestDto);
+ ResponseEntity> getAllExpenses();
+ }
-@Component
-public class ExpenseServiceImpl implements ExpenseService{
+ @Component
+ public class ExpenseServiceImpl implements ExpenseService{
- @Autowired
- private ExpenseRepository expenseRepository;
+ @Autowired
+ private ExpenseRepository expenseRepository;
- @Override
- public ResponseEntity> addExpense(ExpenseRequestDto requestDto) {
- // will implement later
- }
+ @Override
+ public ResponseEntity> addExpense(ExpenseRequestDto requestDto) {
+ // will implement later
+ }
- @Override
- public ResponseEntity> getAllExpenses() {
- List expenses = expenseRepository.findAll();
- try {
- return ResponseEntity.ok(
- ApiResponseDto.builder()
- .isSuccess(true)
- .response(expenses)
- .message(expenses.size() + " results found!")
- .build()
- );
- }catch (Exception e) {
- // Try to create a custom exception and handle them using exception handlers
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
- ApiResponseDto.builder()
- .isSuccess(false)
- .response("Unable to process right now. Try again later!")
- .message("No results found!")
- .build()
- );
+ @Override
+ public ResponseEntity> getAllExpenses() {
+ List expenses = expenseRepository.findAll();
+ try {
+ return ResponseEntity.ok(
+ ApiResponseDto.builder()
+ .isSuccess(true)
+ .response(expenses)
+ .message(expenses.size() + " results found!")
+ .build()
+ );
+ }catch (Exception e) {
+ // Try to create a custom exception and handle them using exception handlers
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
+ ApiResponseDto.builder()
+ .isSuccess(false)
+ .response("Unable to process right now. Try again later!")
+ .message("No results found!")
+ .build()
+ );
+ }
}
}
-}
-```
-
-### 2.3.8. Create expense controller
+ ```
-```
-@RestController
-@RequestMapping("/expense")
-public class ExpenseController {
+#### 2.3.8. Create expense controller
- @Autowired
- private ExpenseService expenseService;
+ ```java title="ExpenseController.java"
+ @RestController
+ @RequestMapping("/expense")
+ public class ExpenseController {
- @PostMapping("/new")
- public ResponseEntity> addExpense(@RequestBody ExpenseRequestDto requestDto){
- return expenseService.addExpense(requestDto);
- }
+ @Autowired
+ private ExpenseService expenseService;
- @GetMapping("/all")
- public ResponseEntity> getAllExpenses(){
- return expenseService.getAllExpenses();
- }
+ @PostMapping("/new")
+ public ResponseEntity> addExpense(@RequestBody ExpenseRequestDto requestDto){
+ return expenseService.addExpense(requestDto);
+ }
+ @GetMapping("/all")
+ public ResponseEntity> getAllExpenses(){
+ return expenseService.getAllExpenses();
+ }
-}
-```
+ }
+ ```
-### 2.3.9. Run expense-service application
+#### 2.3.9. Run expense-service application
- After running the service, once again refresh eureka server in your browser. Now you can see that EXPENSE-SERVICE is displaying under available instances.
-
+ 
- Let’s check endpoints for confirmation.
-
+ 
- Done. We have created 2 microservices successfully. Let’s move to next step.
- Imagine the scenario, creating an expense, before saving expenses in the database, it’s crucial to validate that the category ID provided is valid, meaning it exists in the category table. However, the Expense Service lacks direct access to category information, as categories are managed by the Category Service. This necessitates communication between the two services.
-
-## 2.4. Communication between microservices
+### 2.4. Communication between microservices
- Before persisting the expense data, the Expense Service needs to validate the category ID present in the CategoryRequestDto.
- To verify the category ID’s validity, the Expense Service communicates with the Category Service. It sends a request containing the category ID to the Category Service.
@@ -577,126 +573,124 @@ public class ExpenseController {
- OpenFeign integrates seamlessly with Spring Cloud, providing additional features for service discovery and load balancing.
- Let’s see how expense service going to interact with category service using OpenFeign
-### 2.4.1. Add OpenFeign dependency in pom.xml
+#### 2.4.1. Add OpenFeign dependency in pom.xml
- Add below dependency in expence-service pom.xml.
-```
-
- org.springframework.cloud
- spring-cloud-starter-openfeign
-
-```
+ ```xml title="pom.xml"
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+ ```
-### 2.4.2. Create FeignClient for category service
+#### 2.4.2. Create FeignClient for category service
-```
-@FeignClient("CATEGORY-SERVICE")
-public interface CategoryFeignService {
+ ```java title="CategoryFeignService.java"
+ @FeignClient("CATEGORY-SERVICE")
+ public interface CategoryFeignService {
- @GetMapping("/category/{id}")
- ResponseEntity> getCategoryById(@PathVariable String id);
+ @GetMapping("/category/{id}")
+ ResponseEntity> getCategoryById(@PathVariable String id);
-}
-```
+ }
+ ```
-- @FeignClient
: This annotation marks the interface as a Feign client. It specifies the name of the target microservice ("CATEGORY-SERVICE"). Feign will use this name to locate the service within the service registry.
-- CategoryFeignInterface
: The interface definition for the Feign client. It declares methods that will be used to make HTTP requests to the Category Service.
-- Use the same method name, parameters you have used in the category controller in the category-service. Make sure the defined method’s implementation is available in the category-service. I have implemented the getCategoryById
method the category-service. Recall it again.
+- **`@FeignClient`:** This annotation marks the interface as a Feign client. It specifies the name of the target microservice ("CATEGORY-SERVICE"). Feign will use this name to locate the service within the service registry.
+- **`CategoryFeignInterface`:** The interface definition for the Feign client. It declares methods that will be used to make HTTP requests to the Category Service.
+- Use the same method name, parameters you have used in the category controller in the category-service. Make sure the defined method’s implementation is available in the category-service. I have implemented the `getCategoryById` method the category-service. Recall it again.
+#### 2.4.3. Update ExpenseServiceApplication.java
-### 2.4.3. Update ExpenseServiceApplication.java
+ ```java title="ExpenseServiceApplication.java"
+ @SpringBootApplication
+ @EnableDiscoveryClient
+ @EnableFeignClients
+ public class ExpenseServiceApplication {
-```
-@SpringBootApplication
-@EnableDiscoveryClient
-@EnableFeignClients
-public class ExpenseServiceApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(ExpenseServiceApplication.class, args);
+ }
- public static void main(String[] args) {
- SpringApplication.run(ExpenseServiceApplication.class, args);
- }
+ }
+ ```
-}
-```
-- @EnableFeignClients annotation enables the Feign client in the Spring Boot application. It scans the classpath for interfaces annotated with @FeignClient and generates proxy implementations for them. These proxies are used to make HTTP requests to other microservices or external APIs.
+- `@EnableFeignClients` annotation enables the Feign client in the Spring Boot application. It scans the classpath for interfaces annotated with `@FeignClient` and generates proxy implementations for them. These proxies are used to make HTTP requests to other microservices or external APIs.
+#### 2.4.4. Implement addExpense method in expense service
-### 2.4.4. Implement addExpense method in expense service
+ ```java title="ExpenseServiceImpl.java"
+ @Component
+ public class ExpenseServiceImpl implements ExpenseService{
-```
-@Component
-public class ExpenseServiceImpl implements ExpenseService{
+ @Autowired
+ private ExpenseRepository expenseRepository;
- @Autowired
- private ExpenseRepository expenseRepository;
+ @Autowired
+ private CategoryFeignService categoryFeignService;
- @Autowired
- private CategoryFeignService categoryFeignService;
+ @Override
+ public ResponseEntity> addExpense(ExpenseRequestDto requestDto) {
+ try {
- @Override
- public ResponseEntity> addExpense(ExpenseRequestDto requestDto) {
- try {
+ // fetching category from category service
+ CategoryDto category = categoryFeignService.getCategoryById(requestDto.getCategoryId()).getBody().getResponse();
- // fetching category from category service
- CategoryDto category = categoryFeignService.getCategoryById(requestDto.getCategoryId()).getBody().getResponse();
+ if (category == null) {
+ // Try to create a custom exception and handle them using exception handlers
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(
+ ApiResponseDto.builder()
+ .isSuccess(false)
+ .response("Category not exists with id: " + requestDto.getCategoryId())
+ .message("Unable to create Category.")
+ .build()
+ );
+ }
- if (category == null) {
- // Try to create a custom exception and handle them using exception handlers
- return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(
+ Expense expense = Expense.builder()
+ .description(requestDto.getDescription())
+ .amount(requestDto.getAmount())
+ .date(requestDto.getDate())
+ .categoryId(requestDto.getCategoryId())
+ .build();
+
+ expenseRepository.insert(expense);
+ return ResponseEntity.status(HttpStatus.CREATED).body(
+ ApiResponseDto.builder()
+ .isSuccess(true)
+ .message("Expense saved successfully!")
+ .build()
+ );
+
+ }catch (Exception e) {
+ // Try to create a custom exception and handle them using exception handlers
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
ApiResponseDto.builder()
.isSuccess(false)
- .response("Category not exists with id: " + requestDto.getCategoryId())
- .message("Unable to create Category.")
+ .response("Unable to process right now. Try again later!")
+ .message("Unable to save expense.")
.build()
);
}
-
- Expense expense = Expense.builder()
- .description(requestDto.getDescription())
- .amount(requestDto.getAmount())
- .date(requestDto.getDate())
- .categoryId(requestDto.getCategoryId())
- .build();
-
- expenseRepository.insert(expense);
- return ResponseEntity.status(HttpStatus.CREATED).body(
- ApiResponseDto.builder()
- .isSuccess(true)
- .message("Expense saved successfully!")
- .build()
- );
-
- }catch (Exception e) {
-// Try to create a custom exception and handle them using exception handlers
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
- ApiResponseDto.builder()
- .isSuccess(false)
- .response("Unable to process right now. Try again later!")
- .message("Unable to save expense.")
- .build()
- );
}
- }
-// getAllExpenses method here.
+ // getAllExpenses method here.
-}
-```
+ }
+ ```
-### 2.4.5. Run the application
+#### 2.4.5. Run the application
- There will be no changes in the eureka dashboard. It will be display same as before.
- Let’s check the add expense endpoint from frontend.
-
-
-
-- In wrapping up our discussion on microservices, there’s one critical aspect left to address: the challenge of accessing microservices individually via their own port numbers. This approach becomes impractical as the number of microservices, or instances thereof increases. That’s precisely where an API gateway steps in.
+ 
+ 
+- In wrapping up our discussion on microservices, there’s one critical aspect left to address: the challenge of accessing microservices individually via their own port numbers. This approach becomes impractical as the number of microservices, or instances thereof increases. That’s precisely where an API gateway steps in.
-## 2.5. Developing API gateway
+### 2.5. Developing API gateway
- Imagine having numerous microservices or multiple instances of a single microservice scattered across your architecture. Directly accessing each one via its unique port number would result in complexity and maintenance headaches. An API gateway acts as a centralized entry point for clients, providing a unified interface to access the various microservices.
- An API gateway acts as a centralized entry point for clients, providing a unified interface to access the various microservices.
@@ -705,92 +699,93 @@ public class ExpenseServiceImpl implements ExpenseService{
- Load Balancing: The API gateway can distribute incoming requests across multiple instances of a microservice, ensuring optimal resource utilization and high availability.
- Security: Centralized authentication, authorization, and security policies can be enforced at the API gateway, safeguarding the entire system from unauthorized access and attacks.
- Monitoring and Analytics: By serving as a centralized point of contact, the API gateway facilitates comprehensive monitoring, logging, and analytics of incoming and outgoing traffic, providing valuable insights into system performance and usage patterns.
-- Now let’s see how we can develop an Api-gateway for our application.
-### 2.5.1. Create spring project
+Now let’s see how we can develop an Api-gateway for our application.
-
+#### 2.5.1. Create spring project
-### 2.5.2. Add @EnableDiscoveryClient annotation ApiGatewayApplication.java
+- Create a spring boot project as below.
+ 
-```
-@SpringBootApplication
-@EnableDiscoveryClient
-public class ApiGatewayApplication {
+#### 2.5.2. Add @EnableDiscoveryClient annotation ApiGatewayApplication.java
- public static void main(String[] args) {
- SpringApplication.run(ApiGatewayApplication.class, args);
- }
-
-}
-```
+ ```java title="ApiGatewayApplication.java"
+ @SpringBootApplication
+ @EnableDiscoveryClient
+ public class ApiGatewayApplication {
-### 2.5.3. Configure application.yml file
+ public static void main(String[] args) {
+ SpringApplication.run(ApiGatewayApplication.class, args);
+ }
-```
-# The port number (8080) on which the API gateway will listen for incoming requests.
-server:
- port: 8080
-
-# The name of the Spring Boot application.
-spring:
- application:
- name: api-gateway
- cloud:
- gateway:
- mvc:
-# Enables discovery-based routing. When enabled, Spring Cloud Gateway will automatically discover
-# routes for registered services using service discovery
- discovery:
- locator:
- enabled: true
-# Defining the routing rules for accessing microservices
- routes:
- - id: category-service
- uri: lb://CATEGORY-SERVICE
- predicates:
- - Path=/category-service/**
- filters:
- - StripPrefix=1
-
- - id: expense-service
- uri: lb://EXPENSE-SERVICE
- predicates:
- - Path=/expense-service/**
- filters:
- - StripPrefix=1
-
-# The URL of the Eureka Server where the API gateway will register itself and discover other services.
-eureka:
- client:
- serviceUrl:
- defaultZone: http://localhost:8761/eureka/
-```
+ }
+ ```
+
+#### 2.5.3. Configure application.yml file
+
+ ```yml title="application.yml"
+ # The port number (8080) on which the API gateway will listen for incoming requests.
+ server:
+ port: 8080
+
+ # The name of the Spring Boot application.
+ spring:
+ application:
+ name: api-gateway
+ cloud:
+ gateway:
+ mvc:
+ # Enables discovery-based routing. When enabled, Spring Cloud Gateway will automatically discover
+ # routes for registered services using service discovery
+ discovery:
+ locator:
+ enabled: true
+ # Defining the routing rules for accessing microservices
+ routes:
+ - id: category-service
+ uri: lb://CATEGORY-SERVICE
+ predicates:
+ - Path=/category-service/**
+ filters:
+ - StripPrefix=1
+
+ - id: expense-service
+ uri: lb://EXPENSE-SERVICE
+ predicates:
+ - Path=/expense-service/**
+ filters:
+ - StripPrefix=1
+
+ # The URL of the Eureka Server where the API gateway will register itself and discover other services.
+ eureka:
+ client:
+ serviceUrl:
+ defaultZone: http://localhost:8761/eureka/
+ ```
- id: category-service
— This is an identifier for the route. It’s used internally within the API gateway configuration to refer to this specific route. It doesn’t have any significance outside of the configuration context.
- uri
: Specifies the URI (Uniform Resource Identifier) of the target microservice.
- lb://
: This prefix indicates that load balancing should be applied to the target URI. The lb stands for "load balancer".
- CATEGORY-SERVICE
: This is the logical name of the microservice registered with the service registry (Eureka). Use the same name which displayed in the eureka server.
- Predicates are conditions that must be met for a request to match this route and be forwarded to the target microservice.
-- - Path=/category-service/
: This predicate specifies that requests must have a path starting with "/category-service/" followed by any additional path segments. The "**" wildcard matches any number of additional path segments. You can use any prefix as you wish.
+- - Path=/category-service/
: This predicate specifies that requests must have a path starting with "/category-service/" followed by any additional path segments. The "\*\*" wildcard matches any number of additional path segments. You can use any prefix as you wish.
- Filters are applied to requests before they are forwarded to the target microservice. They can modify request headers, paths, or payloads, among other things.
- - StripPrefix=1
: This filter removes one path segment, effectively stripping "/category-service" from the request path. This is necessary because the routing predicate matches requests starting with "/category-service/", but the target microservice expects requests without this prefix.
-
-### 2.5.4. Test the application
+#### 2.5.4. Test the application
- For instance, let’s say you want to retrieve all expenses. You would typically use the URI localhost:8080/expense-service/expense/all.
- It matches the incoming request against the defined routes based on the configured predicates. In this case, it identifies that the request path starts with “/expense-service/”, indicating that it should be directed to the expense service.
- Before forwarding the request to the expense service, the API gateway rewrites the URI to match the expected format of the microservice. Since the expense service expects requests without the “/expense-service” prefix, the API gateway removes this prefix from the URI.
- Once the URI is properly formatted, the API gateway forwards the request to the identified microservice. In this example, it sends the request to the expense service, ensuring that it reaches the correct endpoint (“/expense/all”).
-Let’s check this in post man.
-
+
+ Let’s check this in post man.
-
-
+ 
+ 
Throughout this journey, we explored the core concepts of microservices, compared them to monolithic architectures, to developing microservices.
-Thanks for taking the time to read my article.
+Thanks for taking the time to read our article.
\ No newline at end of file
diff --git a/dsa-solutions/lc-solutions/0000-0099/0017-letter-combinations-of-a-phone-number.md b/dsa-solutions/lc-solutions/0000-0099/0017-letter-combinations-of-a-phone-number.md
deleted file mode 100644
index 26cf596b7..000000000
--- a/dsa-solutions/lc-solutions/0000-0099/0017-letter-combinations-of-a-phone-number.md
+++ /dev/null
@@ -1,315 +0,0 @@
----
-id: letter-combinations-of-a-phone-number
-title: Letter Combinations of a Phone Number (LeetCode)
-sidebar_label: 0017 Letter Combinations of a Phone Number
-tags:
- - Back Tracking
- - Mapping
- - String
-description: The problem requires generating all letter combinations corresponding to given digits (2-9). The solution utilizes backtracking to explore all combinations efficiently, employing a recursive approach in Java.
----
-
-## Problem Description
-
-| Problem Statement | Solution Link | LeetCode Profile |
-| :----------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------- |
-| [Letter Combinations of a Phone Number](https://leetcode.com/problems/Letter Combinations of a Phone Number/) | [Letter Combinations of a Phone Number Solution on LeetCode](https://leetcode.com/problems/Letter Combinations of a Phone Number/solutions/5055810/video-two-pointer-solution/) | [gabaniyash846](https://leetcode.com/u/gabaniyash846/) |
-
-### Problem Description
-
-## Problem Statement:
-Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. Return the answer in any order.
-
-### Examples
-
-#### Example 1
-
-- **Input:** `digits = "23"`
-- **Output:** `["ad","ae","af","bd","be","bf","cd","ce","cf"]`
-
-#### Example 2
-
-- **Input:** `digits = ""`
-- **Output:** `[]`
-
-
-#### Example 3
-
-- **Input:** `2`
-- **Output:** `["a","b","c"]`
-
-### Constraints:
-- `0 ≤ digits.length ≤ 4`
-- `0 ≤ digits.length ≤ 4digits[𝑖]`
-- `digits[i] is a digit in the range ['2', '9'].`
-- `A mapping of digits to letters (similar to telephone buttons) is given below. Note that 1 does not map to any letters.`
-
-### Approach
-
-1. **Mapping Digits to Letters:**
- - Define a mapping of digits to their corresponding letters, similar to telephone buttons.
-
-2. **Backtracking Function:**
- - Define a recursive backtracking function to generate all possible combinations.
- - The function takes four parameters:
- - `index`: The current index in the digits string.
- - `path`: The current combination of letters.
- - If the index is equal to the length of the digits string, it means we have reached the end of a combination, so we add it to the result list.
- - Otherwise, for each letter corresponding to the current digit, we append it to the current combination and recursively call the function with the next index.
- - After the recursive call, we remove the last character from the combination (backtracking).
-
-3. **Base Case:**
- - If the length of the current combination is equal to the length of the input digits string, we add the combination to the result list.
-
-4. **Main Function:**
- - Initialize an empty list to store the combinations.
- - Call the backtracking function with the initial index set to 0 and an empty string as the initial combination.
- - Return the list of combinations.
-
-This approach ensures that all possible combinations are generated using backtracking, and the result is returned in the desired format.
-
-### Solution Code
-#### Python
-
-```python
-class Solution:
- def letterCombinations(self, digits: str) -> List[str]:
- if not digits:
- return []
-
- digit_to_letters = {
- '2': 'abc',
- '3': 'def',
- '4': 'ghi',
- '5': 'jkl',
- '6': 'mno',
- '7': 'pqrs',
- '8': 'tuv',
- '9': 'wxyz'
- }
-
- def backtrack(index, path):
- if index == len(digits):
- combinations.append(path)
- return
- for letter in digit_to_letters[digits[index]]:
- backtrack(index + 1, path + letter)
-
- combinations = []
- backtrack(0, '')
- return combinations
-```
-
-#### Java
-
-```java
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class Solution {
- private Map digitToLetters = new HashMap<>();
-
- public Solution() {
- digitToLetters.put('2', "abc");
- digitToLetters.put('3', "def");
- digitToLetters.put('4', "ghi");
- digitToLetters.put('5', "jkl");
- digitToLetters.put('6', "mno");
- digitToLetters.put('7', "pqrs");
- digitToLetters.put('8', "tuv");
- digitToLetters.put('9', "wxyz");
- }
-
- public List letterCombinations(String digits) {
- List combinations = new ArrayList<>();
- if (digits == null || digits.isEmpty()) {
- return combinations;
- }
- backtrack(combinations, digits, 0, new StringBuilder());
- return combinations;
- }
-
- private void backtrack(List combinations, String digits, int index, StringBuilder path) {
- if (index == digits.length()) {
- combinations.add(path.toString());
- return;
- }
- String letters = digitToLetters.get(digits.charAt(index));
- for (char letter : letters.toCharArray()) {
- path.append(letter);
- backtrack(combinations, digits, index + 1, path);
- path.deleteCharAt(path.length() - 1);
- }
- }
-
- public static void main(String[] args) {
- Solution solution = new Solution();
- List result = solution.letterCombinations("23");
- System.out.println(result); // Output: [ad, ae, af, bd, be, bf, cd, ce, cf]
- }
-}
-```
-
-#### CPP:
-```cpp
-#include
-#include
-#include
-
-using namespace std;
-
-class Solution {
-private:
- unordered_map digitToLetters;
- vector combinations;
-
-public:
- Solution() {
- digitToLetters = {
- {'2', "abc"},
- {'3', "def"},
- {'4', "ghi"},
- {'5', "jkl"},
- {'6', "mno"},
- {'7', "pqrs"},
- {'8', "tuv"},
- {'9', "wxyz"}
- };
- }
-
- vector letterCombinations(string digits) {
- if (digits.empty()) return {};
- backtrack(digits, 0, "");
- return combinations;
- }
-
- void backtrack(const string& digits, int index, string path) {
- if (index == digits.length()) {
- combinations.push_back(path);
- return;
- }
- for (char letter : digitToLetters[digits[index]]) {
- backtrack(digits, index + 1, path + letter);
- }
- }
-};
-
-int main() {
- Solution solution;
- vector result = solution.letterCombinations("23");
- for (const string& comb : result) {
- cout << comb << " ";
- }
- // Output: ad ae af bd be bf cd ce cf
- return 0;
-}
-```
-
-#### JavaScript
-```js
-/**
- * @param {string} digits
- * @return {string[]}
- */
-var letterCombinations = function(digits) {
- if (digits.length === 0) return [];
-
- const digitToLetters = {
- '2': 'abc',
- '3': 'def',
- '4': 'ghi',
- '5': 'jkl',
- '6': 'mno',
- '7': 'pqrs',
- '8': 'tuv',
- '9': 'wxyz'
- };
-
- const combinations = [];
-
- const backtrack = (index, path) => {
- if (index === digits.length) {
- combinations.push(path);
- return;
- }
- const letters = digitToLetters[digits.charAt(index)];
- for (let letter of letters) {
- backtrack(index + 1, path + letter);
- }
- };
-
- backtrack(0, '');
- return combinations;
-};
-
-// Example usage:
-console.log(letterCombinations("23")); // Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]
-```
-
-#### TypeScript
-```ts
-class Solution {
- private digitToLetters: { [key: string]: string } = {
- '2': 'abc',
- '3': 'def',
- '4': 'ghi',
- '5': 'jkl',
- '6': 'mno',
- '7': 'pqrs',
- '8': 'tuv',
- '9': 'wxyz'
- };
-
- letterCombinations(digits: string): string[] {
- const combinations: string[] = [];
-
- const backtrack = (index: number, path: string): void => {
- if (index === digits.length) {
- combinations.push(path);
- return;
- }
- const letters = this.digitToLetters[digits.charAt(index)];
- for (let letter of letters) {
- backtrack(index + 1, path + letter);
- }
- };
-
- if (digits.length !== 0) {
- backtrack(0, '');
- }
-
- return combinations;
- }
-}
-
-// Example usage:
-const solution = new Solution();
-console.log(solution.letterCombinations("23")); // Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]
-```
-
-### Step-by-Step Algorithm
-
-Here's a step-by-step algorithm for generating all possible letter combinations of a given string of digits using backtracking:
-
-1. **Define a mapping of digits to letters:**
- - Create a map where each digit from 2 to 9 is mapped to its corresponding letters on a telephone keypad.
-
-2. **Define a backtracking function:**
- - The function will take the following parameters:
- - `index`: The current index in the digits string.
- - `path`: The current combination of letters.
- - If the index is equal to the length of the digits string, it means we have formed a complete combination, so add it to the result list.
- - Otherwise, for each letter corresponding to the current digit at the given index, append it to the current combination and recursively call the function with the next index.
- - After the recursive call, remove the last character from the combination (backtracking).
-
-3. **Base Case:**
- - If the length of the current combination is equal to the length of the input digits string, add the combination to the result list.
-
-4. **Main Function:**
- - Initialize an empty list to store the combinations.
- - Call the backtracking function with the initial index set to 0 and an empty string as the initial combination.
- - Return the list of combinations.
-
-This algorithm ensures that all possible combinations are generated by exploring all valid paths through backtracking.
\ No newline at end of file
diff --git a/dsa-solutions/lc-solutions/0000-0099/0023-merge-k-sorted-lists.md b/dsa-solutions/lc-solutions/0000-0099/0023-merge-k-sorted-lists.md
deleted file mode 100644
index 3c79fc976..000000000
--- a/dsa-solutions/lc-solutions/0000-0099/0023-merge-k-sorted-lists.md
+++ /dev/null
@@ -1,190 +0,0 @@
----
-id: merge-k-sorted-lists
-title: Merge k Sorted Lists (LeetCode)
-sidebar_label: 0023-MergekSortedLists
-description: Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
----
-
-## Problem Description
-
-| Problem Statement | Solution Link | LeetCode Profile |
-| :---------------- | :------------ | :--------------- |
-| [Merge k Sorted Lists](https://leetcode.com/problems/merge-k-sorted-lists/) | [Merge k Sorted Lists Solution on LeetCode](https://leetcode.com/problems/merge-k-sorted-lists/solutions/) | [vaishu_1904](https://leetcode.com/u/vaishu_1904/) |
-
-## Problem Description
-
-You are given an array of `k` linked-lists `lists`, each linked-list is sorted in ascending order.
-
-Merge all the linked-lists into one sorted linked-list and return it.
-
-### Examples
-
-#### Example 1
-
-- **Input:** `lists = [[1,4,5],[1,3,4],[2,6]]`
-- **Output:** `[1,1,2,3,4,4,5,6]`
-- **Explanation:** The linked-lists are:
- `[
- 1->4->5,
- 1->3->4,
- 2->6
- ]`
- merging them into one sorted list:
- `1->1->2->3->4->4->5->6`
-
-#### Example 2
-
-- **Input:** `lists = []`
-- **Output:** `[]`
-
-#### Example 3
-
-- **Input:** `lists = [[]]`
-- **Output:** `[]`
-
-### Constraints
-
-- `k == lists.length`
-- $0 <= k <= 10^4$
-- $0 <= lists[i].length <= 500$
-- $-10^4 <= lists[i][j] <= 10^4$
-- `lists[i]` is sorted in ascending order.
-- The sum of `lists[i].length` will not exceed `10^4`.
-
-### Approach
-
-To merge `k` sorted linked lists, we can use a min-heap (priority queue) to efficiently get the smallest node among the current heads of the lists. This ensures that we always add the smallest element to the merged list.
-
-1. **Initialize the Min-Heap:**
- - Add the first node of each linked list to the heap.
-
-2. **Build the Merged List:**
- - Extract the smallest node from the heap and add it to the merged list.
- - If the extracted node has a next node, add it to the heap.
- - Repeat until the heap is empty.
-
-### Solution Code
-
-#### Python
-
-```python
-from heapq import heappop, heappush
-
-class ListNode:
- def __init__(self, val=0, next=None):
- self.val = val
- self.next = next
-
- def __lt__(self, other):
- return self.val < other.val
-
-class Solution:
- def mergeKLists(self, lists):
- min_heap = []
-
- # Initialize the heap
- for l in lists:
- if l:
- heappush(min_heap, l)
-
- dummy = ListNode()
- current = dummy
-
- # Extract the minimum node from the heap
- while min_heap:
- node = heappop(min_heap)
- current.next = node
- current = current.next
- if node.next:
- heappush(min_heap, node.next)
-
- return dummy.next
-```
-#### Java
-
-```java
-import java.util.PriorityQueue;
-
-class ListNode {
- int val;
- ListNode next;
- ListNode(int x) { val = x; }
-}
-
-class Solution {
- public ListNode mergeKLists(ListNode[] lists) {
- PriorityQueue minHeap = new PriorityQueue<>((a, b) -> a.val - b.val);
-
- for (ListNode list : lists) {
- if (list != null) {
- minHeap.offer(list);
- }
- }
-
- ListNode dummy = new ListNode(0);
- ListNode current = dummy;
-
- while (!minHeap.isEmpty()) {
- ListNode node = minHeap.poll();
- current.next = node;
- current = current.next;
- if (node.next != null) {
- minHeap.offer(node.next);
- }
- }
-
- return dummy.next;
- }
-}
-
-```
-
-#### C++
-
-``` c++
-#include
-
-struct ListNode {
- int val;
- ListNode *next;
- ListNode(int x) : val(x), next(nullptr) {}
-};
-
-struct compare {
- bool operator()(ListNode* a, ListNode* b) {
- return a->val > b->val;
- }
-};
-
-class Solution {
-public:
- ListNode* mergeKLists(vector& lists) {
- priority_queue, compare> minHeap;
-
- for (ListNode* list : lists) {
- if (list != nullptr) {
- minHeap.push(list);
- }
- }
-
- ListNode dummy(0);
- ListNode* current = &dummy;
-
- while (!minHeap.empty()) {
- ListNode* node = minHeap.top();
- minHeap.pop();
- current->next = node;
- current = current->next;
- if (node->next != nullptr) {
- minHeap.push(node->next);
- }
- }
-
- return dummy.next;
- }
-};
-
-```
-
-#### Conclusion
-The above solutions effectively merge k sorted linked lists into a single sorted list using a min-heap. This approach ensures that the smallest element is always added next to the merged list, maintaining sorted order. This solution efficiently handles edge cases and returns the correct merged list for various input configurations.
diff --git a/dsa-solutions/lc-solutions/0100-0199/0137-Single-Number-II b/dsa-solutions/lc-solutions/0100-0199/0137-single-number-2.md
similarity index 99%
rename from dsa-solutions/lc-solutions/0100-0199/0137-Single-Number-II
rename to dsa-solutions/lc-solutions/0100-0199/0137-single-number-2.md
index 378e5c72f..cbb6c04a4 100644
--- a/dsa-solutions/lc-solutions/0100-0199/0137-Single-Number-II
+++ b/dsa-solutions/lc-solutions/0100-0199/0137-single-number-2.md
@@ -1,5 +1,5 @@
---
-id: Single-Number-II
+id: single-number-ii
title: Single Number II
sidebar_label: 0136 Single Number II
tags:
@@ -16,8 +16,6 @@ Given an integer array nums where every element appears three times except for o
You must implement a solution with a linear runtime complexity and use only constant extra space.
-
-
### Examples
**Example 1:**
diff --git a/dsa-solutions/lc-solutions/0100-0199/0141-linked-list-cycle.md b/dsa-solutions/lc-solutions/0100-0199/0141-linked-list-cycle.md
index c2e8a9882..3339474d4 100644
--- a/dsa-solutions/lc-solutions/0100-0199/0141-linked-list-cycle.md
+++ b/dsa-solutions/lc-solutions/0100-0199/0141-linked-list-cycle.md
@@ -155,3 +155,4 @@ var hasCycle = function (head) {
return false;
};
```
+
diff --git a/dsa-solutions/lc-solutions/0100-0199/0141-linked-list-cycles.md b/dsa-solutions/lc-solutions/0100-0199/0141-linked-list-cycles.md
deleted file mode 100644
index 5cc0b5d89..000000000
--- a/dsa-solutions/lc-solutions/0100-0199/0141-linked-list-cycles.md
+++ /dev/null
@@ -1,103 +0,0 @@
----
-id: linked-list-cycle
-title: Linked List Cycle(LeetCode)
-sidebar_label: 0141-Linked List Cycle
-tags:
- - Linked list
- - Hash Table
- - Two pointer
-description: Given head, the head of a linked list, determine if the linked list has a cycle in it.
----
-
-## Problem Statement
-
-Given `head`, the head of a linked list, determine if the linked list has a cycle in it.
-
-There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the `next` pointer. Internally, `pos` is used to denote the index of the node that tail's `next` pointer is connected to. Note that `pos` is not passed as a parameter.
-
-Return `true` if there is a cycle in the linked list. Otherwise, return `false`.
-
-### Examples
-
-**Example 1:**
-
-
-
-```plaintext
-Input: head = [3,2,0,-4], pos = 1
-Output: true
-Explanation: There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed).
-```
-
-**Example 2:**
-
-
-
-```plaintext
-Input: head = [1,2], pos = 0
-Output: true
-Explanation: There is a cycle in the linked list, where the tail connects to the 0th node.
-```
-
-**Example 3:**
-
-
-
-```plaintext
-Input: head = [1], pos = -1
-Output: false
-Explanation: There is no cycle in the linked list.
-```
-
-### Constraints
-
-- The number of the nodes in the list is in the range `[0, 104]`.
-- `-105 <= Node.val <= 105`
-- `pos` is `-1` or a valid index in the linked-list.
-
-## Solution
-
-This algorithm, also known as Floyd's Cycle-Finding Algorithm or the "tortoise and the hare algorithm," is used to detect cycles in a linked list. It uses two pointers, one moving at twice the speed of the other, to detect if the linked list contains a cycle.
-
-### Approach
-
-#### Algorithm
-
-1. Initialize two pointers, `slow` and `fast`, pointing to the head of the linked list.
-2. Move `slow` one step at a time and `fast` two steps at a time.
-3. If `fast` reaches the end of the list (i.e., `fast` or `fast->next` is `NULL`), then there is no cycle, so return false.
-4. If `fast` meets `slow` at any point during traversal, then there is a cycle in the linked list, so return true.
-
-#### Implementation
-
-```C++
-class Solution {
-public:
- bool hasCycle(ListNode *head) {
- ListNode *fast = head;
- ListNode *slow = head;
-
- // Traverse the linked list
- while (fast != NULL && fast->next != NULL) {
- fast = fast->next->next; // Move fast pointer two steps
- slow = slow->next; // Move slow pointer one step
-
- // Check if fast and slow pointers meet
- if (fast == slow) {
- return true; // Cycle detected
- }
- }
-
- return false; // No cycle found
- }
-};
-```
-
-### Complexity Analysis
-
-- **Time complexity**: The slow pointer traverses the list once, and the fast pointer traverses it twice. Therefore, the time complexity is O(N), where N is the number of nodes in the linked list.
-- **Space complexity**: The algorithm uses only two pointers (`slow` and `fast`) and has a constant space complexity of O(1).
-
-### Conclusion
-
-The Floyd's Cycle-Finding Algorithm is an efficient way to detect cycles in a linked list using only two pointers and constant space. It is a popular algorithm due to its simplicity and effectiveness in solving this problem.