Tutorial Flask Python — Menggunakan Transactions pada SQLAlchemy
Gunakan Transaction pada database yang bersifat Relasi untuk mencegah proses yang gagal ditengah jalan!
Halo semua, Kiddy disini dan pada kali ini gue mau berbagi insight megnenai Transactions tapi menggunakan Flask Python. Kalo di tutorial sebelumnya gue udah cuap-cuap tentang Transactions namun masih di Level Database, namun sekarang gue koar-koar masalah Transactions pada Level Framework, yaitu Flask Python.
Nah buat yang masuk ke tutorial ini saya peringatkan ya kalo tutorial ini adalah tutorial level advanced! Jadi bagi kamu yang belom paham apa itu Flask atau bahkan ngoding Python baru dasar, jangan marah-marah ke saya kalo kamu bingung sama konten yang ada di artikel ini, tapi kalo kamu udah pernah pake Flask, dan tau konsep Transactions pada database relasi, saya rasa tutorial ini udah cocok buat kamu.
Oke, untuk tutorial ini kita akan menggunakan kasus todo list seperti biasa (hehehe), namun bedanya adalah kasus todo list ini menggunakan logs, jadi setiap aktivitas todo yang dibuat kita akan mencatat logs, kenapa sih harus gitu? Ya biar ada kasus yang bisa dikulik hehehe, ngga deng nanti saya akan bagi tahu (bukan tempe) alasannya di akhir artikel.
Nah untuk mempercepat proses penulisan tutorial ini, saya akan bagikan sedikit jalan pintas, yaitu menggunakan boilerplate buatan saya. Boilerplate tuh apaan sih? Ya bisa dibilang semacam set armor, senjata, helm, sepatu, dan kebutuhan perang dasar sebelum kamu mulai perang, dengan boilerplate, kamu udah siap ngoding projek baru atau buat bahan ngulik aja tanpa perlu config projek baru dari 0 (enol) banget.
Nah silahkan clone terlebih dahulu projek boilerplate saya dsini:
Kalo sudah diclone, jangan lupa untuk buat venv, install pip requirements, konfigurasi database pada .env, jalankan migration, dan projek sudah siap untuk dijalankan.
Nah karena kita menggunakan kasus todo list, disini kita juga memiliki user, namun mohon maaf saya ngga ngebuat seeder nih hehehe, maka saya sarankan kamu untuk menjalankan command dibawah ini:
flask cli user:create Kiddy kiddy@mail.com secret
Command ini terletak pada app -> commands, dimana fungsinya adalah membuat user baru dengan memasukan tiga parameter yang diminta, yaitu nama, email, dan password. Setelah terbuat jangan lupa dicatat ID user kalian.
Sekarang kita udah bisa lanjut ngoding, kita akan membuat file TodoController.py pada file controllers dan masukkan kode dibawah:
from uuid import uuid4
from flask import request
from flask_restful import Resource
from app import response, db
from app.models.todo import Todos
from app.models.todo_log import TodoLogs
class TodoController(Resource):
def get(self):
todo = Todos.query.all()
return response.ok(todo, 'ok!')
def post(self):
try:
# Mengambil data dari JSON
user_id = request.json["user_id"]
title = request.json["title"]
desc = request.json["description"]
# Inisialisasi id Todo
todo_id = str(uuid4())
# Membuat objek Todo
todo = Todos()
todo.id = todo_id
todo.title = title
todo.description = desc
todo.user_id = user_id
# Menambahkan Todo ke sesi SQLALchemy
db.session.add(todo)
# Mengambil kembali data todo dari sesi
todo = Todos.query.filter_by(id=todo_id).first()
# Membuat objek Todo Logs
todo_logs = TodoLogs()
todo_logs.id = str(uuid4())
todo_logs.todo_id = todo_id
todo_logs.user_id = user_id
todo_logs.message = f"{todo.user_detail.name} membuat todo list dengan judul {title}"
todo_logs.type = "CREATE"
# Menambahkan Todo Logs ke sesi SQLALchemy
db.session.add(todo_logs)
# Kembalian payload todo
payload = {
"id": str(todo.id),
"title": title,
"description": desc,
"created_at": str(todo.created_at),
"updated_at": str(todo.updated_at),
}
return response.ok(payload, 'ok!')
except Exception as e:
# Rollback keadaan ke state sebelumnya
db.session.rollback()
return response.badRequest('', f"{e}")
Oke sekarang kita mempunyai dua proses secara bersamaan, yaitu pertama kita membuat todo list kita, dan kedua kita menambahkan logs dari todo kita, jadi prosesnya adalah:
- Membuat todo
- Membuat logs dari todo
Pertanyaanya, kalau setelah membuat todo terdapat syntax error, atau logic error, sehingga menyebabkan penulisan log dari todo menjadi error, maka kita harus memaksa todo untuk me-rollback ke keadaan sebelumnya alias belum terbuat!
Mengapa? Karena sangat tidak masuk akal apabila kita membuat todo namun todo logs tidak terbuat, sebagai seorang backend developer yang baik kita harus memastikan semua proses harus berjalan baik dan benar, dan apabila terdapat kesalahan (error), maka semua proses harus dikembalikan pada keadaan paling awal, finish everything, or don’t do anything.
Sekarang ubah routes.py terlebih dahulu dan tambahkan routes untuk todo:
from flask_restful import Api
from app import app
from app.controllers import ExampleControllerApi
from app.controllers.TodoController import TodoController
api = Api(app, prefix="/api")
api.add_resource(ExampleControllerApi.ExampleControllerApi, '/')
api.add_resource(TodoController, '/todo')
Oke, sekarang bagaimana cara mengetes bahwa apabila terjadi exception, kita akan mengembalikan error, dan memastikan data tidak terbuat.
Pertama, saya akan kasitau kalian isi table-table milik saya.
Oke let’s go, disini saya akan pake usernya Hudya!
Sebelum saya buktikan, saya akan buat error dengan menaikkan exception terlebih dahulu pada bagian setelah menambahkan object todo kedalam session.
....
# Menambahkan Todo ke sesi SQLALchemy
db.session.add(todo)
raise Exception('Oops, something wrong!')
# Mengambil kembali data todo dari sesi
todo = Todos.query.filter_by(id=todo_id).first()
.....
Silahkan ditambahkan raise Exception terlebih dahulu, gunanya agar kita bisa membuat exception secara manual.
Sekarang, jalankan Flasknya dan hit endpointnya!
Oke, liat kan request codenya 400? Maka terjadi kesalahan, dan tebak apa yang terjadi pada table todo dan todo_logs saya? Yup tidak terbuat! Ngga percaya? Coba cek deh isi table todo dan todo_logs kamu, jangan heran kalo ngga terbuat, ini bukan sulap bukan sihir. ini fitur Transaction di database.
Sekarang kalo kita pindahin exceptionnya pada satu baris sebelum response apa yang terjadi kira-kira?
.
.
.
Tentu saja, tetap tidak akan terbuat baik object todo maupun logsnya, karena konsep transaction adalah dengan memastikan bahwa semua proses berjalan dengan benar tanpa ada gangguan hingga akhir. Kalo ngga percaya coba aja.
# Kembalian payload todo
payload = {
"id": str(todo.id),
"title": title,
"description": desc,
"created_at": str(todo.created_at),
"updated_at": str(todo.updated_at),
}
raise Exception('Oops, something wrong!')
return response.ok(payload, 'ok!')
Taruh Exception satu baris tepat sebelum response, dan hit kembali endpoitnnya, tentu saja pasti tidak akan ada data pada table todo, dan todo_logs.
Gampang banget kan pake transaction di Flask Python? Tentu saja kawan~
Eh tapi pada sadar ngga sih? Sebenernya ada satu baris yang kurang saat saya melakukan insert data, pasti ngga pada engeh.
Apa itu? Yup commit si session!
Bahkan contoh tutorial crud Flask di Blog saya menuliskan db.session.commit loh hehehe, tapi kenapa saya ngga nulis itu disini ya? Ada yang tau?
Jawabannya ada pada file Config.py, coba cek deh
....
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
....
Jadi konfigurasi ini akan ngebuat flask kamu melakukan db.session.commit() di akhir sesi setelah semua fungsi kamu berjalan semua, mengapa gitu? Karena sebenarnya, kita hanya menggunakan sesi dari Flask dan SQLAlchemy saat membuat objek tersebut, namun kenyataannya objek tersebut belum benar-benar ada di Database.
Apabila kamu membuat konfigurasi diatas menjadi False, maka pada akhir kode sebelum response, kamu wajib menuliskan kode db.session.commit(), tujuannya adalah untuk mengcommit session yang ada di flask, untuk dikirimkan ke database. Apabila kamu tidak menuliskan kode tersebut, maka object kamu hanya bertahan di level Session dan tidak akan masuk ke database. Ngga percaya? Coba aja deh atur konfigurasi ini False dan jangan tulis db.session.commit() di kodemu, lalu lakukan POST kembali, cek databasemu, pasti ngga ada isinya.
Kalo saya bener jangan lupa traktir saya makan Indomie di warkop ya xixixi (saya mah yang murah meriah aja).
KENAPA HARUS PAKE LOGS?
Oke, mungkin bagi kalian-kalian yang baru aja masuk di dunia backend akan bertanya, kenapasih harus pake logs, emang kenapa sih? Seberapa penting sih? Jawabannya, penting banget.
Logs adalah sebuah table yang cuma menggunakan dua operasi, yaitu INSERT dan READ, jadi kamu diharamkan untuk merubah apalagi menghapus isi logs.
Gunanya adalah untuk tracking ketika terjadi kesalahan, misal kamu membuat sebuah table transaksi, apabila user membuat transaksi, log ditulis, apabila user mengubah metode pembayaran, log ditulis, apabila user menyelesaikan pembayaran, log ditulis. Penulisan ini akan mempermudah kamu untuk melakukan tracking apabila terjadi kesalahan data, maupun hal-hal lainnya yang bersifat teknis, sehingga kamu bisa memastikan bahwa itu benar-benar terjadi lewat data yang ditulis. Selain itu log umumnya dapat menjadi bukti seperti kurir yang menuliskan log perjalanan kamu dari awal dipacking, hingga tiba ke tanganmu. Penulisan ini sangat berguna untuk benar-benar memeriksa apa yang salah dengan sebuah data, sehingga kalian sebagai backend developer bisa paham sumber kesalahannya terletak darimana.
Selain itu, log mempermudah kalian untuk membuat REPORTING! Mengapa begitu? Apabila kita bergantung kepada sebuah status transaksi (contoh: “DIPESAN”, “DIBAYAR” , “SELESAI”), kita akan kesulitan untuk menentukan query reporting seperti berapa transaksi yang dibayar dibulan januari? berapa transaksi yang memerlukan waktu > 10 jam untuk membayarnya? Berapa rata-rata waktu pengguna yang dibutuhkan untuk membayar transaksi? dan masih banyak query reporting lainnya yang akan membuat kalian kesulitan apabila kalian tidak memiliki table log. Table log akan baik menjadi acuan dikarenakan kamu tidak akan pernah mengubah isi log, maupun menghapus data log.
Oke, gimana guys? Udah pada paham kan konsep transaction dan cara penggunaannya di Python Flask? Selain itu, tentu saja kalian sudah paham alasan mengapa kita harus menggunakan log dalam membuat struktur table.
Tunggu tutorial selanjutnya ya, saya akan nulis tentang cara menggunakan Queue Worker (RabbitMQ) di Flask Python menggunakan Celery~