Spring Boot GraphQL Guide in Sinhala - REST Alternatives & API Development

Spring Boot GraphQL Guide in Sinhala - REST Alternatives & API Development

Spring Boot සමඟ GraphQL: REST වලට ආයුබෝවන් කියමු! SC Guide

කොහොමද යාලුවනේ! අද අපි කතා කරන්නේ ලෝකේ Software Development පැත්තෙන් එන අලුත්ම ට්‍රෙන්ඩ් එකක් ගැන. විශේෂයෙන්ම APIs හදනකොට, REST APIs එක්ක ඔයාලට ඔලුව රිදෙනවාද? Over-fetching, under-fetching වගේ ප්‍රශ්න වලට පිලිතුරු හොය හොයා එපා වෙලාද? එහෙනම් මේ ලිපිය ඔයාලටමයි. අද අපි බලමු Spring Boot එකත් එක්ක GraphQL API එකක් හදන්නේ කොහොමද කියලා. REST වලට වඩා GraphQL කොච්චර වටිනවද කියලත් අපි ගැඹුරින් කතා කරමු.

දැන් කාලේ වෙබ් applications හදනකොට, client එකට අවශ්‍ය data ලබාගන්න අපි හැමෝම වගේ REST APIs පාවිච්චි කරනවා. ඒත් REST කියන්නේ හැමවෙලේම හොඳම විසඳුම නෙමෙයි. සමහර වෙලාවට අපිට අවශ්‍ය දේට වඩා වැඩි data ප්‍රමාණයක් එනවා, නැත්තම් අපිට අවශ්‍ය data ටික එක API call එකකින් ලැබෙන්නේ නැතුව call කිහිපයක් කරන්න වෙනවා. මේ ප්‍රශ්න වලට GraphQL නියම විසඳුමක් දෙනවා.

REST එපා වෙලාද? GraphQL ඇයි හොඳ?

REST API එකක් කියන්නේ, අපේ server එකේ තියෙන data වලට access කරන්න පුළුවන් endpoints ගොඩක්. උදාහරණයක් විදියට, අපිට users ලා ගැන data ගන්න ඕන නම් /users කියල endpoint එකක් තියෙන්න පුළුවන්. තනි user කෙනෙක්ගේ data ගන්න /users/{id} වගේ endpoint එකක් තියෙන්න පුළුවන්. මේකෙන් වෙන්නේ, අපිට පොඩි data ටිකක් ගන්නත්, ඒකටම වෙන්වූ endpoint එකකට request කරන්න වෙන එක. ඒ වගේම, සමහර වෙලාවට අපිට user කෙනෙක්ගේ නමයි, email එකයි විතරක් ඕන වුනාට, REST endpoint එකෙන් එන්නේ එයාගේ ලිපිනය, ෆෝන් නම්බර්, උපන් දිනේ වගේ අනවශ්‍ය data ගොඩකුත් එක්ක. මේකට කියන්නේ Over-fetching කියලා.

අනිත් පැත්තෙන්, අපිට user කෙනෙක්ගේ නමයි, එයාගේ last purchased item එකයි ඕන කියලා හිතමු. REST API එකකින් user ගේ data ගන්න එක endpoint එකක්. ඒ user ගේ purchases ගැන data ගන්න වෙනම endpoint එකක්. එතකොට අපිට API calls දෙකක් කරන්න වෙනවා. මේකට කියන්නේ Under-fetching කියලා.

මේ ප්‍රශ්න නිසා, mobile apps වගේ අඩු network bandwidth තියෙන තැන් වලදී performance වලට බලපෑම් ඇති වෙන්න පුළුවන්. ඒ වගේම front-end developers ලටත් තමන්ට අවශ්‍ය data හරියටම ගන්න, API calls ගොඩක් handle කරන්න වෙනවා. මේක ඇත්තටම headache එකක්.

GraphQL මේකට නියම විසඳුමක්. GraphQL එකේ තියෙන්නේ එකම එක endpoint එකයි. ඔව්, ඇහුවේ හරි! එකම එක endpoint එකක්. අපිට ඕන කරන data මොනවද කියලා අපි ඒ endpoint එකටම කියනවා. ඒ කියන්නේ, client එකට හරියටම අවශ්‍ය data මොනවද කියලා define කරන්න පුළුවන්. ඒක හරියට සුපිරි කඩේකට ගිහින්, අපිට ඕන දේ විතරක් ලැයිස්තුගත කරලා ගන්නවා වගේ වැඩක්. ඕන දේ විතරක්, ඕන විදියට. මේකෙන් Over-fetching, Under-fetching වගේ ප්‍රශ්න විසඳෙනවා. Network requests අඩු වෙනවා, performance වැඩි වෙනවා, front-end developers ලටත් වැඩේ ලේසි වෙනවා.

Spring Boot එකට GraphQL අල්ලගමු

Spring Boot කියන්නේ Java එකේ REST APIs හදනකොට අපේ හොඳම යාළුවා. GraphQL එකත් Spring Boot එක්ක වැඩ කරන්න හරිම ලේසියි. මේ සඳහා අපිට graphql-java-kickstart කියන library එක පාවිච්චි කරන්න පුළුවන්. මේක GraphQL libraries ගොඩක් එකට එකතු කරලා හදපු, Spring Boot එක්ක හොඳින් integrate වෙන excellent solution එකක්.

මුලින්ම, අපේ Maven (හෝ Gradle) project එකට අවශ්‍ය dependencies එකතු කරගමු. pom.xml file එකට මේ ටික add කරගන්න:

<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-spring-boot-starter</artifactId>
    <version>12.0.0</version<!-- නවතම version එක බලලා දාන්න -->
</dependency>
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-java-tools</artifactId>
    <version>12.0.0</version<!-- නවතම version එක බලලා දාන්න -->
</dependency>
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphiql-spring-boot-starter</artifactId>
    <version>12.0.0</version<!-- නවතම version එක බලලා දාන්න -->
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • graphql-spring-boot-starter: මේක තමයි Spring Boot application එකට GraphQL functionality එක add කරන්නේ.
  • graphql-java-tools: මේකෙන් අපිට GraphQL Schema Definition Language (SDL) එක Java code එකට map කරන්න උදව් වෙනවා. ඒ කියන්නේ, අපි හදන .graphqls file එකේ තියෙන schema එක Java objects වලට convert කරලා, data fetch කරන්න උදව් වෙනවා.
  • graphiql-spring-boot-starter: GraphQL APIs develop කරනකොට, test කරන්න interactive in-browser GraphQL IDE එකක් (GraphiQL) මේකෙන් අපිට ලැබෙනවා. මේක හරිම වැදගත් tool එකක්.
  • spring-boot-starter-web: සාමාන්‍ය Spring Boot web application එකකට අවශ්‍ය dependency එක.

Schema එක තමයි බොස් (The Schema is the Boss)

GraphQL API එකක් හදනකොට, 제일 වැදගත් දේ තමයි GraphQL Schema එක. මේක අපේ API එකේ “contract” එක වගේ. ඒ කියන්නේ, client එකට මොන data ගන්න පුළුවන්ද, මොන data modify කරන්න පුළුවන්ද, ඒ data වල format එක මොකක්ද කියලා මේ schema එකෙන් කියනවා. මේ schema එක අපි ලියන්නේ Schema Definition Language (SDL) කියන භාෂාවෙන්. මේක හරිම සරලයි, තේරුම් ගන්නත් ලේසියි.

සාමාන්‍යයෙන්, schema එක අපි src/main/resources ෆෝල්ඩර් එක ඇතුලේ schema.graphqls කියන file එකේ තමයි ලියන්නේ. අපි පොඩි Library Management system එකකට GraphQL API එකක් හදනවා කියලා හිතමු. ඒකට අවශ්‍ය schema එකක් මෙහෙම ලියන්න පුළුවන්:

type Book {
    id: ID!
    title: String!
    author: String!
    publicationYear: Int
}

type Query {
    allBooks: [Book!]!
    bookById(id: ID!): Book
    booksByAuthor(author: String!): [Book!]!
}

type Mutation {
    addBook(title: String!, author: String!, publicationYear: Int): Book!
    updateBook(id: ID!, title: String, author: String, publicationYear: Int): Book
    deleteBook(id: ID!): Boolean!
}

මේ Schema එකේ තියෙන දේවල් ටිකක් පැහැදිලි කරගමු:

  • type Book { ... }: මේකෙන් අපි අලුත් data type එකක් define කරනවා. මේක හරියට Java වල Class එකක් වගේ. Book type එකට id, title, author, publicationYear වගේ fields තියෙනවා.
  • ID!, String!, Int: මේවා GraphQL වල තියෙන built-in scalar types.! ලකුණෙන් කියන්නේ ඒ field එක non-nullable කියන එක. ඒ කියන්නේ, ඒ field එකට අනිවාර්යයෙන්ම value එකක් තියෙන්න ඕනේ. නැත්තම් error එකක් එනවා. publicationYear එකට ! නැති නිසා, ඒක nullable field එකක්.
    • ID: unique identifier එකක්. String එකක් වගේ වුනත් serialization වෙනකොට ටිකක් වෙනස්.
    • String: character string එකක්.
    • Int: signed 32-bit integer එකක්.
    • Boolean: true හෝ false.
    • Float: signed double-precision floating-point value එකක්.
  • type Query { ... }: මේක තමයි client එකට data කියවන්න පුළුවන් operations define කරන්නේ.
    • allBooks: [Book!]!: මේකෙන් කියන්නේ, allBooks කියන Query එක call කරාම, Book type එකේ objects list එකක් ලැබෙනවා කියන එක. ඒ list එක ඇතුලේ තියෙන හැම Book එකක්ම non-nullable (Book!), ඒ වගේම මුළු list එකත් non-nullable ([Book!]!) කියන එකයි.
    • bookById(id: ID!): Book: මේකෙන් කියන්නේ, id එකක් parameter එකක් විදියට දීලා, ඒ id එකට අදාල Book එක ගන්න පුළුවන් කියන එක. ID! කියන්නේ id parameter එක non-nullable වෙන්න ඕනේ කියන එකයි.
    • booksByAuthor(author: String!): [Book!]!: author කෙනෙක්ගේ නම දීලා ඒ කතුවරයාගේ පොත් ටික ගන්න පුළුවන්.
  • type Mutation { ... }: මේකෙන් client එකට data modify කරන්න පුළුවන් operations define කරනවා. Data create කරන, update කරන, delete කරන operations මේකේ තියෙනවා.
    • addBook(title: String!, author: String!, publicationYear: Int): Book!: මේකෙන් පොතක් system එකට add කරන්න පුළුවන්. අවශ්‍ය parameters (title, author) අනිවාර්යයි, publicationYear එක අනිවාර්ය නෑ. පොත add කරාට පස්සේ අලුත් Book object එක return කරනවා.
    • updateBook(id: ID!, title: String, author: String, publicationYear: Int): Book: දැනට තියෙන පොතක් update කරන්න.
    • deleteBook(id: ID!): Boolean!: පොතක් delete කරන්න. සාර්ථක වුනොත් true, නැත්තම් false return කරනවා.

දැන් ඔයාලට schema එක ගැන හොඳ අවබෝධයක් තියෙනවා ඇති. මේක තමයි GraphQL API එකක හදවත. Client එක මේ schema එක පාවිච්චි කරලා තමයි server එකත් එක්ක interact කරන්නේ.

Resolvers ලා වැඩේට බහිමු (Let's get into the Resolvers)

Schema එකෙන් කියන්නේ මොන data එකක්ද අපිට ගන්න පුළුවන් කියලා විතරයි. ඒ data ඇත්තටම database එකකින් හරි වෙනත් data source එකකින් හරි අරගෙන දෙන්නේ resolvers ලා. හරියට schema එක කියන්නේ මෙනු එක නම්, resolver ලා කියන්නේ ඒ කෑම ටික හදන කුක්ලා වගේ. ඒගොල්ලෝ තමයි request එකක් ආවම, ඒකට අදාල data එකතු කරලා client එකට දෙන්නේ.

Spring Boot එකේදී, අපි resolvers ලා හදන්නේ සාමාන්‍ය Spring beans විදියට. ඒ වගේම, graphql-java-tools library එකේ තියෙන GraphQLQueryResolver සහ GraphQLMutationResolver interfaces implement කරලා තමයි අපි මේ resolvers ලා හදන්නේ.

Book model එකක් හදමු මුලින්ම:

package com.example.graphqlspringboot.model;

public class Book {
    private String id;
    private String title;
    private String author;
    private Integer publicationYear;

    public Book(String id, String title, String author, Integer publicationYear) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.publicationYear = publicationYear;
    }

    // Getters and Setters
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Integer getPublicationYear() {
        return publicationYear;
    }

    public void setPublicationYear(Integer publicationYear) {
        this.publicationYear = publicationYear;
    }
}

දැන් අපිට පොත් ටික manage කරගන්න සරල Service එකක් හදාගමු. මේක තාවකාලිකව data memory එකේ store කරගන්නවා:

package com.example.graphqlspringboot.service;

import com.example.graphqlspringboot.model.Book;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;

@Service
public class BookService {
    private final List<Book> books = new ArrayList<>();
    private final AtomicLong counter = new AtomicLong();

    public BookService() {
        books.add(new Book(String.valueOf(counter.incrementAndGet()), "The Lord of the Rings", "J.R.R. Tolkien", 1954));
        books.add(new Book(String.valueOf(counter.incrementAndGet()), "The Hitchhiker's Guide to the Galaxy", "Douglas Adams", 1979));
        books.add(new Book(String.valueOf(counter.incrementAndGet()), "1984", "George Orwell", 1949));
    }

    public List<Book> getAllBooks() {
        return books;
    }

    public Optional<Book> getBookById(String id) {
        return books.stream()
                .filter(book -> book.getId().equals(id))
                .findFirst();
    }

    public List<Book> getBooksByAuthor(String author) {
        return books.stream()
                .filter(book -> book.getAuthor().equalsIgnoreCase(author))
                .toList();
    }

    public Book addBook(String title, String author, Integer publicationYear) {
        Book newBook = new Book(String.valueOf(counter.incrementAndGet()), title, author, publicationYear);
        books.add(newBook);
        return newBook;
    }

    public Optional<Book> updateBook(String id, String title, String author, Integer publicationYear) {
        Optional<Book> existingBookOpt = getBookById(id);
        if (existingBookOpt.isPresent()) {
            Book existingBook = existingBookOpt.get();
            if (title != null) existingBook.setTitle(title);
            if (author != null) existingBook.setAuthor(author);
            if (publicationYear != null) existingBook.setPublicationYear(publicationYear);
            return Optional.of(existingBook);
        }
        return Optional.empty();
    }

    public boolean deleteBook(String id) {
        return books.removeIf(book -> book.getId().equals(id));
    }
}

දැන් අපි Query Resolver එක හදමු:

package com.example.graphqlspringboot.resolver;

import com.example.graphqlspringboot.model.Book;
import com.example.graphqlspringboot.service.BookService;
import graphql.kickstart.tools.GraphQLQueryResolver;
import org.springframework.stereotype.Component;

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

@Component
public class BookQueryResolver implements GraphQLQueryResolver {

    private final BookService bookService;

    public BookQueryResolver(BookService bookService) {
        this.bookService = bookService;
    }

    public List<Book> allBooks() {
        return bookService.getAllBooks();
    }

    public Book bookById(String id) {
        return bookService.getBookById(id).orElse(null);
    }

    public List<Book> booksByAuthor(String author) {
        return bookService.getBooksByAuthor(author);
    }
}

මෙහිදී, allBooks(), bookById(String id), booksByAuthor(String author) කියන methods වල නම් සහ parameters, අපේ schema.graphqls file එකේ type Query එකේ තියෙන field names සහ arguments වලට අනුරූප වෙන්න ඕනේ. graphql-java-tools library එක මේ mapping එක automatically කරනවා.

ඊළඟට, Mutation Resolver එක හදමු:

package com.example.graphqlspringboot.resolver;

import com.example.graphqlspringboot.model.Book;
import com.example.graphqlspringboot.service.BookService;
import graphql.kickstart.tools.GraphQLMutationResolver;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
public class BookMutationResolver implements GraphQLMutationResolver {

    private final BookService bookService;

    public BookMutationResolver(BookService bookService) {
        this.bookService = bookService;
    }

    public Book addBook(String title, String author, Integer publicationYear) {
        return bookService.addBook(title, author, publicationYear);
    }

    public Book updateBook(String id, String title, String author, Integer publicationYear) {
        Optional<Book> updatedBook = bookService.updateBook(id, title, author, publicationYear);
        return updatedBook.orElse(null);
    }

    public Boolean deleteBook(String id) {
        return bookService.deleteBook(id);
    }
}

Mutation Resolver එකත් Query Resolver එක වගේමයි. addBook(), updateBook(), deleteBook() methods වල නම් සහ parameters schema එකේ type Mutation එකේ තියෙන field names සහ arguments වලට අනුරූප වෙනවා.

Practical Example - පොඩි Project එකක්

දැන් අපි මේ හදපු API එක run කරලා බලමු. Spring Boot application එක start කරාට පස්සේ, ඔයාලට http://localhost:8080/graphiql කියන URL එකට ගිහින් GraphiQL IDE එක access කරන්න පුළුවන්. මේක හරිම පවර්ෆුල් tool එකක්, GraphQL queries, mutations, subscriptions test කරන්න. Auto-completion, schema documentation වගේ දේවල් මේකේ තියෙනවා.

Query එකක් පාවිච්චි කරලා පොත් ටික ගන්න හැටි:

allBooks query එක පාවිච්චි කරලා දැනට system එකේ තියෙන පොත් ටික ලබාගන්න පුළුවන්. අවශ්‍ය fields විතරක් request කරන්න පුළුවන් හැටි බලන්න.

query {
  allBooks {
    id
    title
    author
  }
}

මේ query එක run කරාම, ඔයාලට මේ වගේ output එකක් ලැබෙයි:

{
  "data": {
    "allBooks": [
      {
        "id": "1",
        "title": "The Lord of the Rings",
        "author": "J.R.R. Tolkien"
      },
      {
        "id": "2",
        "title": "The Hitchhiker's Guide to the Galaxy",
        "author": "Douglas Adams"
      },
      {
        "id": "3",
        "title": "1984",
        "author": "George Orwell"
      }
    ]
  }
}

දැන් අපිට bookById query එක පාවිච්චි කරලා, නිශ්චිත පොතක් හොයාගන්න පුළුවන්. id එක String! නිසා, parameter එකක් විදියට දෙනකොට String එකක් විදියටම දෙන්න ඕනේ.

query {
  bookById(id: "1") {
    title
    author
    publicationYear
  }
}

Output එක මේ වගේ වෙයි:

{
  "data": {
    "bookById": {
      "title": "The Lord of the Rings",
      "author": "J.R.R. Tolkien",
      "publicationYear": 1954
    }
  }
}

Mutation එකක් පාවිච්චි කරලා පොතක් එකතු කරන හැටි:

addBook mutation එක පාවිච්චි කරලා අලුත් පොතක් system එකට එකතු කරමු. මෙතනදී publicationYear nullable නිසා, අපිට අවශ්‍ය නම් විතරක් ඒක දෙන්න පුළුවන්.

mutation {
  addBook(title: "The Alchemist", author: "Paulo Coelho", publicationYear: 1988) {
    id
    title
    author
  }
}

Output එක:

{
  "data": {
    "addBook": {
      "id": "4",
      "title": "The Alchemist",
      "author": "Paulo Coelho"
    }
  }
}

පොත delete කරන්න මේ වගේ mutation එකක් පාවිච්චි කරන්න පුළුවන්:

mutation {
  deleteBook(id: "4")
}

Output එක:

{
  "data": {
    "deleteBook": true
  }
}

ඔයාලට පේනවා ඇති, GraphQL එක්ක වැඩ කරන එක කොච්චර ලේසිද, ඒ වගේම කොච්චර flexible ද කියලා. Client එකට හරියටම අවශ්‍ය දේ විතරක් ඉල්ලලා ගන්න පුළුවන් වීම, අනවශ්‍ය network traffic එකක් අඩු කරනවා. ඒ වගේම, API එකේ versioning ගැන හිතන්න ඕනේ වෙන්නේ නැහැ. මොකද, client එකට අවශ්‍ය දේ ඉල්ලන්න පුළුවන් නිසා, අලුත් fields add වුනාට, client පැත්තේ code එක වෙනස් කරන්න අවශ්‍ය වෙන්නේ නැහැ.

අවසන් වචන...

ඉතින් යාලුවනේ, ඔයාලට දැන් තේරෙනවා ඇති GraphQL කියන්නේ නිකම්ම නිකම් තවත් API එකක් නෙමෙයි, ඒක API Development වල අලුත් යුගයක් කියලා. විශේෂයෙන්ම Microservices architecture වගේ සංකීර්ණ systems වලදී, GraphQL එක තනි gateway එකක් විදියට පාවිච්චි කරලා, back-end එකේ තියෙන data sources ගොඩක් එක තැනකින් client එකට expose කරන්න පුළුවන්. මේකෙන් Development process එක හරිම ලේසි වෙනවා, maintain කරන්නත් පහසු වෙනවා.

Spring Boot කියන්නේ Java developers ලා අතර ජනප්‍රියම Framework එකක්. GraphQL එක්ක Spring Boot combine කරාම, powerful, efficient, සහ maintain කරන්න ලේසි APIs හදන්න පුළුවන්. අද අපි බලපු මේ සරල උදාහරණයෙන් ඔයාලට පොඩි අදහසක් එන්න ඇති GraphQL වල මූලික සංකල්ප සහ Spring Boot එක්ක ඒක පාවිච්චි කරන්නේ කොහොමද කියලා.

ඔයාලත් මේක ට්‍රයි කරලා බලන්න. තමන්ගේම පොඩි project එකක් හදලා, GraphQL queries, mutations එක්ක වැඩ කරලා බලන්න. මොනවා හරි ප්‍රශ්න තියෙනවා නම්, තේරුණේ නැති තැන් තියෙනවා නම්, අනිවාර්යයෙන්ම පහලින් තියෙන කමෙන්ට් සෙක්ෂන් එකේ අහන්න. අපි හැමෝම එකතු වෙලා ඉගෙන ගමු. තවත් මේ වගේ ලිපියකින් හමුවෙමු, හැමෝටම ජය!

#SpringBoot #GraphQL #API #Development #Java #SoftwareEngineering #SriLanka #TechGuide #Programming #RESTAPI