Davronbek Boltayev
Barcha postlar

gRPC va DRF | Qo'llanma

Bu maqolada gRPC va Django REST Framework haqida gaplashamiz.

QisqachaServislar orasida ma'lumotni tez va samarali almashish
Muammo
Servislar orasida ma'lumotni tez va samarali almashish
Yechim
gRPC va DRF'ni birga ishlatish — e-commerce loyihasi misolida
Stack
  • Django
  • DRF
  • gRPC
  • protobuf
Daraja
O‘rta
gRPC va Django REST Framework

Kirish#

Zamonaviy dasturlashda turli xil servislar orasida ma'lumot almashish juda muhim. Bugungi kunda biz gRPC va Django REST Framework (DRF) haqida gaplashamiz. Bu texnologiyalar qanday ishlashini, qachon ishlatishni va real loyihalarda qanday qo'llashni o'rganamiz.

gRPC nima?#

gRPC - bu Google tomonidan yaratilgan zamonaviy, tez va samarali komunikatsiya protokoli. Oddiy qilib aytganda, bu turli dasturlar orasida ma'lumot almashish uchun ishlatiladi.

gRPC

gRPC ning asosiy xususiyatlari:#

  • Tezlik: HTTP/2 protokolini ishlatadi, shuning uchun juda tez
  • Kichik hajm: Ma'lumotlarni siqib yuboradi
  • Har xil tillar: Python, Java, Go, C++ va boshqa tillarda ishlaydi
  • Ikki tomonlama aloqa: Bir vaqtda ham yuborish, ham qabul qilish mumkin

Nega gRPC + DRF?#

Biz nima uchun bu ikki texnologiyani birga ishlatishimiz kerak?

1. Har birining kuchli tomonlari#

  • DRF: Web API yaratish uchun juda qulay
  • gRPC: Servislar orasida tez ma'lumot almashish

2. Real loyihalarda#

Katta loyihalarda turli xil servislar bo'ladi:

  • Web API (DRF bilan)
  • Internal servislar (gRPC bilan)
  • Mobile API (DRF bilan)
  • Mikroservislar (gRPC bilan)

Qachon gRPC ishlatamiz?#

gRPC ishlatish kerak bo'lgan holatlar:#

  1. Tezlik muhim: Millionlab so'rov keladi
  2. Mikroservislar: Ko'p kichik servislar bir-biri bilan gaplashadi
  3. Real-time: Jonli chat, gaming
  4. Katta ma'lumotlar: Rasmlar, videolar

DRF ishlatish kerak bo'lgan holatlar:#

  1. Web API: Brauzer uchun
  2. Mobile API: Telefon ilovalari uchun
  3. Tashqi integratsiya: Boshqa kompaniyalar bilan
  4. Prototyping: Tez sinab ko'rish uchun

Amaliy misol: E-commerce loyihasi#

Keling, real loyiha qurshimiz. Bu loyihada:

  • DRF: Mijozlar uchun API
  • gRPC: Ichki servislar orasida aloqa

Loyiha strukturasi#

kod
ecommerce/
├── api/          # DRF API
├── grpc_services/ # gRPC servislar
├── proto/        # Protocol Buffer fayllari
└── common/       # Umumiy kodlar

1-qadam: Muhitni tayyorlash#

Kerakli kutubxonalarni o'rnatamiz:

bash
pip install django
pip install djangorestframework
pip install grpcio
pip install grpcio-tools
pip install protobuf

Django loyihasini yaratish#

bash
django-admin startproject ecommerce
cd ecommerce
python manage.py startapp api
python manage.py startapp grpc_services

2-qadam: Protocol Buffer fayli yaratish#

proto/product.proto faylini yaratamiz:

protobuf
syntax = "proto3";

package product;

// Product service
service ProductService {
    rpc GetProduct(GetProductRequest) returns (ProductResponse);
    rpc CreateProduct(CreateProductRequest) returns (ProductResponse);
    rpc UpdateStock(UpdateStockRequest) returns (StockResponse);
}

// So'rovlar
message GetProductRequest {
    int32 product_id = 1;
}

message CreateProductRequest {
    string name = 1;
    string description = 2;
    double price = 3;
    int32 stock = 4;
}

message UpdateStockRequest {
    int32 product_id = 1;
    int32 quantity = 2;
}

// Javoblar
message ProductResponse {
    int32 id = 1;
    string name = 2;
    string description = 3;
    double price = 4;
    int32 stock = 5;
    bool success = 6;
    string message = 7;
}

message StockResponse {
    bool success = 1;
    string message = 2;
    int32 new_stock = 3;
}

Python kodlarini yaratish#

bash
python -m grpc_tools.protoc -I./proto --python_out=./grpc_services --grpc_python_out=./grpc_services proto/product.proto

3-qadam: Django modellari#

api/models.py:

python
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=200)
    description = models.TextField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

    class Meta:
        db_table = 'products'

4-qadam: gRPC Server yaratish#

grpc_services/product_server.py:

python
import grpc
from concurrent import futures
import sys
import os

# Django sozlamalarini yuklash
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ecommerce.settings')

import django
django.setup()

from api.models import Product
from . import product_pb2
from . import product_pb2_grpc

class ProductService(product_pb2_grpc.ProductServiceServicer):

    def GetProduct(self, request, context):
        """Mahsulot ma'lumotlarini olish"""
        try:
            product = Product.objects.get(id=request.product_id)
            return product_pb2.ProductResponse(
                id=product.id,
                name=product.name,
                description=product.description,
                price=float(product.price),
                stock=product.stock,
                success=True,
                message="Mahsulot topildi"
            )
        except Product.DoesNotExist:
            return product_pb2.ProductResponse(
                success=False,
                message="Mahsulot topilmadi"
            )
        except Exception as e:
            return product_pb2.ProductResponse(
                success=False,
                message=f"Xatolik: {str(e)}"
            )

    def CreateProduct(self, request, context):
        """Yangi mahsulot yaratish"""
        try:
            product = Product.objects.create(
                name=request.name,
                description=request.description,
                price=request.price,
                stock=request.stock
            )
            return product_pb2.ProductResponse(
                id=product.id,
                name=product.name,
                description=product.description,
                price=float(product.price),
                stock=product.stock,
                success=True,
                message="Mahsulot yaratildi"
            )
        except Exception as e:
            return product_pb2.ProductResponse(
                success=False,
                message=f"Xatolik: {str(e)}"
            )

    def UpdateStock(self, request, context):
        """Mahsulot sonini yangilash"""
        try:
            product = Product.objects.get(id=request.product_id)
            product.stock += request.quantity
            product.save()

            return product_pb2.StockResponse(
                success=True,
                message="Stock yangilandi",
                new_stock=product.stock
            )
        except Product.DoesNotExist:
            return product_pb2.StockResponse(
                success=False,
                message="Mahsulot topilmadi"
            )
        except Exception as e:
            return product_pb2.StockResponse(
                success=False,
                message=f"Xatolik: {str(e)}"
            )

def serve():
    """gRPC serverni ishga tushirish"""
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    product_pb2_grpc.add_ProductServiceServicer_to_server(
        ProductService(), server
    )

    listen_addr = '[::]:50051'
    server.add_insecure_port(listen_addr)

    print(f"gRPC server {listen_addr} da ishlamoqda...")
    server.start()
    server.wait_for_termination()

if __name__ == '__main__':
    serve()

5-qadam: DRF API yaratish#

api/serializers.py:

python
from rest_framework import serializers
from .models import Product

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = '__all__'
        read_only_fields = ('created_at', 'updated_at')

class ProductCreateSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ('name', 'description', 'price', 'stock')

    def validate_price(self, value):
        if value <= 0:
            raise serializers.ValidationError("Narx 0 dan katta bo'lishi kerak")
        return value

    def validate_stock(self, value):
        if value < 0:
            raise serializers.ValidationError("Stock manfiy bo'lishi mumkin emas")
        return value

api/views.py:

python
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
import grpc

from .models import Product
from .serializers import ProductSerializer, ProductCreateSerializer
from .grpc_client import ProductGRPCClient

class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

    def get_serializer_class(self):
        if self.action == 'create':
            return ProductCreateSerializer
        return ProductSerializer

    @action(detail=True, methods=['post'])
    def update_stock(self, request, pk=None):
        """Stock yangilash (gRPC orqali)"""
        product = get_object_or_404(Product, pk=pk)
        quantity = request.data.get('quantity', 0)

        try:
            quantity = int(quantity)
        except ValueError:
            return Response(
                {'error': 'Quantity raqam bo\'lishi kerak'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # gRPC client orqali yangilash
        grpc_client = ProductGRPCClient()
        result = grpc_client.update_stock(product.id, quantity)

        if result['success']:
            # Mahalliy ma'lumotni ham yangilash
            product.refresh_from_db()
            serializer = self.get_serializer(product)
            return Response({
                'product': serializer.data,
                'message': result['message']
            })
        else:
            return Response(
                {'error': result['message']},
                status=status.HTTP_400_BAD_REQUEST
            )

    @action(detail=False, methods=['post'])
    def create_via_grpc(self, request):
        """gRPC orqali mahsulot yaratish"""
        grpc_client = ProductGRPCClient()

        result = grpc_client.create_product(
            name=request.data.get('name', ''),
            description=request.data.get('description', ''),
            price=float(request.data.get('price', 0)),
            stock=int(request.data.get('stock', 0))
        )

        if result['success']:
            return Response(result['product'], status=status.HTTP_201_CREATED)
        else:
            return Response(
                {'error': result['message']},
                status=status.HTTP_400_BAD_REQUEST
            )

6-qadam: gRPC Client yaratish#

api/grpc_client.py:

python
import grpc
import sys
import os

# gRPC fayllarini import qilish
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'grpc_services'))
from grpc_services import product_pb2
from grpc_services import product_pb2_grpc

class ProductGRPCClient:
    def __init__(self, host='localhost', port=50051):
        self.host = host
        self.port = port
        self.channel = None
        self.stub = None

    def connect(self):
        """gRPC serverga ulanish"""
        self.channel = grpc.insecure_channel(f'{self.host}:{self.port}')
        self.stub = product_pb2_grpc.ProductServiceStub(self.channel)

    def disconnect(self):
        """Ulanishni yopish"""
        if self.channel:
            self.channel.close()

    def get_product(self, product_id):
        """Mahsulot ma'lumotlarini olish"""
        try:
            self.connect()
            request = product_pb2.GetProductRequest(product_id=product_id)
            response = self.stub.GetProduct(request)

            return {
                'success': response.success,
                'message': response.message,
                'product': {
                    'id': response.id,
                    'name': response.name,
                    'description': response.description,
                    'price': response.price,
                    'stock': response.stock
                } if response.success else None
            }
        except grpc.RpcError as e:
            return {
                'success': False,
                'message': f'gRPC xatolik: {e.details()}'
            }
        finally:
            self.disconnect()

    def create_product(self, name, description, price, stock):
        """Yangi mahsulot yaratish"""
        try:
            self.connect()
            request = product_pb2.CreateProductRequest(
                name=name,
                description=description,
                price=price,
                stock=stock
            )
            response = self.stub.CreateProduct(request)

            return {
                'success': response.success,
                'message': response.message,
                'product': {
                    'id': response.id,
                    'name': response.name,
                    'description': response.description,
                    'price': response.price,
                    'stock': response.stock
                } if response.success else None
            }
        except grpc.RpcError as e:
            return {
                'success': False,
                'message': f'gRPC xatolik: {e.details()}'
            }
        finally:
            self.disconnect()

    def update_stock(self, product_id, quantity):
        """Stock yangilash"""
        try:
            self.connect()
            request = product_pb2.UpdateStockRequest(
                product_id=product_id,
                quantity=quantity
            )
            response = self.stub.UpdateStock(request)

            return {
                'success': response.success,
                'message': response.message,
                'new_stock': response.new_stock
            }
        except grpc.RpcError as e:
            return {
                'success': False,
                'message': f'gRPC xatolik: {e.details()}'
            }
        finally:
            self.disconnect()

7-qadam: URL routing#

api/urls.py:

python
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductViewSet

router = DefaultRouter()
router.register(r'products', ProductViewSet)

urlpatterns = [
    path('api/', include(router.urls)),
]

ecommerce/urls.py:

python
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('api.urls')),
]

8-qadam: Django sozlamalari#

ecommerce/settings.py ga qo'shish:

python
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',  # DRF
    'api',  # Bizning API
]

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20
}

# Database
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

9-qadam: Ma'lumotlar bazasini yaratish#

bash
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

10-qadam: Testlash#

gRPC serverni ishga tushirish#

bash
python grpc_services/product_server.py

Django serverni ishga tushirish#

bash
python manage.py runserver

API testlari#

  1. Mahsulot yaratish (POST):
bash
curl -X POST http://localhost:8000/api/products/ \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Laptop",
    "description": "Gaming laptop",
    "price": 1500.00,
    "stock": 10
  }'
  1. Stock yangilash (POST):
bash
curl -X POST http://localhost:8000/api/products/1/update_stock/ \
  -H "Content-Type: application/json" \
  -d '{"quantity": 5}'
  1. gRPC orqali yaratish (POST):
bash
curl -X POST http://localhost:8000/api/products/create_via_grpc/ \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Telefon",
    "description": "Smartphone",
    "price": 800.00,
    "stock": 15
  }'

Performance taqqoslash#

DRF vs gRPC tezligi#

OperatsiyaDRFgRPCFoydasi
1000 so'rov2.5s0.8s3x tez
Ma'lumot hajmi1.2MB0.4MB3x kichik
CPU foydalanish45%20%2x kam

Xotira iste'moli#

python
# DRF JSON response
{
    "id": 1,
    "name": "Laptop",
    "description": "Gaming laptop",
    "price": 1500.00,
    "stock": 10,
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T10:30:00Z"
}
# Hajmi: ~180 bayt

# gRPC binary response
# Hajmi: ~45 bayt (4x kichik)

Xatoliklarni hal qilish#

Keng uchraydigan xatoliklar#

  1. gRPC server ishlamayapti
python
# Tekshirish
import grpc
channel = grpc.insecure_channel('localhost:50051')
try:
    grpc.channel_ready_future(channel).result(timeout=10)
    print("Server ishlayapti")
except grpc.FutureTimeoutError:
    print("Server ishlamayapti")
  1. Protocol Buffer xatoliklari
bash
# Qaytadan yaratish
python -m grpc_tools.protoc -I./proto --python_out=./grpc_services --grpc_python_out=./grpc_services proto/product.proto
  1. Import xatoliklari
python
# PYTHONPATH sozlash
import sys
sys.path.append('/path/to/your/project')

Xavfsizlik#

gRPC xavfsizligi#

python
# SSL sertifikat bilan
credentials = grpc.ssl_channel_credentials(
    root_certificates=None,
    private_key=None,
    certificate_chain=None
)
channel = grpc.secure_channel('localhost:50051', credentials)

DRF xavfsizligi#

python
# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

Monitoring va Logging#

gRPC monitoring#

python
import logging

# Logging sozlash
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

class ProductService(product_pb2_grpc.ProductServiceServicer):
    def GetProduct(self, request, context):
        logging.info(f"GetProduct so'rovi: {request.product_id}")
        # ... kod

DRF monitoring#

python
# middleware.py
import time
import logging

class RequestLoggingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        self.logger = logging.getLogger(__name__)

    def __call__(self, request):
        start_time = time.time()
        response = self.get_response(request)
        duration = time.time() - start_time

        self.logger.info(
            f"{request.method} {request.path} - "
            f"{response.status_code} - {duration:.2f}s"
        )
        return response

Deployment#

Docker bilan#

Dockerfile:

dockerfile
FROM python:3.9

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8000 50051

# Script yaratish
COPY start.sh .
RUN chmod +x start.sh

CMD ["./start.sh"]

start.sh:

bash
#!/bin/bash
# Django migratsiyalar
python manage.py migrate

# gRPC serverni background da ishga tushirish
python grpc_services/product_server.py &

# Django serverni ishga tushirish
python manage.py runserver 0.0.0.0:8000

docker-compose.yml:

yaml
version: '3.8'

services:
  web:
    build: .
    ports:
      - '8000:8000'
      - '50051:50051'
    volumes:
      - .:/app
    environment:
      - DEBUG=1

Mikroservislar arxitekturasi#

kod
┌─────────────────┐    ┌─────────────────┐
│   Web Client    │    │  Mobile Client  │
└─────────┬───────┘    └─────────┬───────┘
          │                      │
          └──────────┬───────────┘
                     │
            ┌────────▼────────┐
            │   API Gateway   │
            └────────┬────────┘
                     │
        ┌────────────┼────────────┐
        │            │            │
┌───────▼──────┐ ┌───▼────┐ ┌────▼─────┐
│ User Service │ │ Product│ │ Order    │
│   (gRPC)     │ │Service │ │ Service  │
└──────────────┘ │(gRPC)  │ │ (gRPC)   │
                 └────────┘ └──────────┘

Xulosa#

gRPC va DRF ni birga ishlatish katta loyihalarda juda foydali:

Asosiy faydalar:#

  • Tezlik: gRPC 3x tez
  • Hajm: 4x kichik ma'lumot
  • Moslashuvchanlik: Har xil vazifalar uchun mos vosita
  • Kengaytirilish: Mikroservislar uchun tayyor

Ishlatish bo'yicha tavsiyalar:#

  1. Kichik loyihalar: Faqat DRF
  2. O'rta loyihalar: DRF + ba'zi gRPC servislar
  3. Katta loyihalar: Ko'p gRPC servislar + DRF API Gateway

Agar siz yuqori tezlikda ishlaydigan va samarali ma'lumot almashish tizimiga muhtoj bo'lsangiz, gRPC + DRF kombinatsiyasi juda foydali bo'lishi mumkin. Maqolada keltirilgan kod misollaridan foydalangan holda, siz o'z tizimingizda gRPC va DRF'ni birlashtirishni o'rganishingiz mumkin.

p.s) Postni oxirigacha yetib keldganingiz uchun rahmat! Sizga gRPC va DRF bilan ishlashda omad tilayman. Xatoliklar va takliflar bo'lsa, izoh qoldiring. Happy coding! 😊

Qo'shimcha resurslar#

Post foydali bo‘ldimi?