Django Transaction Part 2: Mengelola Commit & Rollback

Django Transaction Part 2: Mengelola Commit & Rollback
Photo by Brecht Corbeel / Unsplash

Dalam tulisan sebelumnya saya memilih menggunakan context manager untuk memberi visibilitas bahwa operasional ORM dalam satu transaction.

def process_one():
  with transaction.atomic():
    user = User.objects.create(name='john')
    Profile.objects.create(user=user)

Tapi walaupun kode di atas cukup eksplisit, masih menyembunyikan hal penting: bagaimana prosedur commit dan rollback? Di sini saya tidak akan membahas secara detail bagaimana kode internal Django, tapi secara penyederhanaan:

Commit: Dilakukan ketika dalam lingkup context manager tidak terjadi error apa pun.

Rollback: Dilakukan ketika terjadi error/exception, maka akan memicu terjadinya rollback.

def process_one():
  with transaction.atomic():
    user = User.objects.create(name='john')
    Profile.objects.create(user=user)  # exception terjadi maka rollback

Mengetahui bagaimana commit dan rollback terjadi ini akan membantu dalam mengerjakan proses yang bersangkutan dengan nested transaction, contoh:

def process_one():
  with transaction.atomic():
    User.objects.create(name='john')

def process_two():
  with transaction.atomic():
    Profile.objects.create(user=1)


def run_process():
    process_one()
    process_two()

Jika kita akhirnya menghasilkan kode seperti di atas, walaupun ada context manager transaction.atomic(), ketika dipanggil di run_process() itu akan tetap dianggap sebagai dua transaction yang berbeda, menyebabkan masalah integritas data. Caranya untuk tetap terbungkus satu transaction kita harus membungkusnya kembali oleh context manager, menjadi:

def run_process():
  with transaction.atomic():
    process_one()
    process_two()

Mengelola Commit & Rollback

Dari argumen yang sudah saya sebutkan di atas, untuk kondisi mengelola commit dan rollback ini menjadi penting, terlebih jika dalam kode kita banyak menggunakan try/except, karena dengan kita meng-handle exception ada kemungkinan exception berhenti dalam inner process dan tidak terdeteksi oleh outer context.

Contoh: A
def process_one():
  with transaction.atomic():
    User.objects.create(name='john')

def process_two():
  with transaction.atomic():
    try:
      Profile.objects.create(user=user)
    except:
      print("An exception occurred") 


def run_process():
  with transaction.atomic():
    process_one()
    process_two()

Di contoh kode di atas, jika process_two() terjadi masalah dan exception kita tangani, ini bisa mengakibatkan data tetap tersimpan, karena outer context (context manager yang menaunginya di dalam fungsi run_process()) tidak menangkap sinyal exception, mengakibatkan fungsi rollback tidak terpanggil dan data akan di-commit. Lalu bagaimana jika ingin tetap gagal satu gagal semua? Gunakan flag set rollback.

Contoh B:
def process_one():
  with transaction.atomic():
    User.objects.create(name='john')

def process_two():
  with transaction.atomic():
    try:
      Profile.objects.create(user=user)
    except:
       transaction.set_rollback(True)


def run_process():
  with transaction.atomic():
    process_one()
    process_two()

Jadi kalau kita mau bandingkan alur dari contoh A dan B menjadi seperti ini:

Contoh A:

  • run_process() dijalankan → membuka transaction terluar
  • process_one() dijalankan → membuka blok atomic di dalam transaction terluar, dan mencatat savepoint(), belum commit
  • process_two() dijalankan → membuka blok atomic di dalam transaction terluar, exception di-handle di dalam sehingga tidak terdeteksi oleh transaction terluar
  • Kedua proses selesai, tidak terbaca ada exception, commit. User tercatat dan profile tidak.

Contoh B:

  • run_process() dijalankan → membuka transaction terluar
  • process_one() dijalankan → membuka blok atomic di dalam transaction terluar, dan mencatat savepoint(), belum commit
  • process_two() dijalankan → membuka blok atomic di dalam transaction terluar, exception di-handle dan memberi flag harus di-rollback
  • Kedua proses selesai, tidak terbaca ada exception, tapi flag rollback naik ke transaction terluar, proses di-rollback dan user dan profile tidak tersimpan.

Mengenai savepoint, saya coba bahas di tulisan selanjutnya.

Read more