Skip to content

Django Transaction Atomic Nedir

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.

# models.py

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.

>>> 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

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.
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
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

{
    "product_materials": [
              {"material":3, "rate":"20"},
              {"material":1, "rate":"60"}
],
    "name": "test product"
}
- Cevap
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...

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

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

{
    "product_materials": [
    {"material":2, "rate":"20"},
    {"material":3, "rate":"45"}
],
    "name": "product 2023"
}
- CEVAP
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"
    }
]