Maqola · 2026-08-16 · ~8 daqiqa o‘qiladi · murakkab
Backend'ni mobil ilovaga moslash: versiyalash, payload va offline
Mundarija
Seriyaning avvalgi qismlarida Metro backend'ining bloklarini qurdik. Yakuniy qism — ularni birlashtiruvchi savol: mobil mijozga mos API qanday bo'ladi? Chunki mobil ilova brauzer emas, uning o'z qonunlari bor:
- foydalanuvchi ilovani yangilamasligi mumkin — bir yil oldingi versiya bugun ham production'ga so'rov yuboradi;
- tarmoq sekin va uzilib turadi — metroda ayniqsa;
- har ortiqcha so'rov — batareya va trafik;
- xato yuz bersa foydalanuvchi "qayta yuklash" tugmasini emas, ilovani o'chirib qo'yishni tanlaydi.
1-qoida: versiya URL'da, buzuvchi o'zgarish — yangi versiyada#
Saytda xato qildingiz — deploy qilasiz, bir daqiqada hammada yangi kod. Mobilda esa yangi versiya App Store tekshiruvidan o'tishi va foydalanuvchi uni o'rnatishi kerak. Siz maydon nomini o'zgartirgan kuni eski ilovalar sinadi — va ularni siz tuzata olmaysiz.
# config/urls.py
from django.urls import include, path
urlpatterns = [
# versiya — yo'lning birinchi bo'g'ini. v1 hech qachon buzilmaydi:
# buzuvchi o'zgarish kerak bo'lsa, v2 ochiladi, v1 yashashda davom etadi
path("api/v1/", include("apps.api_v1.urls")),
]
Qoidalar sodda:
- qo'shish mumkin: yangi maydon, yangi endpoint — eski mijozlar ularni ko'rmaydi, buzilmaydi;
- mumkin emas: maydonni o'chirish, nomini yoki tipini o'zgartirish, majburiy parametr qo'shish — bularning har biri yangi versiya degani;
- eski versiyani o'chirishdan oldin uning trafigini o'lchang (access log yoki metrika) — nol bo'lmaguncha o'chirmang.
Nega header emas, URL
Versiyani Accept header'da berish "akademik to'g'ri" hisoblanadi, lekin
URL versiyasi log'da ko'rinadi, curl bilan tekshiriladi, mobil jamoaga
tushuntirish oson. Amaliyotda soddalik yutadi — Metro'da ham /api/v1/.
2-qoida: bitta ekran — bitta so'rov#
Web'da sahifa 5 ta so'rov qilsa sezilmaydi. Mobilda har so'rov — alohida TLS-qo'l berish, radiouyg'onish, kechikish. Metro'ning bosh ekrani liniyalar va stansiyalarni ko'rsatadi — ularni ikki endpoint qilib bermaymiz, ekranga mos bitta javob qilamiz:
# apps/metro/serializers.py
from rest_framework import serializers
from .models import Line, Station
class StationNestedSerializer(serializers.ModelSerializer):
class Meta:
model = Station
fields = ["id", "name", "status"]
class LineWithStationsSerializer(serializers.ModelSerializer):
stations = StationNestedSerializer(many=True, read_only=True)
class Meta:
model = Line
fields = ["id", "name", "color", "stations"]
# apps/metro/views.py
from rest_framework.generics import ListAPIView
from .models import Line
from .serializers import LineWithStationsSerializer
class NetworkView(ListAPIView):
"""Bosh ekran uchun butun tarmoq: liniyalar + ichida stansiyalari."""
serializer_class = LineWithStationsSerializer
# prefetch bo'lmasa har liniya uchun alohida so'rov ketadi — N+1
queryset = Line.objects.prefetch_related("stations").order_by("id")
Ichma-ich serializer N+1 muammosining klassik manbai — prefetch_related
esdan chiqsa, 4 liniya 5 ta SQL so'rov bo'ladi. Bu tuzoqni alohida yoritganman:
Django ORM va N+1.
3-qoida: o'zgarmagan javobni qayta yubormang — ETag#
Metro tarmog'i haftalab o'zgarmaydi, lekin ilova har ochilganda so'raydi.
ConditionalGetMiddleware bilan Django javobga ETag qo'shadi va mijoz
If-None-Match yuborsa, o'zgarmagan javobga 304 (bo'sh tana) qaytaradi:
# config/settings.py
MIDDLEWARE = [
# ...
"django.middleware.http.ConditionalGetMiddleware", # ETag/304 avtomatik
"django.middleware.gzip.GZipMiddleware", # matn javoblarni siqadi
]
# birinchi so'rov: to'liq javob + ETag
curl -i https://api.misol.uz/api/v1/network/ | grep -i etag
# ETag: "6d82cbb050ddc7fa9cbb659014546e59"
# ikkinchi so'rov: mijoz ETag'ni qaytaradi — javob 304, tana bo'sh
curl -i -H 'If-None-Match: "6d82cbb050ddc7fa9cbb659014546e59"' \
https://api.misol.uz/api/v1/network/
# HTTP/1.1 304 Not Modified
Bu ikki qatorlik middleware metro kabi "o'qish ko'p, yozish kam" API'da trafikning katta qismini yo'q qiladi. Server tomonda hisoblash baribir ketadi — uni ham kesish kerak bo'lsa, keyingi bosqich kesh: Redis keshlash strategiyalari.
4-qoida: offline — xato holati emas, normal holat#
Metro ilovasining qiziq sharti: foydalanuvchi aynan yer ostida, aloqasiz paytda ilovaga eng ko'p qaraydi. Yechim — ma'lumotni qurilmada saqlash va tarmoq borida delta-sinxronizatsiya:
# apps/metro/views.py
from django.utils.dateparse import parse_datetime
from rest_framework.exceptions import ValidationError
from rest_framework.generics import ListAPIView
from .models import Station
from .serializers import StationSyncSerializer
class StationSyncView(ListAPIView):
"""?updated_after=2026-08-01T00:00:00Z — faqat o'shandan keyin o'zgarganlar.
Ilova birinchi ochilishda hammasini oladi va lokal bazaga yozadi,
keyin faqat farqni so'raydi — trafik ham, vaqt ham minimal.
"""
serializer_class = StationSyncSerializer
def get_queryset(self):
qs = Station.objects.order_by("updated_at")
raw = self.request.query_params.get("updated_after")
if not raw:
return qs
moment = parse_datetime(raw)
if moment is None:
raise ValidationError({"updated_after": "ISO 8601 formatida bo'lsin"})
return qs.filter(updated_at__gt=moment)
Buning ishlashi uchun modelda updated_at = models.DateTimeField(auto_now=True)
bo'lishi kerak — va o'chirilgan yozuvlar uchun qattiq o'chirish o'rniga
is_deleted bayrog'i (aks holda mijoz o'chirilganni bilmay qoladi).
Takroriy so'rovga chidamlilik (idempotentlik)
Mobil tarmoqda so'rov ketdi-yu, javob yo'qoldi — ilova qayta uradi. POST
endpoint bunga tayyor bo'lmasa, bitta amal ikki marta bajariladi.
Retseptlar: yaratishda update_or_create (3-qismdagi token ro'yxati shunday
ishlaydi), pul/kritik amallarda mijoz yuboradigan Idempotency-Key header
va uni server tomonda eslab qolish.
5-qoida: uch til — bitta javobda#
Metro kontenti uch tilda (uz/ru/en). Klassik yechim — Accept-Language bo'yicha
bitta tilni qaytarish. Metro'da esa boshqa qaror qabul qilingan: hamma til
bitta JSONField'da, javob ham shunday ketadi:
{"id": 4, "name": {"uz": "Amir Temur xiyoboni", "ru": "Сквер Амира Темура", "en": "Amir Temur Square"}}
Sabablari amaliy:
- foydalanuvchi tilni ilova ichida, offline holda almashtira oladi — server so'rovsiz;
- kesh va ETag samaradorligi: javob tilga bog'liq emas, bitta variant;
- uch qisqa satr payload'ni sezilarli oshirmaydi (kontent hajmi cheklangan ma'lumotnoma bo'lgani uchun trade-off oqlanadi — uzun matnli tizimda bu qaror boshqacha bo'lardi).
Xato javoblari ham API'ning qismi
Ilova xatoni foydalanuvchi tilida ko'rsatishi kerak. Matn yuborish o'rniga
kod yuboring — {"error": {"code": "station_closed"}} — tarjimasi
ilovada. Matn yuborsangiz, xabarni o'zgartirish uchun yana App Store
navbatiga turasiz.
Xulosa — mobil API dizaynining besh sinovi#
Har yangi endpoint'ni shu savollar bilan tekshiring:
- Bir yil oldingi ilova versiyasi bu javobni ko'rsa sinadimi? (versiyalash)
- Ekran nechta so'rov qilyapti? (payload dizayni, N+1)
- O'zgarmagan ma'lumot qayta yuborilyaptimi? (ETag/304, kesh)
- So'rov ikki marta kelsa nima bo'ladi? (idempotentlik)
- Javob aloqasiz metro vagonida ham ma'noli ishlaydimi? (delta-sync, offline)
Shu bilan Metro backend amaliyoti seriyasi yakunlandi: geo-so'rovlar, real-time, push, autentifikatsiya va mobil dizayn — birgalikda Play Market'da 100K+ yuklab olingan ilovaning to'liq backend skeleti. Loyihaning o'zi haqida: Tashkent Metro case-study.