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.
xxxxxxxxxxclass Product(models.Model): name = models.CharField(max_length=60)- Şimdi
Productmodel 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.id28 # ve sonuç!- Şimdi transaction atomic yapısını anlamaya çalışalım. Aşağıdaki örneği inceleyelim
xxxxxxxxxxclass 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.
xxxxxxxxxxfrom decimal import Decimalclass 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_validmethodu 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
serializersınıfına eklenen if state'ine dikkat edelim
xxxxxxxxxxclass 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
xxxxxxxxxxHTTP 400 Bad RequestAllow: GET, POST, HEAD, OPTIONSContent-Type: application/jsonVary: Accept[ "total material rate must be 100"]- Beklediğimiz hata mesajı alındı. ANCAK.... get request atınca bir de bakıyoruz ki...
xxxxxxxxxxHTTP 200 OKAllow: GET, POST, HEAD, OPTIONSContent-Type: application/jsonVary: 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
serializersınıfını değiştirelim
xxxxxxxxxxfrom django.db import IntegrityError, transactionclass 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
xxxxxxxxxxHTTP 400 Bad RequestAllow: GET, POST, HEAD, OPTIONSContent-Type: application/jsonVary: Accept[ "total material rate must be 100"]- GET request ve sonuç BAŞARILI! yeni gönderdiğimiz
"name": "product 2023"verisi kaydedilmedi!
HTTP 200 OKAllow: GET, POST, HEAD, OPTIONSContent-Type: application/jsonVary: 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