Django Transaction Part 2: Mengelola Commit & Rollback
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 terluarprocess_one()dijalankan → membuka blok atomic di dalam transaction terluar, dan mencatatsavepoint(), belum commitprocess_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 terluarprocess_one()dijalankan → membuka blok atomic di dalam transaction terluar, dan mencatatsavepoint(), belum commitprocess_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.