Spring Boot GraphQL Subscriptions: Real-time Updates ගෙනෙමු! SC Guide

Spring Boot GraphQL Subscriptions: Real-time Updates ගෙනෙමු! SC Guide

ඉතින් කොහොමද යාලුවනේ? අද අපි කතා කරන්න යන්නේ අතිශය වැදගත්, ඒ වගේම අපේ applications වලට අලුත් ජීවයක් දෙන මාතෘකාවක් ගැන - Spring Boot එක්ක GraphQL Subscriptions කොහොමද implement කරන්නේ කියලා.

අද කාලේ applications වල real-time updates නැතුව බෑ නේද? Chat applications, live dashboards, stock market updates, game scores, notifications - මේ හැම එකටම моментаල් update වෙන්න ඕනේ. සාමාන්‍යයෙන් අපිට තියෙන options තමයි REST API polling, Long Polling, නැත්නම් WebSockets. හැබැයි GraphQL Subscriptions මේ හැමදේටම අලුත් elegant solution එකක් ගේනවා.

අපි මේ post එකෙන් බලමු GraphQL Subscriptions කියන්නේ මොකක්ද, Spring Boot project එකකට ඒක කොහොමද add කරන්නේ, practical example එකක් එක්ක subscription endpoint එකක් හදන්නේ කොහොමද කියලා. මේකෙන් ඔයාලට GraphQL Subscriptions ගැන clear idea එකක් වගේම, ඔයාලගේම project වලට ඒක integrate කරගන්න අවශ්‍ය දැනුම ලැබෙයි.

GraphQL Subscriptions කියන්නේ මොනවාද?

මුලින්ම බලමු GraphQL Subscriptions කියන්නේ හරියටම මොනවාද කියලා. GraphQL වල ප්‍රධාන operation types තුනක් තියෙනවා: Queries, Mutations, සහ Subscriptions.

  • Queries: මේක හරියට REST API වල GET request එකක් වගේ. Client එක server එකෙන් data ඉල්ලනවා, server එක response එක දෙනවා, connection එක close වෙනවා. ඒ කියන්නේ client එකට අවශ්‍ය වෙලාවට server එකෙන් data 'pull' කරනවා.
  • Mutations: මේක REST API වල POST, PUT, DELETE requests වගේ. Client එක server එකට data යවනවා data 'change' කරන්න. Server එක operation එක execute කරලා response එක දෙනවා, connection එක close වෙනවා.
  • Subscriptions: මෙතන තමයි සිරා වැඩේ තියෙන්නේ! Subscriptions කියන්නේ long-lived connection එකක්. Client එක server එකට 'subscribe' වුණාම, server එකට අලුත් data එකක් publish වුණ ගමන්, client එකට ඒක 'push' කරනවා. Client එකට data 'pull' කරන්න අවශ්‍ය වෙන්නේ නෑ. මේක හරියට YouTube channel එකකට subscribe වෙනවා වගේ. අලුත් video එකක් ආපු ගමන් ඔයාට notification එකක් එනවා වගේ තමයි.

මේකේ ලොකුම වාසිය තමයි real-time communication. Client එකට අනවශ්‍ය විදියට server එකෙන් data ඉල්ලන්න ඕනේ නෑ. Server එකට event එකක් publish වුණාම අවශ්‍ය clients ලට විතරක් data යවන එකෙන් network traffic එකත් අඩු වෙනවා, client-side resource usage එකත් අඩු වෙනවා.

නමුත්, Subscriptions implement කරන්න නම් server-side එකේ reactive programming pattern එකක් use කරන්න වෙනවා. Spring Boot වලදී අපි Reactor library එකෙන් ලැබෙන Flux සහ Mono use කරනවා. ඒ ගැන අපි ඉස්සරහට කතා කරමු.

Spring Boot එකට GraphQL Subscriptions ගේන්නේ කොහොමද?

Spring Boot project එකකට GraphQL Subscriptions add කරන එක එච්චරම අමාරු වැඩක් නෙවෙයි. අපි මෙතනදී Netflix DGS Framework එක use කරනවා. DGS කියන්නේ Spring Boot වලට GraphQL implement කරන්න තියෙන හරිම පහසු සහ powerful framework එකක්.

Dependencies ටිකක් add කරගන්න ඕන

මුලින්ම ඔයාලගේ pom.xml (Maven) නැත්නම් build.gradle (Gradle) file එකට අවශ්‍ය dependencies ටික add කරගන්න ඕන.

Maven (pom.xml):

<dependencies>
    <dependency>
        <groupId>com.netflix.graphql.dgs</groupId>
        <artifactId>dgs-spring-boot-starter</artifactId>
        <version>6.0.0</version> <!-- නවතම version එක බලලා දාගන්න -->
    </dependency>
    <dependency>
        <groupId>com.netflix.graphql.dgs</groupId>
        <artifactId>dgs-subscriptions-sse</artifactId>
        <version>6.0.0</version> <!-- නවතම version එක බලලා දාගන්න -->
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

මෙහිදී dgs-spring-boot-starter එකෙන් DGS Framework එකේ core functionality එක ලැබෙනවා. dgs-subscriptions-sse සහ spring-boot-starter-websocket කියන්නේ subscriptions සඳහා අවශ්‍ය server-sent events (SSE) සහ WebSocket support එක ලබා දෙන dependencies. GraphQL Subscriptions වලදී WebSocket (graphql-ws protocol) භාවිතා වෙනවා. spring-boot-starter-webflux එක reactive programming සඳහා අවශ්‍යයි.

Application Properties

application.properties (හෝ application.yml) file එකේ කිසිම විශේෂ configuration එකක් අවශ්‍ය නැහැ DGS subscriptions සඳහා. DGS starter එක automatically WebSocket endpoint එක setup කරනවා. අවශ්‍ය නම් DGS Playground enabled කරගන්න පුළුවන්:

dgs.graphql.graphiql.enabled=true

Subscription Endpoint එකක් හදමු!

දැන් අපි බලමු practical example එකක් එක්ක subscription endpoint එකක් කොහොමද හදන්නේ කියලා. අපි හිතමු අපිට chat application එකක් තියෙනවා, ඒකේ අලුත් messages publish වුණාම client එකට real-time update වෙන්න ඕනේ කියලා. මේක අපි 'newMessage' කියන subscription එකෙන් implement කරමු.

GraphQL Schema එක

මුලින්ම අපි GraphQL schema එක නිර්වචනය කරගන්න ඕන. src/main/resources/schema/schema.graphqls කියන path එකේ මේ file එක හදන්න.

type Message {
    id: ID!
    content: String!
    sender: String!
    timestamp: String!
}

type Query {
    messages: [Message!]
}

type Mutation {
    addMessage(content: String!, sender: String!): Message!
}

type Subscription {
    newMessage: Message!
}

මේ schema එකේ Subscription type එකට අපි newMessage: Message! කියලා field එකක් add කරලා තියෙනවා. මේකෙන් කියවෙන්නේ client එකට අලුත් Message objects subscribe වෙන්න පුළුවන් කියලා.

Data Model එක

ඊළඟට අපි Message model එක නිර්වචනය කරමු. මේක simple Java POJO එකක්.

package com.example.graphqlsubscriptions.model;

import java.time.LocalDateTime;
import java.util.UUID;

public class Message {
    private String id;
    private String content;
    private String sender;
    private String timestamp;

    public Message(String content, String sender) {
        this.id = UUID.randomUUID().toString();
        this.content = content;
        this.sender = sender;
        this.timestamp = LocalDateTime.now().toString();
    }

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

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

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(String timestamp) {
        this.timestamp = timestamp;
    }

    @Override
    public String toString() {
        return "Message{" +
               "id='" + id + '\'' +
               ", content='" + content + '\'' +
               ", sender='" + sender + '\'' +
               ", timestamp='" + timestamp + '\'' +
               '}';
    }
}

MessagePublisher Service එක

Subscriptions වලදී, data stream එකක් publish කරන්න වෙනවා. අපි Reactor framework එකෙන් ලැබෙන Sinks.Many එකක් use කරලා මේක කරමු. මේ service එකේ තමයි අලුත් messages publish කරන්නේ.

package com.example.graphqlsubscriptions.service;

import com.example.graphqlsubscriptions.model.Message;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Sinks;

@Service
public class MessagePublisher {

    private final Sinks.Many<Message> messageSink;

    public MessagePublisher() {
        // Unicast ensures only one subscriber consumes each signal.
        // If there are multiple subscribers, they will only see events
        // from the point they subscribe. To let all subscribers see all events,
        // use Sinks.Many.multicast().onBackpressureBuffer().
        // However, for typical GraphQL Subscriptions, one stream per subscription is common.
        this.messageSink = Sinks.Many.multicast().onBackpressureBuffer();
    }

    public Flux<Message> getMessageStream() {
        return messageSink.asFlux();
    }

    public void publishMessage(Message message) {
        System.out.println("Publishing message: " + message.getContent());
        messageSink.tryEmitNext(message);
    }
}

Sinks.Many.multicast().onBackpressureBuffer() කියන එකෙන් කියවෙන්නේ අපිට multiple subscribers ලා ඉන්නවා නම් හැමෝටම events receive කරන්න පුළුවන්. ඒ වගේම backpressure handling එකත් auto manage කරනවා.

GraphQL DataFetcher එක

දැන් අපි DGS framework එක use කරලා subscription data fetcher එක හදමු. @DgsSubscription annotation එකෙන් DGS වලට කියනවා මේ method එක subscription field එකකට අදාළයි කියලා.

package com.example.graphqlsubscriptions.datafetchers;

import com.example.graphqlsubscriptions.model.Message;
import com.example.graphqlsubscriptions.service.MessagePublisher;
import com.netflix.graphql.dgs.DgsComponent;
import com.netflix.graphql.dgs.DgsMutation;
import com.netflix.graphql.dgs.DgsQuery;
import com.netflix.graphql.dgs.DgsSubscription;
import com.netflix.graphql.dgs.InputArgument;
import reactor.core.publisher.Flux;

import java.util.ArrayList;
import java.util.List;

@DgsComponent
public class MessageDataFetcher {

    private final MessagePublisher messagePublisher;
    private final List<Message> messages = new ArrayList<>(); // For query/mutation persistence

    public MessageDataFetcher(MessagePublisher messagePublisher) {
        this.messagePublisher = messagePublisher;
    }

    // Query to get all messages (optional, for completeness)
    @DgsQuery
    public List<Message> messages() {
        return messages;
    }

    // Mutation to add a new message
    @DgsMutation
    public Message addMessage(@InputArgument String content, @InputArgument String sender) {
        Message newMessage = new Message(content, sender);
        messages.add(newMessage); // Add to our in-memory list
        messagePublisher.publishMessage(newMessage); // Publish to subscribers
        return newMessage;
    }

    // Subscription to get new messages
    @DgsSubscription
    public Flux<Message> newMessage() {
        System.out.println("Client subscribed to new messages!");
        return messagePublisher.getMessageStream();
    }
}

මෙතනදී වැදගත්ම කොටස තමයි @DgsSubscription public Flux<Message> newMessage() method එක. මේක Flux<Message> එකක් return කරනවා. DGS framework එක automatically මේ Flux එක subscribe කරලා, අලුත් event එකක් publish වුණ ගමන් client එකට data push කරනවා.

addMessage mutation එකේදී අපි අලුත් Message එකක් හදලා ඒක messagePublisher.publishMessage(newMessage) call එකෙන් stream එකට publish කරනවා. මේකෙන් තමයි subscribers ලට update එක යන්නේ.

Main Application Class එක

ඔයාලගේ main Spring Boot application class එකේ වෙනසක් නෑ. හැබැයි අපි test කරන්න ලේසි වෙන්න CommandLineRunner එකක් add කරමු, ඒකෙන් server එක start වුණාම messages ටිකක් publish කරමු.

package com.example.graphqlsubscriptions;

import com.example.graphqlsubscriptions.model.Message;
import com.example.graphqlsubscriptions.service.MessagePublisher;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class GraphqlSubscriptionsApplication {

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

    @Bean
    public CommandLineRunner run(MessagePublisher messagePublisher) {
        return args -> {
            System.out.println("Application started. Publishing some initial messages in 5 seconds...");
            Thread.sleep(5000); // Wait a bit for clients to subscribe
            messagePublisher.publishMessage(new Message("Hello from server!", "Server"));
            Thread.sleep(2000);
            messagePublisher.publishMessage(new Message("How are you doing?", "Server"));
            Thread.sleep(2000);
            messagePublisher.publishMessage(new Message("This is a real-time update.", "Server"));
        };
    }
}

Front-end එකෙන් Subscribe වෙන්නේ කොහොමද?

දැන් අපි server-side එකේ subscription endpoint එක හැදුවා. එහෙනම් front-end එකෙන් මේකට connect වෙන්නේ කොහොමද? GraphQL client libraries (Apollo Client, Relay, Urql වගේ) වලට subscriptions සඳහා built-in support එකක් තියෙනවා.

සාමාන්‍යයෙන් මේ client libraries GraphQL over WebSockets protocol එක use කරනවා. Spring Boot DGS starter එක automatically /graphql endpoint එකේදී GraphQL HTTP requests වගේම GraphQL WebSocket connections වලටත් support කරනවා.

අපි හිතමු අපි Apollo Client (React/Vue/Angular) use කරනවා කියලා. අපිට WebSocket link එකක් configure කරන්න වෙනවා.

import { ApolloClient, InMemoryCache, ApolloProvider, gql } from '@apollo/client';
import { WebSocketLink } from '@apollo/client/link/ws';
import { split, HttpLink } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';

const httpLink = new HttpLink({
  uri: 'http://localhost:8080/graphql'
});

const wsLink = new WebSocketLink({
  uri: 'ws://localhost:8080/graphql',
  options: {
    reconnect: true
  }
});

// The split function takes three parameters:
//
// * A function that's called for each operation to execute
// * The Link to use for an operation if the function returns a "truthy" value
// * The Link to use for an operation if the function returns a "falsy" value
const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink,
);

const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache()
});

// Then in your React component:
function App() {
  return (
    <ApolloProvider client={client}>
      <MyChatComponent />
    </ApolloProvider>
  );
}

// Inside MyChatComponent, you'd use the useSubscription hook:
const NEW_MESSAGE_SUBSCRIPTION = gql`
  subscription OnNewMessage {
    newMessage {
      id
      content
      sender
      timestamp
    }
  }
`;

function MyChatComponent() {
  const { data, loading, error } = useSubscription(
    NEW_MESSAGE_SUBSCRIPTION
  );

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error! {error.message}</p>;

  return (
    <div>
      <h2>Live Messages:</h2>
      {data && data.newMessage && (
        <div>
          <p><strong>{data.newMessage.sender}:</strong> {data.newMessage.content} ({data.newMessage.timestamp})</p>
        </div>
      )}
    </div>
  );
}

මේ JavaScript code එකෙන් පෙන්නන්නේ client එක WebSocket එක හරහා server එකේ newMessage subscription එකට connect වෙලා data receive කරන හැටි. Server එකෙන් අලුත් message එකක් publish වුණ ගමන්, client එකට ඒක ලැබෙනවා.

Testing කරමු!

පහසුම විදිය තමයි DGS Playground (http://localhost:8080/graphql) use කරන එක. Browser එකේ GraphQL Playground එක open කරලා, මේ subscription query එක run කරන්න:

subscription NewMessages {
  newMessage {
    id
    content
    sender
    timestamp
  }
}

ඒක run කරලා තිබුණොත්, ඔයාලගේ Spring Boot application එක start වුණාම CommandLineRunner එකෙන් publish කරන messages ටික Playground එකේ real-time display වෙනවා.

ඒ වගේම, අපිට mutations use කරලා අලුත් messages add කරන්න පුළුවන්. ඒකෙන් publish වෙන messages ටිකත් subscription එකට ලැබෙනවද කියලා check කරන්න පුළුවන්.

mutation AddNewMessage {
  addMessage(content: "Hey there!", sender: "DGS User") {
    id
    content
    sender
  }
}

මේ mutation එක run කරපු ගමන්, ඔයාලගේ subscription එකට අලුත් message එක ලැබෙන හැටි බලාගන්න පුළුවන්!

අතිරේක සැලකිලි

GraphQL Subscriptions use කරනකොට තව පොඩි පොඩි දේවල් ගැන හිතන්න වෙනවා.

  • Backpressure: Server එක client එකට වඩා වේගයෙන් data publish කරනකොට client එකට ඒක handle කරන්න බැරි වෙන්න පුළුවන්. Reactor Flux එක automatically backpressure manage කරනවා. onBackpressureBuffer, onBackpressureDrop වගේ operators use කරලා මේක customize කරන්න පුළුවන්.
  • Error Handling: Subscription stream එකක error එකක් ආවොත් ඒක client එකට report වෙන්නේ කොහොමද කියලා සැලකිලිමත් වෙන්න ඕනේ.
  • Authentication & Authorization: Subscriptions වලටත් Queries සහ Mutations වලට වගේම security layer එකක් අවශ්‍යයි. DGS framework එකේ මේ සඳහා Interceptors සහ Security annotations support කරනවා.
  • Scalability: High-volume subscriptions handle කරන්න අවශ්‍ය නම් server-side pub/sub mechanism එකක් (e.g., Redis Pub/Sub, Kafka, RabbitMQ) use කරන්න වෙනවා. MessagePublisher service එකේදී අපි Sinks.Many එකක් use කළාට, real production environment එකකදී external message broker එකක් යොදාගන්න සිද්ධ වෙයි.

මේවා ගැනත් වැඩිදුරට කතා කරන්න පුළුවන්, ඔයාලට අවශ්‍ය නම් comment එකක් දාන්න. එහෙනම් අදට මේක ඇති.

අන්තිම වචනය

දැන් ඔයාලට පැහැදිලියි නේද Spring Boot එක්ක GraphQL Subscriptions implement කරන එක එච්චරම අමාරු දෙයක් නෙවෙයි කියලා. DGS framework එකෙන් මේක හරිම පහසු කරනවා. Real-time updates අවශ්‍ය ඔයාලගේ applications වලට මේකෙන් ලොකු වටිනාකමක් එකතු කරන්න පුළුවන්.

මේකෙන් user experience එක නිකන්ම නිකන් වැඩිදියුණු වෙනවා විතරක් නෙවෙයි, network traffic එකත්, server load එකත් effectively manage කරන්නත් පුළුවන් වෙනවා.

ඉතින්, මේක ඔයාලගේ project එකකට add කරලා බලන්න. මොනවා හරි ප්‍රශ්න තියෙනවා නම්, නැත්නම් මේ ගැන වැඩිදුරට කතා කරන්න ඕනේ නම් පහලින් comment එකක් දාන්න. ඔයාලගේ අත්දැකීම් ගැන දැනගන්නත් අපි ආසයි.

ඊළඟ post එකෙන් හමුවෙමු! වැඩේට බහිමු නේද!