# PRODUCT-MANAGEMENT-SERVICE

## Objective:

By the end of this session, students will be able to create a robust Spring Boot backend service for product management with proper entity relationships, RESTful APIs, and CRUD operations. The system will manage categories and products with a many-to-one relationship.

## Technology Stack:

- Java 21+
- Spring Boot 3.44
- Spring Data JPA
- Gradle
- MySQL

## 1. Introduction (10 minutes)

- Overview of Object-Oriented Programming in Java.
- Introduction to Java Spring Boot framework.
- Brief explanation on Spring Boot architectures.
- Introduction to Swagger UI for API testing.

## 2. Project Setup (15 minutes)

### SQL Setup

- Install MySQL Server by following this url [click here](https://dev.mysql.com/downloads/installer/).
- After installing the MySQL server, add the path in System Variables.
  - Copy the path of the MySQL server which will like this `C:\Program Files\MySQL\MySQL Server 8.0\bin` for most cases.
  - Open Environment Variables.
  - Click Environment Variables > Under System Variables, find Path > Edit.
  - Add the copied path of MySQL server.
  - Click OK to save.
- Install SQLyog by following this url [click here](https://github.com/webyog/sqlyog-community/wiki/Downloads).

### JDK Setup

- Install Java version 21 by following this link [click here](https://www.oracle.com/java/technologies/javase/jdk21-archive-downloads.html).
- Upon installing JDK 21, open Environment Variables.
- Under System Variables, click New.
  - Variable Name: JAVA_HOME.
  - Variable Value: C:\Program Files\Java\jdk-21 (or your JDK path)
  - Click OK to save.
- Add Java to PATH:
  - Find Path under System Variables > Click Edit > New.
  - Add: %JAVA_HOME%\bin
  - Click OK to save.

### Spring Boot Setup

- Create new project in [Spring initializr](https://start.spring.io/).
- Choose Java, Gradle, and the spring version 3.44.
- Create a name for the project.
- Choose the packaging as Jar and the Java version 21.
- Choose the needed dependencies:
  - Spring Web
  - Spring Data JPA
  - MySQL Driver
  - Lombok

### 3. Project Structure

- Most folders will be created by spring initializr.
- Create folder/packages and files under src\main\java\com\ctsb\my_product_management_svc path according to project structure below.
- Brief overview of the project's folder structure.

```bash
my-product-management-svc
├───.gradle
├───.idea
├───build
├───gradle
└───src
    ├───main
    │   ├───java
    │   │   └───com
    │   │       ├───ctsb
    │   │          └───my_product_management_svc
    │   │              ├───category
    │   │              │    ├───Category.java
    │   │              │    ├───CategoryController.java
    │   │              │    ├───CategoryDto.java
    │   │              │    ├───CategoryRepository.java
    │   │              │    ├───CategoryService.java
    │   │              ├───config
    │   │              │    ├───OpenApiConfig.java
    │   │              │    ├───SwaggerConfig.java
    │   │              └───product
    │   │              │    ├───Product.java
    │   │              │    ├───ProductController.java
    │   │              │    ├───ProductDto.java
    │   │              │    ├───ProductRepository.java
    │   │              │    ├───ProductService.java
    │   │              │    ├───Status.java
    │   │
    │   └───resources
    │
    │
    └───test
```

### 4. Setting up Database

- In the application.properties file under the resources folder add the database configuration.

```
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/productmngm?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=01234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update

```

## Core Implementation:

### 5. Creating Entities (15 minutes)

- Deep dive into creating entities.
- Create Category.java class under category package.
- Create Product.java class under product package.
- Discuss attributes and relationships between entities.

#### Category Entity:

##### i. Importing packages and classes

```
package com.ctsb.my_product_management_svc.category;

import com.ctsb.my_product_management_svc.product.Product;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
```

##### ii. Adding annotations

```
@Getter
@Setter
@Entity
@Table(name = "category")
```

##### iii. Defining class and fields

```
public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(nullable = false, unique = true)
    private String name;

    private String description;
    private Boolean active;

    @CreationTimestamp
    private LocalDateTime createdAt;

    @UpdateTimestamp
    private LocalDateTime updatedAt;

}
```

#### Product Entity:

##### i. Importing packages and classes

```
package com.ctsb.my_product_management_svc.product;

import com.ctsb.my_product_management_svc.category.Category;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
```

##### ii. Adding annotations

```
@Getter
@Setter
@Entity
@Table(name = "product")
```

##### iii. Defining class and fields

```
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    private String name;
    private String description;
    private Double price;
    private Integer stockCount;

    @Enumerated(EnumType.STRING)
    private Status status;

    @CreationTimestamp
    private LocalDateTime createdAt;

    @UpdateTimestamp
    private LocalDateTime updatedAt;
}
```

##### iv. Creating Enum for product entity

```Java
package com.ctsb.my_product_management_svc.product;

public enum Status {
    IN_STOCK,
    OUT_OF_STOCK,
    DISCONTINUED
}
```

##### v. Declaring relationship between category and product

```
    //Declare one-to-many relationship to product in category
    @OneToMany(mappedBy = "category", orphanRemoval = true)
    @JsonIgnore
    private List<Product> products = new ArrayList<>();

    //Declare many-to-one relationship to category in product
    @ManyToOne
    @JoinColumn(name = "category_id", nullable = false)
    private Category category;
```

<details>
<summary>Full code of category entity</summary>

```Java
package com.ctsb.my_product_management_svc.category;

import com.ctsb.my_product_management_svc.product.Product;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.\*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
@Entity
@Table(name = "category")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;

    @Column(nullable = false, unique = true)
    private String name;

    private String description;
    private Boolean active;

    @CreationTimestamp
    private LocalDateTime createdAt;

    @UpdateTimestamp
    private LocalDateTime updatedAt;

    @OneToMany(mappedBy = "category", orphanRemoval = true)
    @JsonIgnore
    private List<Product> products = new ArrayList<>();
}

```

</details>

<details>
<summary>Full code of product entity</summary>

```Java
package com.ctsb.my_product_management_svc.product;

import com.ctsb.my_product_management_svc.category.Category;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;

@Getter
@Setter
@Entity
@Table(name = "product")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    private String name;
    private String description;
    private Double price;
    private Integer stockCount;

    @Enumerated(EnumType.STRING)
    private Status status;

    @CreationTimestamp
    private LocalDateTime createdAt;

    @UpdateTimestamp
    private LocalDateTime updatedAt;

    @ManyToOne
    @JoinColumn(name = "category_id", nullable = false)
    private Category category;
}
```

</details>

### 6. Creating Repositories (10 minutes)

- Deep dive into creating repositories.
- Create CategoryRepository.java interface under category package.
- Create ProductRepository.java interface under product package.
- Discuss Spring Data JPA.

#### i. Creating CategoryRepository

```Java
package com.ctsb.my_product_management_svc.category;

import org.springframework.data.jpa.repository.JpaRepository;

public interface CategoryRepository extends JpaRepository<Category, Long> {
}
```

#### ii. Creating ProductRepository

```Java
package com.ctsb.my_product_management_svc.product;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
}
```

### 6. Creating Data Transfer Object(DTO) (10 minutes)

- Deep dive into creating DTOs.
- Create CategoryDto.java record under category package.
- Create ProductDto.java record under product package.
- Discuss on how Dto works.

#### i. Creating CategoryDto

```Java
package com.ctsb.my_product_management_svc.category;

public record CategoryDto(
        String name,
        String description,
        Boolean active
) {
}
```

#### ii. Creating ProductDto

```Java
package com.ctsb.my_product_management_svc.product;

public record ProductDto(
        Long categoryId,
        String name,
        String description,
        Double price,
        Integer stockCount,
        Status status
) {
}

```

### 7. Creating Service Layer (60 minutes)

- Deep dive into creating service layer.
- Create CategoryService.java class under category package.
- Create ProductService.java class under product package.
- Discuss what is service layer and basic CRUD logics.

#### Category Service

##### i. Importing necessary packages and classes

```
package com.ctsb.my_product_management_svc.category;

import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

import java.time.LocalDateTime;
import java.util.List;
```

##### ii. Adding necessary annotations

```
@Service
@AllArgsConstructor // Lombok annotation for constructor-based dependency injection
```

##### iii. Calling repository and declare error message

```
public class CategoryService {

    private final CategoryRepository categoryRepository; // Handles database operations
    private static final String NOT_FOUND = "Category with %s id is not found!"; // Error message template
}
```

##### iv. Creating mapper method for create operation

```
    // Maps DTO to Entity
    private Category mapper(CategoryDto categoryDto){
        var category = new Category();
        category.setName(categoryDto.name());
        category.setDescription(categoryDto.description());
        category.setActive(categoryDto.active());
        return category;
    }
```

##### v. Creating logics for CRUD operations

```
    // Get all categories
    public List <Category> findAll(){
            return categoryRepository.findAll();
        }

    // Get single category by ID (throws 404 if not found)
    public Category findById(Long id){
        return categoryRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format(NOT_FOUND,id))
        );
    }

    // Create new category with current timestamp
    public Category create(CategoryDto categoryDto){
        Category category = mapper(categoryDto);
        category.setCreatedAt(LocalDateTime.now());
        category.setUpdatedAt(null);
        return categoryRepository.save(category);
    }

    // Update existing category (partial update)
    public Category update(Long id, CategoryDto categoryDto){

        // Reuse findById for 404 check
        Category existingCategory = categoryRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format(NOT_FOUND,id))
        );

        // Only update non-null fields from DTO
        if(categoryDto.name() != null){
            existingCategory.setName(categoryDto.name());
        }
        if(categoryDto.description() != null){
            existingCategory.setDescription(categoryDto.description());
        }
        if(categoryDto.active() != null){
            existingCategory.setActive(categoryDto.active());
        }

        existingCategory.setUpdatedAt(LocalDateTime.now());
        return categoryRepository.save(existingCategory);
    }

     // Delete category by ID (throws 404 if not found)
    public void delete(Long id){

        // Reuse findById for 404 check
        Category category = categoryRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format(NOT_FOUND,id))
        );
        categoryRepository.deleteById(id);
    }
```

<details>

<summary> Full code of Category Service </summary>

```Java
package com.ctsb.my_product_management_svc.category;

import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.List;

@Service
@AllArgsConstructor
public class CategoryService {

    private final CategoryRepository categoryRepository;
    private static final String NOT_FOUND = "Category with %s id is not found!";

    public List <Category> findAll(){
        return categoryRepository.findAll();
    }

    public Category findById(Long id){
        return categoryRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format(NOT_FOUND,id))
        );
    }

    public Category create(CategoryDto categoryDto){
        Category category = mapper(categoryDto);
        category.setCreatedAt(LocalDateTime.now());
        category.setUpdatedAt(null);
        return categoryRepository.save(category);
    }

    public Category update(Long id, CategoryDto categoryDto){
        Category existingCategory = categoryRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format(NOT_FOUND,id))
        );
        if(categoryDto.name() != null){
            existingCategory.setName(categoryDto.name());
        }
        if(categoryDto.description() != null){
            existingCategory.setDescription(categoryDto.description());
        }
        if(categoryDto.active() != null){
            existingCategory.setActive(categoryDto.active());
        }
        existingCategory.setUpdatedAt(LocalDateTime.now());
        return categoryRepository.save(existingCategory);
    }

    public void delete(Long id){
        Category category = categoryRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format(NOT_FOUND,id))
        );
        categoryRepository.deleteById(id);
    }

    private Category mapper(CategoryDto categoryDto){
        var category = new Category();
        category.setName(categoryDto.name());
        category.setDescription(categoryDto.description());
        category.setActive(categoryDto.active());
        return category;
    }
}

```

</details>

#### Product Service

##### i. Importing necessary packages and classes

```
package com.ctsb.my_product_management_svc.product;

import com.ctsb.my_product_management_svc.category.Category;
import com.ctsb.my_product_management_svc.category.CategoryRepository;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.List;
```

##### ii. Adding necessary annotations

```
@Service
@AllArgsConstructor
```

##### iii. Calling repository and declare error message

```
public class ProductService {

    private final ProductRepository productRepository;  // Handles product database operations
    private final CategoryRepository categoryRepository;    // Handles category lookups
    private static final String NOT_FOUND = "Product with %s id is not found!"; // Error message template
}
```

##### iv. Creating mapper method for create operation

```
     // Maps DTO to Product entity (without category)
    private Product mapper(ProductDto productDto){
        var product = new Product();
        product.setName(productDto.name());
        product.setDescription(productDto.description());
        product.setPrice(productDto.price());
        product.setStockCount(productDto.stockCount());
        product.setStatus(productDto.status());
        return product;
    }
```

##### v. Creating logics for CRUD operations

```
    // Get all products
    public List<Product> findAll(){
        return productRepository.findAll();
    }

    // Get single product by ID (throws 404 if not found)
    public Product findById(Long id){
        return productRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format(NOT_FOUND,id))
        );
    }

    // Create new product with category association
    public Product create(ProductDto productDto){

        // Verify category exists
        Category category = categoryRepository.findById(productDto.categoryId()).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND,"Category not found!")
        );
        Product product = mapper(productDto);
        product.setCategory(category);  // Set category relationship
        product.setCreatedAt(LocalDateTime.now());
        product.setUpdatedAt(null);
        return productRepository.save(product);
    }

     // Update existing product (partial update)
    public Product update(Long id, ProductDto productDto){

        // Verify product exists
        Product existingProduct = productRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format(NOT_FOUND,id))
        );

        // Verify new category exists if being updated
        Category category = categoryRepository.findById(productDto.categoryId()).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Category not found!")
        );

        // Update only non-null fields from DTO
        if(productDto.categoryId() != null){
            existingProduct.setCategory(category);
        }
        if(productDto.name() != null){
            existingProduct.setName(productDto.name());
        }
        if(productDto.description() != null){
            existingProduct.setDescription(productDto.description());
        }
        if(productDto.price() != null){
            existingProduct.setPrice(productDto.price());
        }
        if(productDto.stockCount() != null){
            existingProduct.setStockCount(productDto.stockCount());
        }
        if(productDto.status() != null){
            existingProduct.setStatus(productDto.status());
        }
        if(existingProduct.getCreatedAt() != null){
            existingProduct.setCreatedAt(LocalDateTime.now());
        }
        existingProduct.setUpdatedAt(LocalDateTime.now());
        return productRepository.save(existingProduct);
    }

    // Delete product by ID (throws 404 if not found)
    public void delete(Long id){

        // Verify exists before delete
        Product product = productRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format(NOT_FOUND,id))
        );
        productRepository.deleteById(id);
    }
```

<details>

<summary> Full code of Product Service </summary>

```Java
package com.ctsb.my_product_management_svc.product;

import com.ctsb.my_product_management_svc.category.Category;
import com.ctsb.my_product_management_svc.category.CategoryRepository;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.List;

@Service
@AllArgsConstructor
public class ProductService {

    private final ProductRepository productRepository;
    private final CategoryRepository categoryRepository;
    private static final String NOT_FOUND = "Product with %s id is not found!";

    public List<Product> findAll(){
        return productRepository.findAll();
    }

    public Product findById(Long id){
        return productRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format(NOT_FOUND,id))
        );
    }

    public Product create(ProductDto productDto){
        Category category = categoryRepository.findById(productDto.categoryId()).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND,"Category not found!")
        );
        Product product = mapper(productDto);
        product.setCategory(category);
        product.setCreatedAt(LocalDateTime.now());
        product.setUpdatedAt(null);
        return productRepository.save(product);
    }

    public Product update(Long id, ProductDto productDto){
        Product existingProduct = productRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format(NOT_FOUND,id))
        );
        Category category = categoryRepository.findById(productDto.categoryId()).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Category not found!")
        );
        if(productDto.categoryId() != null){
            existingProduct.setCategory(category);
        }
        if(productDto.name() != null){
            existingProduct.setName(productDto.name());
        }
        if(productDto.description() != null){
            existingProduct.setDescription(productDto.description());
        }
        if(productDto.price() != null){
            existingProduct.setPrice(productDto.price());
        }
        if(productDto.stockCount() != null){
            existingProduct.setStockCount(productDto.stockCount());
        }
        if(productDto.status() != null){
            existingProduct.setStatus(productDto.status());
        }
        if(existingProduct.getCreatedAt() != null){
            existingProduct.setCreatedAt(LocalDateTime.now());
        }
        existingProduct.setUpdatedAt(LocalDateTime.now());
        return productRepository.save(existingProduct);
    }

    public void delete(Long id){
        Product product = productRepository.findById(id).orElseThrow(
                () -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format(NOT_FOUND,id))
        );
        productRepository.deleteById(id);
    }

    private Product mapper(ProductDto productDto){
        var product = new Product();
        product.setName(productDto.name());
        product.setDescription(productDto.description());
        product.setPrice(productDto.price());
        product.setStockCount(productDto.stockCount());
        product.setStatus(productDto.status());
        return product;
    }
}

```

</details>

### 8. Creating Controller Layer (45 minutes)

- Deep dive into creating controller layer.
- Create CategoryController.java class under category package.
- Create ProductController.java class under product package.
- Discuss what is controller layer and RESTful APIs.

#### Category Controller

##### i. Importing necessary packages and classes

```
package com.ctsb.my_product_management_svc.category;

import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
```

##### ii. Adding necessary annotations

```
@RestController
@RequestMapping("/categories")  // Base path for all endpoints in this controller
@AllArgsConstructor
```

##### iii. Calling category service class

```
public class CategoryController {

    private final CategoryService categoryService;  // Handles business logic
}
```

##### iv. Creating endpoints for logic in service layer

```
    // GET endpoint to retrieve all categories
    @GetMapping("/find")
    public ResponseEntity<List<Category>> findAll(){
        List<Category> categories = categoryService.findAll();
        return ResponseEntity.ok(categories);
    }

    // GET endpoint to retrieve a single category by ID
    @GetMapping("/find/{id}")
    public ResponseEntity<Category>findById(@PathVariable Long id){
        Category category = categoryService.findById(id);
        return ResponseEntity.ok(category);
    }

    // POST endpoint to create a new category
    @PostMapping("/add")
    public ResponseEntity<Category> create(@RequestBody CategoryDto categoryDto){
        Category category = categoryService.create(categoryDto);
        return ResponseEntity.ok(category);
    }

    // PUT endpoint to update an existing category
    @PutMapping("/update/{id}")
    public ResponseEntity<Category> update(@PathVariable Long id, @RequestBody CategoryDto categoryDto){
        Category category = categoryService.update(id, categoryDto);
        return ResponseEntity.ok(category);
    }

    // DELETE endpoint to remove a category
    @DeleteMapping("/delete/{id}")
   public ResponseEntity<String> delete(@PathVariable Long id){
        categoryService.delete(id);
        return ResponseEntity.ok("Category deleted!");
    }
```

<details>

<summary> Full code for Category Controller </summary>

```Java
package com.ctsb.my_product_management_svc.category;

import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/categories")
@AllArgsConstructor
public class CategoryController {

    private final CategoryService categoryService;

    @GetMapping("/find")
    public ResponseEntity<List<Category>> findAll(){
        List<Category> categories = categoryService.findAll();
        return ResponseEntity.ok(categories);
    }

    @GetMapping("/find/{id}")
    public ResponseEntity<Category>findById(@PathVariable Long id){
        Category category = categoryService.findById(id);
        return ResponseEntity.ok(category);
    }

    @PostMapping("/add")
    public ResponseEntity<Category> create(@RequestBody CategoryDto categoryDto){
        Category category = categoryService.create(categoryDto);
        return ResponseEntity.ok(category);
    }

    @PutMapping("/update/{id}")
    public ResponseEntity<Category> update(@PathVariable Long id, @RequestBody CategoryDto categoryDto){
        Category category = categoryService.update(id, categoryDto);
        return ResponseEntity.ok(category);
    }

    @DeleteMapping("/delete/{id}")
   public ResponseEntity<String> delete(@PathVariable Long id){
        categoryService.delete(id);
        return ResponseEntity.ok("Category deleted!");
    }
}

```

</details>

#### Product Controller

##### i. Importing necessary packages and classes

```
package com.ctsb.my_product_management_svc.product;

import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
```

##### ii. Adding necessary annotations

```
@RestController
@RequestMapping("/products")
@AllArgsConstructor
```

##### iii. Calling product service class

```
public class ProductController {

    private final ProductService productService;    // Handles business logic
}
```

##### iv. Creating endpoints for logic in service layer

```
    // GET endpoint to retrieve all products
    @GetMapping("/find")
    public ResponseEntity<List<Product>> findAll(){
        List<Product> products = productService.findAll();
        return ResponseEntity.ok(products);
    }

    // GET endpoint to retrieve a single product by ID
    @GetMapping("/find/{id}")
    public ResponseEntity<Product> findById(@PathVariable Long id){
        Product product = productService.findById(id);
        return ResponseEntity.ok(product);
    }

     // Post endpoint to create a new product
    @PostMapping("/add")
    public ResponseEntity<Product> create(@RequestBody ProductDto productDto){
        Product product = productService.create(productDto);
        return ResponseEntity.ok(product);
    }

    // PUT endpoint to update an existing product
    @PutMapping("/update/{id}")
    public ResponseEntity<Product> update(
            @PathVariable Long id,
            @RequestBody ProductDto productDto
    ){
        Product product = productService.update(id,productDto);
        return ResponseEntity.ok(product);
    }

    // DELETE endpoint to remove a product
    @DeleteMapping("/delete/{id}")
    public ResponseEntity<String> delete(@PathVariable Long id){
        productService.delete(id);
        return ResponseEntity.ok("Product deleted!");
    }
```

<details>

<summary> Full code for Product Controller </summary>

```Java
package com.ctsb.my_product_management_svc.product;

import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/products")
@AllArgsConstructor
public class ProductController {

    private final ProductService productService;

    @GetMapping("/find")
    public ResponseEntity<List<Product>> findAll(){
        List<Product> products = productService.findAll();
        return ResponseEntity.ok(products);
    }

    @GetMapping("/find/{id}")
    public ResponseEntity<Product> findById(@PathVariable Long id){
        Product product = productService.findById(id);
        return ResponseEntity.ok(product);
    }

    @PostMapping("/add")
    public ResponseEntity<Product> create(@RequestBody ProductDto productDto){
        Product product = productService.create(productDto);
        return ResponseEntity.ok(product);
    }

    @PutMapping("/update/{id}")
    public ResponseEntity<Product> update(
            @PathVariable Long id,
            @RequestBody ProductDto productDto
    ){
        Product product = productService.update(id,productDto);
        return ResponseEntity.ok(product);
    }

    @DeleteMapping("/delete/{id}")
    public ResponseEntity<String> delete(@PathVariable Long id){
        productService.delete(id);
        return ResponseEntity.ok("Product deleted!");
    }
}

```

</details>

### 9. OpenApi Configuration (30 minutes)

- Deep dive into OpenApi condiguration and documentation.
- Create OpenApiConfig.java class under config package.
- Create SwaggerConfig.java class under config package.
- Discuss what is OpenApi and Swagger UI.

#### Adding Dependency

- Add OpenApi dependency in build.gradle file.
- Build the project file.

```
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.5' // Add this dependency
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.mysql:mysql-connector-j'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
```

#### Setting up OpenApiConfig file

##### i. Packages and classes

```
package com.ctsb.my_product_management_svc.config;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.info.License;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
```

##### ii. OpenApi Definition

```
@OpenAPIDefinition(
        info = @Info(
                                                                                        // Contact information for support
                contact = @Contact(
                        name = "Thaswin Muralikaran",
                        email = "thaswin@cybersolution.com.my",
                        url = "https://cybersolution.com.my/"
                ),
                                                                                        //Detailed API description
                description = "OpenApi documentation for Product Management System",
                title = "OpenApi specification - Product Management Service",
                version = "1.0",                                                        //Versioning information
                license = @License(
                                                                                        //Licensing details
                        name = "Cybersolution Technologies Sdn Bhd",
                        url = "https://cybersolution.com.my/"
                ),
                termsOfService = "Terms of service"
        ),
        security = {                                                                     // Applies security to all endpoints
                @SecurityRequirement(
                        name = "bearerAuth"
                )
        }
)
```

##### iii. Security Configuration

```
@SecurityScheme(
        name = "bearerAuth",                                // Name referenced in SecurityRequirement
        description = "Input JWT token for authentication",
        type = SecuritySchemeType.HTTP,                     // Uses HTTP authentication
        scheme = "bearer",                                  // Bearer token scheme
        bearerFormat = "JWT"                                // Specifies JWT token format
)
```

##### iii. OpenApi config class

```
public class OpenApiConfig {
    // Configuration-only class - no methods needed
    // Spring will automatically pick up these annotations
}
```

<details>

<summary> Full code of OpenApi configuration </summary>

```Java
package com.ctsb.my_product_management_svc.config;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.info.License;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;

@OpenAPIDefinition(
        info = @Info(
                contact = @Contact(
                        name = "Thaswin Muralikaran",
                        email = "thaswin@cybersolution.com.my",
                        url = "https://cybersolution.com.my/"
                ),
                description = "OpenApi documentation for Product Management System",
                title = "OpenApi specification - Product Management Service",
                version = "1.0",
                license = @License(
                        name = "Cybersolution Technologies Sdn Bhd",
                        url = "https://cybersolution.com.my/"
                ),
                termsOfService = "Terms of service"
        ),
        security = {
                @SecurityRequirement(
                        name = "bearerAuth"
                )
        }
)
@SecurityScheme(
        name = "bearerAuth",
        description = "Input JWT token for authentication",
        type = SecuritySchemeType.HTTP,
        scheme = "bearer",
        bearerFormat = "JWT"
)
public class OpenApiConfig {
}

```

</details>

#### Setting up SwaggerConfig file

```Java
package com.ctsb.my_product_management_svc.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration                   // Marks this as a Spring configuration class
public class SwaggerConfig {

     /*
     * Creates and configures the OpenAPI documentation bean
     * @return Configured OpenAPI instance with basic API info
     */

    @Bean
    public OpenAPI customOpenApi(){
        return new OpenAPI().info(new Info()
        .title("Product Management API")        // Sets the API title
        .version("1.0"));                        // Sets the API version
    }
}

```

#### Swagger UI

- Upon completing the OpenApi Configuration the API endpoints can be tested.
- The API endpoints will be tested here.
- Below shows the interface of Swagger, endpoints, and the response from a GET endpoint.

![User Interface of Swagger](swaggerUI.png)

![API Endpoints](endpoints.png)

![GET Response](response.png)

## 10. Conclusion (15 minutes)

- Recap the key concepts covered.
- Q&A session to address any doubts or questions.

## Resources:

- [Spring Initializr](https://start.spring.io/)
- [MySQL Workbench](https://dev.mysql.com/downloads/workbench/)
- [OpenApi Specification](https://swagger.io/specification/)

This 3.5-hour intensive module provides students with complete mastery of Spring Boot backend development, including entity relationships, RESTful API design, and CRUD operations implementation, with dedicated hands-on experience and coverage of advanced techniques.
