Apollo Server के साथ GraphQL BFF बनाना

ग्राफक्यूएल बीएफएफ और एपोलो सर्वर का उपयोग करके फ्रंटएंड एपीआई को अनुकूलित करें

Page content

बैकएंड फॉर फ्रंटएंड (BFF) पैटर्न के साथ ग्राफक्यूएल और एपोलो सर्वर का संयोजन आधुनिक वेब एप्लिकेशनों के लिए एक शक्तिशाली आर्किटेक्चर बनाता है।

इस दृष्टिकोण से आप क्लाइंट-ऑप्टिमाइज्ड एपीआई बना सकते हैं जो कई स्रोतों से डेटा एकत्र करते हैं जबकि क्लीन सेपरेशन ऑफ कंसेप्ट्स बनाए रखते हैं।

git-flow

BFF पैटर्न को समझना

बैकएंड फॉर फ्रंटएंड पैटर्न ने वेब, मोबाइल, और डेस्कटॉप जैसे विभिन्न फ्रंटएंड एप्लिकेशनों को समर्थन करने के चुनौतियों का समाधान किया है जिनके पास अलग-अलग डेटा आवश्यकताएं होती हैं। एकल सामान्य एपीआई का उपयोग करने के बजाय, BFF प्रत्येक क्लाइंट की विशिष्ट आवश्यकताओं के लिए अनुकूलित डेडिकेटेड बैकएंड सर्विसेस बनाता है।

BFF के प्रमुख लाभ

  • क्लाइंट-ऑप्टिमाइज्ड एपीआई: प्रत्येक फ्रंटएंड को वह डेटा मिलता है जो उसे आवश्यक है और वह प्रारूप जिसमें उसे अपेक्षित है
  • कम क्लाइंट जटिलता: डेटा एग्रीगेशन और ट्रांसफॉर्मेशन लॉजिक बैकएंड में स्थानांतरित हो जाता है
  • स्वतंत्र विकास: फ्रंटएंड्स अन्य क्लाइंट्स या कोर सर्विसेस को प्रभावित किए बिना विकसित हो सकते हैं
  • बेहतर प्रदर्शन: कम राउंड ट्रिप्स और छोटे पेलोड्स एप्लिकेशन की गति को बढ़ाते हैं
  • टीम स्वायत्तता: फ्रंटएंड टीमें अपने BFF को स्वामित्व में ले सकती हैं, जिससे तेज़ इटरेशन संभव होता है

ग्राफक्यूएल BFF के लिए क्यों आदर्श है

ग्राफक्यूएल का लचीला क्वेरी भाषा इसे BFF कार्यान्वयन के लिए आदर्श बनाता है:

  1. सटीक डेटा फेट्चिंग: क्लाइंट्स केवल उन फील्ड्स का अनुरोध करते हैं जिन्हें उन्हें आवश्यक है
  2. एकल अनुरोध: एक ही क्वेरी में कई स्रोतों से डेटा को संयोजित करें
  3. मजबूत टाइपिंग: स्कीमा फ्रंटएंड और बैकएंड के बीच स्पष्ट अनुबंध प्रदान करता है
  4. रियल-टाइम क्षमताएं: सब्सक्रिप्शन्स लाइव डेटा अपडेट्स की अनुमति देते हैं
  5. डेवलपर अनुभव: इंट्रोस्पेक्शन और ग्राफक्यूएल प्लेग्राउंड विकास को सरल बनाते हैं

एपोलो सर्वर के साथ BFF सेटअप करना

एपोलो सर्वर ग्राफक्यूएल BFF लेयर्स को बनाने के लिए एक मजबूत आधार प्रदान करता है। आइए एक प्रोडक्शन-रेडी कार्यान्वयन बनाने के लिए एक चरण-दर-चरण मार्गदर्शिका देखें।

इंस्टॉलेशन और बेसिक सेटअप

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { gql } from 'graphql-tag';

// अपने ग्राफक्यूएल स्कीमा को परिभाषित करें
const typeDefs = gql`
  type User {
    id: ID!
    email: String!
    name: String!
    orders: [Order!]!
  }

  type Order {
    id: ID!
    status: String!
    total: Float!
    items: [OrderItem!]!
  }

  type OrderItem {
    id: ID!
    productId: ID!
    quantity: Int!
    price: Float!
  }

  type Query {
    me: User
    user(id: ID!): User
    orders(userId: ID!): [Order!]!
  }
`;

// रिज़ॉल्वर्स को लागू करें
const resolvers = {
  Query: {
    me: async (_, __, { dataSources, user }) => {
      return dataSources.userAPI.getUser(user.id);
    },
    user: async (_, { id }, { dataSources }) => {
      return dataSources.userAPI.getUser(id);
    },
    orders: async (_, { userId }, { dataSources }) => {
      return dataSources.orderAPI.getOrdersByUser(userId);
    },
  },
  User: {
    orders: async (parent, _, { dataSources }) => {
      return dataSources.orderAPI.getOrdersByUser(parent.id);
    },
  },
};

// एपोलो सर्वर इंस्टेंस बनाएं
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

// सर्वर को स्टार्ट करें
const { url } = await startStandaloneServer(server, {
  context: async ({ req }) => ({
    token: req.headers.authorization,
    dataSources: {
      userAPI: new UserAPI(),
      orderAPI: new OrderAPI(),
    },
  }),
  listen: { port: 4000 },
});

console.log(`🚀 सर्वर ${url} पर तैयार है`);

डेटा स्रोतों को लागू करना

डेटा स्रोत विभिन्न बैकएंड्स से डेटा प्राप्त करने के लिए एक क्लीन अभिसरण प्रदान करते हैं:

import { RESTDataSource } from '@apollo/datasource-rest';

class UserAPI extends RESTDataSource {
  override baseURL = 'https://api.example.com/users/';

  async getUser(id: string) {
    return this.get(`${id}`);
  }

  async getUsersByIds(ids: string[]) {
    return Promise.all(ids.map(id => this.getUser(id)));
  }

  async updateUser(id: string, data: any) {
    return this.patch(`${id}`, { body: data });
  }
}

class OrderAPI extends RESTDataSource {
  override baseURL = 'https://api.example.com/orders/';

  async getOrdersByUser(userId: string) {
    return this.get('', {
      params: { userId },
    });
  }

  async getOrder(id: string) {
    return this.get(`${id}`);
  }
}

डेटालोडर के साथ ऑप्टिमाइज करना

डेटालोडर अनुरोधों को बैच और कैश करता है ताकि N+1 क्वेरी समस्या से बचा जा सके:

import DataLoader from 'dataloader';

const createUserLoader = (userAPI: UserAPI) =>
  new DataLoader(async (ids: readonly string[]) => {
    const users = await userAPI.getUsersByIds([...ids]);
    return ids.map(id => users.find(user => user.id === id));
  });

// कॉन्टेक्स्ट में उपयोग करें
const context = async ({ req }) => ({
  dataSources: {
    userAPI: new UserAPI(),
    orderAPI: new OrderAPI(),
  },
  loaders: {
    userLoader: createUserLoader(new UserAPI()),
  },
});

एडवांस्ड BFF पैटर्न

कई सर्विसों को एग्रीगेट करना

BFF की मुख्य ताकतों में से एक है कई बैकएंड सर्विसों से डेटा को संयोजित करना:

const resolvers = {
  Query: {
    dashboard: async (_, __, { dataSources, user }) => {
      // कई सर्विसों से डेटा को समानांतर में फेट्च करें
      const [userData, orders, recommendations, analytics] = await Promise.all([
        dataSources.userAPI.getUser(user.id),
        dataSources.orderAPI.getRecentOrders(user.id),
        dataSources.recommendationAPI.getRecommendations(user.id),
        dataSources.analyticsAPI.getUserStats(user.id),
      ]);

      return {
        user: userData,
        recentOrders: orders,
        recommendations,
        stats: analytics,
      };
    },
  },
};

एरर हैंडलिंग और रिज़िलिएंस

प्रोडक्शन BFF के लिए मजबूत एरर हैंडलिंग लागू करें:

import { GraphQLError } from 'graphql';

const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources }) => {
      try {
        const user = await dataSources.userAPI.getUser(id);

        if (!user) {
          throw new GraphQLError('उपयोगकर्ता नहीं मिला', {
            extensions: {
              code: 'NOT_FOUND',
              http: { status: 404 },
            },
          });
        }

        return user;
      } catch (error) {
        if (error instanceof GraphQLError) throw error;

        throw new GraphQLError('उपयोगकर्ता प्राप्त करने में विफल', {
          extensions: {
            code: 'INTERNAL_SERVER_ERROR',
            originalError: error.message,
          },
        });
      }
    },
  },
};

कैशिंग रणनीतियां

प्रदर्शन को सुधारने के लिए कुशल कैशिंग लागू करें:

import { KeyValueCache } from '@apollo/utils.keyvaluecache';
import { InMemoryLRUCache } from '@apollo/utils.keyvaluecache';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  cache: new InMemoryLRUCache({
    maxSize: Math.pow(2, 20) * 100, // 100 MB
    ttl: 300, // 5 मिनट
  }),
  plugins: [
    {
      async requestDidStart() {
        return {
          async willSendResponse({ response, contextValue }) {
            // कैश कंट्रोल हेडर्स सेट करें
            response.http.headers.set(
              'Cache-Control',
              'max-age=300, public'
            );
          },
        };
      },
    },
  ],
});

ऑथेंटिकेशन और ऑथोराइजेशन

अपने BFF को उचित ऑथेंटिकेशन और ऑथोराइजेशन के साथ सुरक्षित करें:

import jwt from 'jsonwebtoken';

const context = async ({ req }) => {
  const token = req.headers.authorization?.replace('Bearer ', '');

  if (!token) {
    return { user: null };
  }

  try {
    const user = jwt.verify(token, process.env.JWT_SECRET);
    return { user };
  } catch (error) {
    throw new GraphQLError('अमान्य ऑथेंटिकेशन टोकन', {
      extensions: { code: 'UNAUTHENTICATED' },
    });
  }
};

// रिज़ॉल्वर्स को सुरक्षित करें
const resolvers = {
  Query: {
    me: (_, __, { user }) => {
      if (!user) {
        throw new GraphQLError('आपको लॉग इन करना होगा', {
          extensions: { code: 'UNAUTHENTICATED' },
        });
      }
      return user;
    },
  },
};

माइक्रोसर्विसेस के लिए एपोलो फेडरेशन

कई टीमों और सर्विसों के साथ काम करते समय, एपोलो फेडरेशन एक वितरित ग्राफक्यूएल आर्किटेक्चर की अनुमति देता है:

import { buildSubgraphSchema } from '@apollo/subgraph';

const typeDefs = gql`
  extend schema
    @link(url: "https://specs.apollo.dev/federation/v2.3",
          import: ["@key", "@shareable"])

  type User @key(fields: "id") {
    id: ID!
    email: String!
    name: String!
  }
`;

const resolvers = {
  User: {
    __resolveReference: async (reference, { dataSources }) => {
      return dataSources.userAPI.getUser(reference.id);
    },
  },
};

const server = new ApolloServer({
  schema: buildSubgraphSchema({ typeDefs, resolvers }),
});

प्रदर्शन सुधार के टिप्स

  1. डेटालोडर का उपयोग करें: हमेशा डेटालोडर लागू करें ताकि अनुरोधों को बैच और कैश किया जा सके
  2. फील्ड-लेवल कैशिंग लागू करें: महंगे कंप्यूटेशन को फील्ड स्तर पर कैश करें
  3. क्वेरी जटिलता विश्लेषण: क्वेरी गहराई और जटिलता को सीमित करें ताकि दुरुपयोग से बचा जा सके
  4. पर्सिस्टेड क्वेरी: प्रोडक्शन में पर्सिस्टेड क्वेरी का उपयोग करें ताकि पेलोड आकार कम हो सके
  5. रिस्पॉन्स कम्प्रेशन: रिस्पॉन्स के लिए gzip/brotli कम्प्रेशन सक्षम करें
  6. क्वेरी प्रदर्शन निगरानी: धीमी क्वेरी को ट्रैक करें और रिज़ॉल्वर्स को ऑप्टिमाइज करें
  7. स्टैटिक स्कीमा के लिए CDN का उपयोग करें: एज पर इंट्रोस्पेक्शन क्वेरी को कैश करें

अपने BFF का परीक्षण करना

अपने ग्राफक्यूएल BFF के लिए व्यापक परीक्षण लिखें:

import { ApolloServer } from '@apollo/server';

describe('User Queries', () => {
  let server: ApolloServer;

  beforeAll(() => {
    server = new ApolloServer({
      typeDefs,
      resolvers,
    });
  });

  it('id द्वारा उपयोगकर्ता फेट्च करता है', async () => {
    const result = await server.executeOperation({
      query: `
        query GetUser($id: ID!) {
          user(id: $id) {
            id
            email
            name
          }
        }
      `,
      variables: { id: '123' },
    });

    expect(result.body.kind).toBe('single');
    if (result.body.kind === 'single') {
      expect(result.body.singleResult.data?.user).toEqual({
        id: '123',
        email: 'test@example.com',
        name: 'Test User',
      });
    }
  });
});

डिप्लॉयमेंट विचार

कंटेनराइजेशन

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 4000

CMD ["node", "dist/index.js"]

पर्यावरण कॉन्फ़िगरेशन

पर्यावरण चरों का उपयोग कॉन्फ़िगरेशन के लिए करें:

const config = {
  port: process.env.PORT || 4000,
  userServiceUrl: process.env.USER_SERVICE_URL,
  orderServiceUrl: process.env.ORDER_SERVICE_URL,
  jwtSecret: process.env.JWT_SECRET,
  nodeEnv: process.env.NODE_ENV || 'development',
};

निगरानी और अवलोकन

विस्तृत निगरानी लागू करें:

import { ApolloServerPluginInlineTrace } from '@apollo/server/plugin/inlineTrace';
import { ApolloServerPluginLandingPageDisabled } from '@apollo/server/plugin/disabled';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [
    ApolloServerPluginInlineTrace(),
    process.env.NODE_ENV === 'production'
      ? ApolloServerPluginLandingPageDisabled()
      : ApolloServerPluginLandingPageLocalDefault(),
    {
      async requestDidStart() {
        const start = Date.now();
        return {
          async willSendResponse({ operationName, contextValue }) {
            const duration = Date.now() - start;
            console.log(`Operation ${operationName} took ${duration}ms`);
          },
        };
      },
    },
  ],
});

टालने के लिए सामान्य गलतियाँ

  1. N+1 क्वेरी समस्या: संबंधित डेटा के लिए हमेशा DataLoader का उपयोग करें
  2. बैकएंड से ओवर-फेचिंग: GraphQL चयन सेट्स के आधार पर बैकएंड क्वेरीज को अनुकूलित करें
  3. एरर हैंडलिंग का अभाव: उचित एरर हैंडलिंग और लॉगिंग लागू करें
  4. नो रेट लिमिटिंग: रेट लिमिटिंग के साथ अपने BFF को दुरुपयोग से बचाएं
  5. सुरक्षा की उपेक्षा: इनपुट्स को वैलिडेट करें, ऑथेंटिकेशन लागू करें, और क्वेरी जटिलता को सीमित करें
  6. खराब स्कीमा डिजाइन: क्लाइंट की आवश्यकताओं के बारे में सोचकर स्कीमाओं का डिजाइन करें
  7. नो कैशिंग स्ट्रैटेजी: कई स्तरों पर कैशिंग लागू करें

उपयोगी लिंक्स