Nahtlose Integration von GraphQL und REST in einem einzigen Backend-Framework
Wenhao Wang
Dev Intern · Leapcell

Einleitung
In der sich ständig weiterentwickelnden Landschaft der Backend-Entwicklung sind Flexibilität und Anpassungsfähigkeit von größter Bedeutung. Moderne Anwendungen bedienen oft eine Vielzahl von Clients – von traditionellen Webbrowsern über mobile Apps und IoT-Geräte bis hin zu anderen Backend-Diensten. Jeder dieser Clients kann unterschiedliche Anforderungen und Präferenzen für den Datenabruf haben. Während RESTful APIs seit langem der De-facto-Standard sind, hat sich GraphQL als leistungsstarke Alternative herauskristallisiert, die mehr Effizienz und Flexibilität bei der Datenabfrage bietet. Die Herausforderung besteht dann darin, wie wir unser Backend befähigen können, beide Lager effektiv zu bedienen und gleichzeitig die Vertrautheit und breite Unterstützung von REST neben den innovativen Datenabfragefähigkeiten von GraphQL anzubieten. Hier geht es nicht nur darum, auf unterschiedliche Client-Bedürfnisse einzugehen, sondern auch darum, Entwicklungs-Workflows zu optimieren, Datenentwicklungen zu verwalten und möglicherweise eine schrittweise Migrationsstrategie zu ermöglichen. Dieser Artikel befasst sich mit den Strategien für die erfolgreiche Bereitstellung von GraphQL- und REST-APIs innerhalb eines einzigen Backend-Frameworks und untersucht die Vorteile, Herausforderungen und praktischen Implementierungsansätze.
Die Dual-API-Strategie erklärt
Bevor wir zum "Wie" kommen, lassen Sie uns einige Kernkonzepte klären, die unserer Diskussion zugrunde liegen werden.
Kernterminologie
- REST (Representational State Transfer): Ein architektonischer Stil für vernetzte Hypermedia-Anwendungen. Er betont zustandslose Client-Server-Kommunikation, eine einheitliche Schnittstelle und ressourcenbasierte Interaktionen (z. B.
/users
,/products
). REST APIs verwenden typischerweise Standard-HTTP-Methoden (GET, POST, PUT, DELETE) und geben feste Datenstrukturen zurück. - GraphQL: Eine Abfragesprache für APIs und eine Laufzeitumgebung zur Erfüllung dieser Abfragen mit Ihren vorhandenen Daten. Sie ermöglicht es Clients, genau die Daten anzufordern, die sie benötigen, nicht mehr und nicht weniger. GraphQL-APIs stellen einen einzigen Endpunkt bereit, und Daten werden mit einem Typsystem beschrieben.
- Schema (GraphQL): Eine zentrale Definition aller Datentypen und Operationen (Queries, Mutations, Subscriptions), die über Ihre GraphQL-API verfügbar sind. Es fungiert als Vertrag zwischen Client und Server.
- Resolvers (GraphQL): Funktionen, die für den Abruf der tatsächlichen Daten für jedes Feld in Ihrem GraphQL-Schema verantwortlich sind. Sie verbinden die Schema-Definition mit den Datenquellen Ihres Backends (Datenbanken, andere Dienste).
- Middleware: Software, die zwischen dem Betriebssystem oder der Datenbank und den Anwendungen sitzt und die Kommunikation und Datenverwaltung für verteilte Anwendungen ermöglicht. In Web-Frameworks werden Aufgaben wie Authentifizierung, Protokollierung und Routing häufig von Middleware übernommen.
Die Begründung für eine Dual-API
Warum sollten Sie beides wollen?
- Client-Vielfalt: Einige Clients bevorzugen möglicherweise die Einfachheit und die Caching-Vorteile von REST, während andere die Präzision und Effizienz von GraphQL verlangen.
- Schrittweise Einführung/Migration: Wenn Sie eine bestehende REST-API haben, können Sie GraphQL inkrementell einführen, ohne eine vollständige Neufassung vorzunehmen, und den Clients erlauben, in ihrem eigenen Tempo zu migrieren.
- Spezifische Anwendungsfälle: Bestimmte Aufgaben, wie komplexe Datenaggregationen oder Echtzeit-Updates (über GraphQL Subscriptions), werden oft eleganter von GraphQL gehandhabt. Einfachere CRUD-Operationen für einzelne Ressourcen sind möglicherweise perfekt für REST geeignet.
- Entwicklerpräferenz: Unterschiedliche Teams oder Entwickler haben möglicherweise Expertise und Präferenz für das eine oder das andere.
Implementierungsstrategien
Die Kernidee ist, die Fähigkeiten Ihres Backend-Frameworks zu nutzen, um Anfragen für beide API-Stile unabhängig voneinander zu routen und zu verarbeiten, während so viel wie möglich von der zugrunde liegenden Geschäftslogik und Datenzugriff gemeinsam genutzt wird.
Strategie 1: Separate Endpunkte, gemeinsame Geschäftslogik
Dies ist der gebräuchlichste und oft empfohlene Ansatz.
- REST-Endpunkt-Hierarchie: Ihre REST-API folgt typischerweise einer ressourcenorientierten Struktur, z. B.
/api/v1/users
,/api/v1/products/{id}
. Jeder Endpunkt ist einer bestimmten Controller- oder Ansichtsfunktion zugeordnet. - GraphQL-Endpunkt: Ihre GraphQL-API stellt normalerweise einen einzigen Einstiegspunkt bereit, z. B.
/graphql
. Alle GraphQL-Abfragen und -Mutationen laufen über diesen Endpunkt.
Wie sie interagieren: Sowohl die REST-Controller als auch die GraphQL-Resolver sollten in einen gemeinsamen Satz von Funktionen der Service-Schicht oder Repository-Schicht aufrufen, die Ihre Kern-Geschäftslogik kapseln und mit Ihrer Datenbank oder anderen externen Diensten interagieren. Dies verhindert Code-Duplizierung und gewährleistet die Datenkonsistenz über beide APIs hinweg.
**Beispiel (Konzeptionell in einer Python/Django-Umgebung):
# --- api_project/core_app/services.py --- # Dies ist Ihre gemeinsame Geschäftslogikschicht def get_all_users(): # Logik zum Abrufen von Benutzern aus der DB return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}] def get_user_by_id(user_id): # Logik zum Abrufen eines einzelnen Benutzers return {"id": user_id, "name": f"User {user_id}"} def create_user(data): # Logik zum Erstellen eines Benutzers return {"id": 3, "name": data['name']} # --- api_project/rest_app/views.py (Django REST Framework) --- from rest_framework.views import APIView from rest_framework.response import Response from api_project.core_app.services import get_all_users, get_user_by_id, create_user class UserListAPIView(APIView): def get(self, request): users = get_all_users() return Response(users) def post(self, request): user = create_user(request.data) return Response(user, status=201) class UserDetailAPIView(APIView): def get(self, request, user_id): user = get_user_by_id(user_id) if not user: return Response({"error": "User not found"}, status=404) return Response(user) # --- api_project/graphql_app/schema.py (Graphene-Python) --- import graphene from api_project.core_app.services import get_all_users, get_user_by_id, create_user class UserType(graphene.ObjectType): id = graphene.Int() name = graphene.String() class Query(graphene.ObjectType): all_users = graphene.List(UserType) user = graphene.Field(UserType, user_id=graphene.Int(required=True)) def resolve_all_users(root, info): return get_all_users() def resolve_user(root, info, user_id): return get_user_by_id(user_id) class CreateUser(graphene.Mutation): class Arguments: name = graphene.String(required=True) Output = UserType def mutate(root, info, name): user = create_user({"name": name}) return UserType(**user) class Mutation(graphene.ObjectType): create_user = CreateUser.Field() schema = graphene.Schema(query=Query, mutation=Mutation) # --- api_project/urls.py (Django Routing) --- from django.urls import path, include from graphene_django.views import GraphQLView from api_project.rest_app.views import UserListAPIView, UserDetailAPIView urlpatterns = [ # REST API routes path('api/v1/users/', UserListAPIView.as_view(), name='user-list'), path('api/v1/users/<int:user_id>/', UserDetailAPIView.as_view(), name='user-detail'), # GraphQL API route path('graphql/', GraphQLView.as_view(graphiql=True, schema=schema)), ] ```} In diesem Beispiel delegieren sowohl `UserListAPIView`/`UserDetailAPIView` (REST) als auch `resolve_all_users`/`resolve_user`/`mutate` (GraphQL) die eigentlichen Datenoperationen an das Modul `api_project.core_app.services`. Dies ist entscheidend, um eine DRY (Don't Repeat Yourself)-Codebasis beizubehalten. #### Strategie 2: GraphQL als API-Gateway für REST Bei diesem fortgeschritteneren Muster fungiert der GraphQL-Server selbst als Fassade, die Daten aus vorhandenen REST-Endpunkten oder Microservices abruft. Dies ist besonders nützlich für: * **Frontends, die hochspezifische Daten benötigen:** Der GraphQL-Server kann Daten aus mehreren REST-Ressourcen zu einer einzigen, optimierten Antwort "zusammenfügen". * **Einwickeln von Legacy-REST-APIs:** Modernisieren Sie den Datenzugriff schrittweise, ohne die ursprünglichen REST-Dienste zu ändern. * **Microservice-Architekturen:** Ein GraphQL-Gateway kann eine einheitliche API über verschiedene Microservices hinweg bereitstellen. **Implementierung:** GraphQL-Resolver rufen anstatt direkt Service-Layer-Funktionen aufzurufen, HTTP-Aufrufe an Ihre internen oder externen REST-Endpunkte durch, verarbeiten die Daten und geben sie zurück. **Beispiel (Konzeptionell Node.js mit Apollo Server): ```javascript // --- graphql_server/datasources/UserService.js --- const { RESTDataSource } = require('apollo-datasource-rest'); class UsersAPI extends RESTDataSource { constructor() { super(); this.baseURL = 'http://localhost:3000/api/v1/'; // Ihre interne REST-API } async getAllUsers() { return this.get('users'); } async getUserById(id) { return this.get(`users/${id}`); } } // --- graphql_server/index.js --- const { ApolloServer, gql } = require('apollo-server'); const UsersAPI = require('./datasources/UserService'); const typeDefs = gql` type User { id: ID! name: String } type Query { users: [User] user(id: ID!): User } `; const resolvers = { Query: { users: (_, __, { dataSources }) => dataSources.usersAPI.getAllUsers(), user: (_, { id }, { dataSources }) => dataSources.usersAPI.getUserById(id), }, }; const server = new ApolloServer({ typeDefs, resolvers, dataSources: () => ({ usersAPI: new UsersAPI(), }), }); server.listen().then(({ url }) => { console.log(`🚀 GraphQL Server ready at ${url}`); }); ```} In diesem Node.js-Beispiel ist der GraphQL-Server selbst eine separate Anwendung, die Daten von einem REST-Dienst abzieht. Dies ist ein gängiges Muster, wenn GraphQL als Aggregationsschicht fungiert. Ihr primäres Backend-Framework würde weiterhin die ursprünglichen REST-Endpunkte bereitstellen, und dieser GraphQL-Server würde sie dann konsumieren. #### Überlegungen für eine Dual-API * **Authentifizierung/Autorisierung:** Stellen Sie eine konsistente und robuste Authentifizierungs- und Autorisierungsmechanismus für beide APIs sicher. JWTs (JSON Web Tokens) sind eine beliebte Wahl, die sowohl für REST-Endpunkte als auch für GraphQL-Anfragen validiert werden kann. * **Fehlerbehandlung:** Definieren Sie klare und konsistente Fehlerformate für beide API-Typen. Während GraphQL eine standardisierte Fehlerstruktur aufweist, sollte Ihre REST-API ebenfalls einem vorhersehbaren Muster folgen (z. B. unter Verwendung von HTTP-Statuscodes und einer JSON-Fehlerantwort). * **Datenvalidierung:** Implementieren Sie die serverseitige Datenvalidierung für alle Eingaben, sei es aus einem REST-Body oder GraphQL-Eingabetypen. * **Tooling und Ökosystem:** REST profitiert von einem riesigen Ökosystem von Tools (Postman, curl, OpenAPI/Swagger). GraphQL hat seine eigenen hervorragenden Tools (GraphiQL, Apollo Client, Relay). Beachten Sie, wie Ihre Entwicklungs- und Client-Teams mit jeder API interagieren werden. * **Caching:** REST kann HTTP-Caching effektiv nutzen. GraphQL erfordert typischerweise Client-seitiges Caching (wie den normalisierten Cache von Apollo Client) oder Caching-Strategien auf Anwendungsebene. * **Dokumentation:** Pflegen Sie separate, klare Dokumentationen für beide APIs. OpenAPI/Swagger für REST und GraphQL Schema Definition Language (SDL) mit Tools wie Apollo Studio für GraphQL. ### Anwendungsfälle * **Entwicklung einer neuen Anwendung mit Altanbindung:** Beginnen Sie mit GraphQL für neue clientseitige Funktionen, während Sie REST für die Interaktion mit bestehenden internen Systemen beibehalten. * **Mobile-First-Entwicklung:** Bieten Sie GraphQL für mobile Apps an, um Bandbreite und Netzwerkanfragen zu optimieren, während bestehende Weboberflächen weiterhin REST verwenden könnten. * **API-Entwicklung:** Führen Sie schrittweise GraphQL in eine bestehende REST-API ein, ohne die Abwärtskompatibilität für aktuelle Clients zu beeinträchtigen. * **Microservice-Orchestrierung:** Verwenden Sie GraphQL als API-Gateway, um Daten aus mehreren Microservices zusammenzuführen, die nur REST-Schnittstellen bereitstellen. ## Fazit Die Bereitstellung von GraphQL- und REST-APIs innerhalb eines einzigen Backend-Frameworks bietet eine beispiellose Flexibilität, die auf unterschiedliche Client-Anforderungen zugeschnitten ist und eine strategische API-Entwicklung ermöglicht. Durch die gemeinsame Nutzung von Kern-Geschäftslogik und intelligentes Routing von Anfragen können Entwickler robuste, anpassungsfähige Systeme aufbauen, die die Stärken beider Architekturstile nutzen. Der Schlüssel zum Erfolg liegt in der Aufrechterhaltung einer klaren Trennung der Zuständigkeiten, der gemeinsamen Nutzung gemeinsamer Geschäftslogik und der sorgfältigen Berücksichtigung von Authentifizierung, Fehlerbehandlung und Dokumentation für jede API. Dieser zweigleisige Ansatz ermöglicht es Ihren Backend-Diensten, sowohl effizient als auch breit zugänglich zu sein und Ihre Datenebene für die Zukunft zu sichern. Letztendlich ermöglicht diese Strategie maximale Flexibilität für konsumierende Clients und minimiert gleichzeitig redundanten Code auf dem Server.