Spring Boot REST API Development Sinhala | Robust APIs | Best Practices

Spring Boot REST API Development Sinhala | Robust APIs | Best Practices

Building Robust REST APIs with Spring Boot | Sinhala Guide

ආයුබෝවන් හැමෝටම! 👋

අද කාලේ software development field එකේ වැඩ කරන කෙනෙක්ට REST APIs ගැන දැනුමක් තියෙන එක අත්‍යවශ්‍ය දෙයක්. ඒ වගේම, වේගයෙන්, කාර්යක්ෂමව සහ පහසුවෙන් API develop කරන්න Spring Boot වගේ framework එකක් පාවිච්චි කරන එක බොහොම ප්‍රයෝජනවත්. මයික්‍රෝ සර්විසස් (microservices), මොබයිල් ඇප් (mobile apps), web frontends වගේ ඕනෑම තැනකට REST APIs තමයි backbone එක වෙන්නේ. ඉතින්, ඔයාලටත් production-grade application එකකට ඕන කරන විදිහට robust REST API එකක් Spring Boot පාවිච්චි කරලා කොහොමද හදන්නේ කියලා මේ guide එකෙන් කියලා දෙන්න තමයි අපි බලාපොරොත්තු වෙන්නේ.

මේ tutorial එකෙන් අපි REST API එකක මූලික සංකල්පවල ඉඳන්, Spring Boot project එකක් setup කරගෙන, layered architecture එකක් එක්ක data validation, error handling වගේ දේවලුත් කොහොමද implement කරන්නේ කියලා බලමු. මේ දැනුම ඔයාලගේ ඊළඟ project එකට අතිශයින් වැදගත් වෙයි කියලා මම විශ්වාස කරනවා.

1. REST APIs සහ Spring Boot අතර සම්බන්ධය

මුලින්ම අපි පොඩි වෙලාවක් REST API කියන්නේ මොකක්ද කියලා මතක් කරගෙන ඉමු. REST (Representational State Transfer) කියන්නේ web services නිර්මාණය කරන්න පාවිච්චි කරන architecture style එකක්. මේකෙන් HTTP protocols පාවිච්චි කරලා client-server communication එකක් කරනවා. REST API එකක ප්‍රධාන අංග කිහිපයක් තියෙනවා:

  • Resources: මේවා තමයි අපි access කරන්න බලාපොරොත්තු වෙන දේවල් (e.g., users, products, orders).
  • URIs (Uniform Resource Identifiers): එක් එක් resource එක identify කරන address එක (e.g., /api/users, /api/products/123).
  • HTTP Methods: Resources මත කරන්න පුළුවන් operations (e.g., GET - data ගන්න, POST - data අලුතින් add කරන්න, PUT - data update කරන්න, DELETE - data remove කරන්න).
  • Representations: Resources වල data format එක (e.g., JSON, XML).

ඉතින්, Spring Boot මේකට සම්බන්ධ වෙන්නේ කොහොමද? Spring Boot කියන්නේ Spring Framework එකේම කොටසක්. ඒකෙන් Spring applications develop කරන එක අතිශයින් පහසු කරනවා. විශේෂයෙන්ම, “Convention over Configuration” කියන සංකල්පය නිසා අපිට boilerplate code ලියන්න වෙනවා අඩුයි. Spring Boot වල spring-boot-starter-web dependency එකෙන් REST APIs හදන්න අවශ්‍ය සියලුම දේවල් (like Spring Web MVC, Tomcat server) auto-configure කරලා දෙනවා. ඒක නිසා අපි request එකක් ආවම ඒක handle කරන්නේ කොහොමද, response එකක් දෙන්නේ කොහොමද වගේ core business logic එකට වැඩි අවධානයක් දෙන්න පුළුවන්.

හිතන්නකෝ, ඔයාට කොළඹ ඉඳන් මහනුවරට යන්න ඕනේ කියලා. සාමාන්‍යයෙන් අපි බස් එකක, කෝච්චියක, නැත්නම් වාහනයක යනවා නේ. හැබැයි, අපි යාළුවෙක්ට 'මාව මහනුවරට ගිහින් දාපන්' කියලා කිව්වොත් එයාට වැඩේ කරන්න විවිධ විදි තියෙන්න පුළුවන්. REST API එකත් හරියට එහෙමයි. අපිට යම්කිසි සේවාවක් කරන්න කියලා request එකක් දුන්නම, ඒකට standard ක්‍රමවේදයක් (HTTP methods) තියෙනවා. Spring Boot මේ ක්‍රමවේදයන් implement කරන එක හරියටම පහසු කරනවා.

2. Spring Boot Project එකක් පටන් ගනිමු!

අපි දැන් අපේ පළමු Spring Boot project එක හදමු. මේකට අපි Spring Initializr පාවිච්චි කරනවා. මේක web interface එකක්. ඒකෙන් අපිට අවශ්‍ය dependencies එක්ක Spring Boot project එකක් generate කරගන්න පුළුවන්.

2.1 Project Details

  • Project: Maven Project
  • Language: Java
  • Spring Boot: (Latest Stable Version, e.g., 3.x.x)
  • Group: com.example
  • Artifact: demo-rest-api
  • Name: demo-rest-api
  • Package Name: com.example.demorestapi
  • Packaging: Jar
  • Java: 17 (or your preferred version)

2.2 Dependencies

පහත සඳහන් dependencies add කරගන්න:

  • Spring Web: RESTful applications develop කරන්න අවශ්‍යයි.
  • Spring Data JPA: Database interaction වලට පහසුකම් සපයනවා.
  • H2 Database: Development වලට පහසු in-memory database එකක්. Production වලට MySQL, PostgreSQL වගේ databases පාවිච්චි කරනවා.
  • Lombok: Boilerplate code (getters, setters, constructors) අඩු කරන්න. (Optional, but highly recommended)
  • Validation: Data validation වලට අවශ්‍යයි.

මේ dependencies add කරලා, project එක generate කරගන්න (Click 'Generate' button එක). ලැබෙන .zip file එක extract කරලා, ඔයාලගේ IDE (IntelliJ IDEA, VS Code, Eclipse) එකේ open කරගන්න.

ඔබේ pom.xml file එක මේ වගේ වෙන්න ඕනේ (dependencies ටිකක් විතරයි මෙතන පෙන්වන්නේ):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
    ...
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        ...
    </dependencies>
    ...
</project>

3. Core Components: Controller, Service, Repository

Robust REST API එකක් හදද්දී application එක layered architecture එකකට develop කරන එක බොහොම වැදගත්. මේකෙන් code එක organize වෙනවා, maintain කරන්න ලේසියි, ඒ වගේම test කරන්නත් පහසු වෙනවා. අපි ප්‍රධාන layers තුනක් ගැන බලමු:

  • Controller Layer: Incoming HTTP requests handle කරනවා.
  • Service Layer: Business logic අඩංගු වෙනවා.
  • Repository Layer: Database interaction handle කරනවා.

හිතන්නකෝ, අපේ ගෙදර වැඩ ටික වෙන් වෙන්ව බෙදලා වගේ. අම්මා කෑම උයනවා, තාත්තා බඩු ගේනවා, අපි clean කරනවා වගේ. ඒ වගේ තමයි මේ layers වල වැඩත්.

3.1 Entity එකක් නිර්මාණය කිරීම (Creating an Entity)

මුලින්ම අපි Product කියන resource එක නිරූපණය කරන්න Product entity එකක් හදමු.

src/main/java/com/example/demorestapi/model/Product.java

package com.example.demorestapi.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "products")
@Data // Generates getters, setters, toString, equals, hashCode
@NoArgsConstructor // Generates a constructor with no arguments
@AllArgsConstructor // Generates a constructor with all arguments
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    private double price;
    private int quantity;
}

3.2 Repository Layer

මේ layer එකෙන් database එකත් එක්ක data interaction එක handle කරනවා. Spring Data JPA වල JpaRepository extend කරලා අපිට බොහොම පහසුවෙන් CRUD (Create, Read, Update, Delete) operations වලට අවශ්‍ය methods ලබාගන්න පුළුවන්.

src/main/java/com/example/demorestapi/repository/ProductRepository.java

package com.example.demorestapi.repository;

import com.example.demorestapi.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

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

3.3 Service Layer

Service layer එකේ තමයි business logic එක තියෙන්නේ. Controller එකෙන් එන requests වලට අදාළව, database එකෙන් data අරගෙන, process කරලා, නැවත controller එකට response එකක් දෙන එක මේකෙන් සිද්ධ වෙනවා. මේකෙදි අපි Repository එක inject කරලා පාවිච්චි කරනවා.

src/main/java/com/example/demorestapi/service/ProductService.java

package com.example.demorestapi.service;

import com.example.demorestapi.model.Product;
import com.example.demorestapi.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

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

    public Optional<Product> getProductById(Long id) {
        return productRepository.findById(id);
    }

    public Product createProduct(Product product) {
        return productRepository.save(product);
    }

    public Product updateProduct(Long id, Product productDetails) {
        Product product = productRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Product not found with id " + id));

        product.setName(productDetails.getName());
        product.setDescription(productDetails.getDescription());
        product.setPrice(productDetails.getPrice());
        product.setQuantity(productDetails.getQuantity());

        return productRepository.save(product);
    }

    public void deleteProduct(Long id) {
        Product product = productRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Product not found with id " + id));
        productRepository.delete(product);
    }
}

3.4 Controller Layer

Controller layer එක තමයි client requests receive කරලා, service layer එක හරහා business logic execute කරලා, response එකක් client එකට ආපහු යවන්නේ. @RestController annotation එකෙන් මේ class එක RESTful endpoint එකක් විදිහට define කරනවා. @RequestMapping එකෙන් base path එක specify කරන්න පුළුවන්.

src/main/java/com/example/demorestapi/controller/ProductController.java

package com.example.demorestapi.controller;

import com.example.demorestapi.model.Product;
import com.example.demorestapi.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

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

    @Autowired
    private ProductService productService;

    @GetMapping
    public List<Product> getAllProducts() {
        return productService.getAllProducts();
    }

    @GetMapping("/{id}")
    public ResponseEntity<Product> getProductById(@PathVariable Long id) {
        return productService.getProductById(id)
                .map(product -> new ResponseEntity<>(product, HttpStatus.OK))
                .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

    @PostMapping
    public ResponseEntity<Product> createProduct(@RequestBody Product product) {
        return new ResponseEntity<>(productService.createProduct(product), HttpStatus.CREATED);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Product> updateProduct(@PathVariable Long id, @RequestBody Product productDetails) {
        try {
            Product updatedProduct = productService.updateProduct(id, productDetails);
            return new ResponseEntity<>(updatedProduct, HttpStatus.OK);
        } catch (RuntimeException e) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<HttpStatus> deleteProduct(@PathVariable Long id) {
        try {
            productService.deleteProduct(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } catch (RuntimeException e) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }
}

මේ code එක Run කරලා බලන්න, DemoRestApiApplication.java එකේ main method එක Run කරහම application එක start වෙනවා. Postman, Insomnia වගේ tool එකක් පාවිච්චි කරලා මේ endpoints test කරන්න පුළුවන්.

  • GET http://localhost:8080/api/products
  • POST http://localhost:8080/api/products (with JSON body)
  • GET http://localhost:8080/api/products/1
  • PUT http://localhost:8080/api/products/1 (with JSON body)
  • DELETE http://localhost:8080/api/products/1

4. දත්ත වලංගු කිරීම (Data Validation) සහ දෝෂ හැසිරවීම (Error Handling)

Production-grade API එකක අනිවාර්යයෙන්ම තිබිය යුතුම දේවල් දෙකක් තමයි data validation සහ error handling. වැරදි data ඇතුල්වීම වළක්වන්නත්, unexpected errors අලංකාරව handle කරන්නත් මේවා අත්‍යවශ්‍යයි.

හිතන්න ඔයා bank එකකට ගිහින් loan එකක් ඉල්ලනවා කියලා. හැම document එකක්ම හරියට නැත්නම්, bank එකෙන් කියනවා 'මේක මදි, මේක වැරදියි' කියලා. අන්න ඒ වගේ තමයි validation.

4.1 Data Validation

අපි Product entity එකට validation constraints add කරමු.

src/main/java/com/example/demorestapi/model/Product.java (Updated)

package com.example.demorestapi.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.*; // Important: New import
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "Product name is mandatory")
    @Size(min = 3, max = 100, message = "Product name must be between 3 and 100 characters")
    private String name;

    @Size(max = 500, message = "Description cannot exceed 500 characters")
    private String description;

    @NotNull(message = "Price is mandatory")
    @Positive(message = "Price must be positive")
    private double price;

    @NotNull(message = "Quantity is mandatory")
    @Min(value = 0, message = "Quantity cannot be negative")
    private int quantity;
}

දැන් Controller එකේදී @RequestBody එකට @Valid annotation එක එකතු කරන්න, එවිට incoming request body එක validate වෙනවා.

src/main/java/com/example/demorestapi/controller/ProductController.java (Updated createProduct and updateProduct methods)

// ... imports
import jakarta.validation.Valid; // New import

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

    // ... other methods

    @PostMapping
    public ResponseEntity<Product> createProduct(@Valid @RequestBody Product product) {
        return new ResponseEntity<>(productService.createProduct(product), HttpStatus.CREATED);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Product> updateProduct(@PathVariable Long id, @Valid @RequestBody Product productDetails) {
        try {
            Product updatedProduct = productService.updateProduct(id, productDetails);
            return new ResponseEntity<>(updatedProduct, HttpStatus.OK);
        } catch (RuntimeException e) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }

    // ...
}

දැන් ඔබට නමක් නැති හෝ price එක negative වන Product එකක් POST කරන්න හැදුවොත් 400 Bad Request response එකක් ලැබෙයි.

4.2 Global Error Handling

@RestControllerAdvice annotation එක පාවිච්චි කරලා අපිට globally exceptions handle කරන්න පුළුවන්. මේකෙන් අපේ API එකට consistent error responses දෙන්න පුළුවන්.

මුලින්ම, අපිට Product එකක් නැති වෙලාවට throw කරන්න custom exception එකක් හදමු.

src/main/java/com/example/demorestapi/exception/ProductNotFoundException.java

package com.example.demorestapi.exception;

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

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ProductNotFoundException extends RuntimeException {
    public ProductNotFoundException(String message) {
        super(message);
    }
}

දැන් ProductService එකේ RuntimeException වෙනුවට මේක පාවිච්චි කරමු.

src/main/java/com/example/demorestapi/service/ProductService.java (Updated)

package com.example.demorestapi.service;

import com.example.demorestapi.exception.ProductNotFoundException; // New import
// ... other imports

@Service
public class ProductService {

    // ... other methods

    public Product updateProduct(Long id, Product productDetails) {
        Product product = productRepository.findById(id)
                .orElseThrow(() -> new ProductNotFoundException("Product not found with id " + id));

        // ... update logic

        return productRepository.save(product);
    }

    public void deleteProduct(Long id) {
        Product product = productRepository.findById(id)
                .orElseThrow(() -> new ProductNotFoundException("Product not found with id " + id));
        productRepository.delete(product);
    }
}

දැන් Global exception handler එක හදමු.

src/main/java/com/example/demorestapi/exception/GlobalExceptionHandler.java

package com.example.demorestapi.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

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

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ProductNotFoundException.class)
    public ResponseEntity<String> handleProductNotFoundException(ProductNotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }

    // General exception handler for any other unexpected exceptions
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGenericException(Exception ex) {
        return new ResponseEntity<>("An unexpected error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

දැන් අපේ API එකේ errors, validation failures වඩාත් හොඳින් handle වෙනවා.

5. Production-Grade සඳහා තවත් Tips

අපි මේ වන විට REST API එකක් හදන්න අවශ්‍ය මූලික දේවල් ඉගෙන ගත්තා. Production environment එකකට ගැලපෙන විදිහට API එකක් හදනකොට තව ගොඩක් දේවල් ගැන අවධානය යොමු කරන්න ඕනේ.

Deployment (පද්ධතිය යෙදවීම)

අපි හදන application එක production එකට deploy කරන්න විවිධ ක්‍රම තියෙනවා. Dockerize කරන එක (Docker containers), Kubernetes මත deploy කරන එක, Cloud platforms (AWS, Azure, GCP) වල deploy කරන එක වගේ දේවල් ගැන ඉගෙන ගන්න එක අද කාලේ developer කෙනෙක්ට අත්‍යවශ්‍යයි.

Configuration (වින්‍යාස කිරීම)

Database credentials, API keys වගේ sensitive information හෝ environment-specific configurations application.properties හෝ application.yml වගේ files වලින් වෙන් කරලා තියන්න. මේවා hardcode කරන එකෙන් වළකින්න.

Testing (පරීක්ෂා කිරීම)

API එකේ correctness එක සහ reliability එක තහවුරු කරන්න unit tests, integration tests අනිවාර්යයි. JUnit, Mockito, Spring Boot Test වගේ frameworks පාවිච්චි කරලා test cases ලියන්න පුරුදු වෙන්න. මේවා production එකකට deploy කරන්න කලින් bug free solution එකක් දෙන්න උදව් වෙනවා.

Logging (ලොග් කිරීම)

Application එකේ සිදුවන දේවල් trace කරන්න, errors debug කරන්න logging අත්‍යවශ්‍යයි. Spring Boot වල default logging (SLF4J with Logback) setup කරලා තියෙන්නේ. Proper logging configurations එක්ක, production එකේදී සිදුවන ගැටලු ඉක්මනින් හොයාගන්න පුළුවන්.

Security (ආරක්ෂාව)

ඕනෑම application එකක security එක අත්‍යවශ්‍යයි. Spring Security framework එකෙන් authentication (කවුද මේ request එක කරන්නේ?) සහ authorization (මේ request එක කරන්න මෙයාට බලය තියෙනවද?) වගේ දේවල් පහසුවෙන් implement කරන්න පුළුවන්. JWT (JSON Web Tokens), OAuth2 වගේ දේවල් ගැන ඉගෙන ගන්න එක බොහොම වැදගත්.Dependency: spring-boot-starter-security

අවසන් වශයෙන්...

ඉතින්, මේ Spring Boot Sinhala guide එකෙන් අපි robust REST API එකක් නිර්මාණය කරන්න අවශ්‍ය මූලික සහ ප්‍රායෝගික දැනුම ලබාගත්තා. අපි REST API වල මූලික සංකල්ප, Spring Boot project එකක් setup කරන ආකාරය, Controller, Service, Repository layers, CRUD operations, data validation, ඒ වගේම global error handling වගේ වැදගත් දේවල් ගැන සාකච්ඡා කළා.

මේක හොඳ ආරම්භයක් විතරයි. Spring Boot වල තව ගොඩක් advanced concepts තියෙනවා. ඒවා ගැනත් ඉගෙනගෙන, ඔයාලගේ project වලට මේ දැනුම add කරන්න උත්සාහ කරන්න. මතක තියාගන්න, නිතරම අලුත් දේවල් ඉගෙන ගන්න එකයි, practice කරන එකයි තමයි හොඳ developer කෙනෙක් වෙන්න තියෙන හොඳම මාර්ගය.

ඔයාලගේ අදහස්, මේ tutorial එක ගැන ප්‍රශ්න, නැත්නම් ඔයාලගේ project වලදී මුහුණ දුන්නු අත්දැකීම් පහළින් comment කරන්න. හැමෝටම ජය! 🚀