Django'da Transaction Atomic Yapısı
- Bu yazımızda django'daki transaction atomic yapısını Django Rest Framework üzerindeki bir örnek ile anlamaya çalışacağız.
- Django'nun varsayılan transaction davranışı otomatik işleme(autocommit) olarak çalışmaktır. Yani her bir sorgu(query) veritabanına anlık olarak işlenmektedir. Aşağıdaki örnek ile burayı anlamaya çalışalım.
xxxxxxxxxx
class Product(models.Model):
name = models.CharField(max_length=60)
- Şimdi
Product
model sınıfına ait verileri python manage.py shell diyerek ekleyelim.
xxxxxxxxxx
>>> p1 = Product(name="product 2021")
>>> p1.name
'product 2021'
>>> p1.id
# Henüz transaction gerçekleşmediği için
# herhangi bir cevap yok
>>> p1.save() # transaction işlemini başlatıyoruz.
>>> p1.id
28 # ve sonuç!
- Şimdi transaction atomic yapısını anlamaya çalışalım. Aşağıdaki örneği inceleyelim
xxxxxxxxxx
class ProductModelSerializer(serializers.ModelSerializer):
# ...
# .....
def create(self, validated_data):
productmaterial_set = validated_data.pop('productmaterial_set')
# Yeni product Product tablosuna ekleniyor
new_product = Product.objects.create(**validated_data)
# Yeni product ile beraber gönderilen material ve rate
# verileri ProductMaterial tablosuna ekleniyor.
for prod_material_data in productmaterial_set:
ProductMaterial.objects.create(
product=new_product,
material=prod_material_data.get('material'),
rate=prod_material_data.get('rate')
)
return new_product
- Yukarıdaki örnekte client tarafından gönderilen product ve product'a ait material ve rate verileri sırasıyla veritabanına ekleniyor(transaction gerçekleşiyor.)
- Peki create(...) methodunda herhangi bir hata oluşursa bu hataya rağmen yine de veriler veri tabanına eklenmeli midir? Cevap tabi ki HAYIR. Çünkü gönderilen verinin doğrulanması ve modellerdeki yapıya uygun olması gerekmektedir. Ancak django transaction işlemlerini sıra sıra işlemiş ve herhangi hata durumunda kod kırılsa(break) veya exception fırlatsa bile transaction işlemi tamamlanan veriler veri tabanına eklenmiş olacaktır. Herhangi bir durumda hata oluşursa transaction işlemlerinin geri alınması(roll back) için transaction atomic yapısını kullanırız.
- Örnek üzerinde görelim daha iyi anlayalım.
xxxxxxxxxx
from decimal import Decimal
class Product(models.Model):
name = models.CharField(max_length=60)
def is_material_rate_sum_valid(self):
rates = [pm.rate for pm in self.productmaterial_set.all()]
return sum(rates)==Decimal("100")
- Yukarıdaki Product model sınıfına eklenen
is_material_rate_sum_valid
methodu product'a ait material rate(oranlarının) toplamı 100 is True, değilse False cevabı dönecektir. Client'ı materyaller toplamının 100 olmasına zorlamak istiyoruz. Kuralımız bu şekilde... - Aşağıdaki
serializer
sınıfına eklenen if state'ine dikkat edelim
xxxxxxxxxx
class ProductModelSerializer(serializers.ModelSerializer):
#...
def create(self, validated_data):
productmaterial_set = validated_data.pop('productmaterial_set')
new_product = Product.objects.create(**validated_data)
for prod_material_data in productmaterial_set:
ProductMaterial.objects.create(
product=new_product,
material=prod_material_data.get('material'),
rate=prod_material_data.get('rate')
)
# Eğer product material rate toplamı 100 değilse
# client'a hata mesajını göster
if not new_product.is_material_rate_sum_valid():
raise serializers.ValidationError("total material rate must be 100")
return new_product
- Şimdi bu halde iken bir post request atalım
xxxxxxxxxx
{
"product_materials": [
{"material":3, "rate":"20"},
{"material":1, "rate":"60"}
],
"name": "test product"
}
- Cevap
xxxxxxxxxx
HTTP 400 Bad Request
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
"total material rate must be 100"
]
- Beklediğimiz hata mesajı alındı. ANCAK.... get request atınca bir de bakıyoruz ki...
xxxxxxxxxx
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
{
"id": 30,
"product_materials": [
{
"id": 13,
"product": 30,
"material": 3,
"rate": "20.00",
"material_name": "material3",
"product_name": "test product"
},
{
"id": 14,
"product": 30,
"material": 1,
"rate": "60.00",
"material_name": "material1",
"product_name": "test product"
}
],
"name": "test product"
}
]
- Hata'ya rağmen yeni veriler eklenmiş durumda!
- BU SORUNU transaction atomic kullanarak aşabiliriz
serializer
sınıfını değiştirelim
xxxxxxxxxx
from django.db import IntegrityError, transaction
class ProductModelSerializer(serializers.ModelSerializer):
# ...
def create(self, validated_data):
try:
with transaction.atomic():
productmaterial_set = validated_data.pop('productmaterial_set')
new_product = Product.objects.create(**validated_data)
for prod_material_data in productmaterial_set:
ProductMaterial.objects.create(
product=new_product,
material=prod_material_data.get('material'),
rate=prod_material_data.get('rate')
)
if not new_product.is_material_rate_sum_valid():
raise serializers.ValidationError("total material rate must be 100")
return new_product
except IntegrityError as ierr:
raise ierr
- post request atalım
xxxxxxxxxx
{
"product_materials": [
{"material":2, "rate":"20"},
{"material":3, "rate":"45"}
],
"name": "product 2023"
}
- CEVAP
xxxxxxxxxx
HTTP 400 Bad Request
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
"total material rate must be 100"
]
- GET request ve sonuç BAŞARILI! yeni gönderdiğimiz
"name": "product 2023"
verisi kaydedilmedi!
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
{
"id": 30,
"product_materials": [
{
"id": 13,
"product": 30,
"material": 3,
"rate": "20.00",
"material_name": "material3",
"product_name": "test product"
},
{
"id": 14,
"product": 30,
"material": 1,
"rate": "60.00",
"material_name": "material1",
"product_name": "test product"
}
],
"name": "test product"
}
]
Yorumlar