Django 5.2: Custom Middleware dan Signal Handling untuk Wawancara Teknis
Panduan lengkap Django 5.2 custom middleware dan signal handling untuk persiapan wawancara teknis. Mencakup request pipeline, async middleware, post_save, pre_save, custom signals, dan praktik terbaik produksi.

Custom middleware dan signal handling merupakan dua topik fundamental yang hampir selalu muncul dalam sesi wawancara teknis Django. Pemahaman tentang bagaimana sebuah request mengalir melalui middleware stack serta bagaimana signal memisahkan logika aplikasi secara elegan menjadi pembeda utama antara kandidat yang sekadar menghafal dokumentasi dengan kandidat yang benar-benar membangun aplikasi Django di lingkungan produksi.
Django 5.2, sebagai rilis Long-Term Support (LTS), membawa penyempurnaan signifikan pada dukungan asynchronous middleware dan stabilitas signal handling. Bagi para pengembang yang sedang mempersiapkan wawancara teknis di tahun 2026, penguasaan kedua konsep ini memberikan keunggulan kompetitif yang nyata. Artikel ini membahas secara mendalam mekanisme kerja middleware dan signal, disertai contoh kode yang siap diterapkan dalam proyek sesungguhnya.
Django middleware memproses request dari atas ke bawah sesuai urutan dalam daftar MIDDLEWARE, kemudian memproses response dari bawah ke atas. Signal bekerja dengan pola publish-subscribe -- sender menyebarkan event dan receiver merespons tanpa keterikatan langsung. Kedua konsep ini sering muncul dalam wawancara tingkat senior karena menguji pemahaman arsitektural, bukan sekadar pengetahuan sintaksis.
Mekanisme Kerja Django Middleware
Middleware pipeline berada di antara web server dan Django views. Setiap middleware component membungkus component berikutnya, membentuk struktur berlapis seperti bawang (onion-like structure). Ketika sebuah request masuk, Django memanggil setiap middleware secara berurutan sesuai konfigurasi settings.MIDDLEWARE. Setelah view mengembalikan response, middleware stack yang sama memproses response tersebut dalam urutan terbalik.
Django 5.2 menggunakan pola middleware modern yang diperkenalkan sejak Django 1.10. Setiap middleware berupa callable yang menerima get_response pada saat inisialisasi dan memproses siklus request/response melalui method __call__.
# middleware.py - Basic middleware structure
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration happens here at server start
def __call__(self, request):
# Code executed BEFORE the view (and later middleware)
response = self.get_response(request)
# Code executed AFTER the view (on the way back)
return responseMethod __init__ dijalankan hanya sekali pada saat server dimulai, sehingga merupakan tempat yang tepat untuk konfigurasi yang membutuhkan sumber daya besar seperti memuat konfigurasi atau membuat koneksi. Sementara itu, method __call__ dijalankan setiap kali ada request masuk. Pemanggilan self.get_response(request) menjadi titik pembatas antara kode yang berjalan sebelum view dan kode yang berjalan setelah view.
Dalam konteks wawancara, pewawancara sering meminta kandidat untuk menjelaskan kapan tepatnya kode sebelum dan sesudah get_response dieksekusi. Jawaban yang tepat adalah: kode sebelum get_response berjalan pada fase request (dari atas ke bawah melalui middleware stack), sedangkan kode setelah get_response berjalan pada fase response (dari bawah ke atas).
Membangun Request Logging Middleware
Request logging middleware merupakan contoh praktis yang mendemonstrasikan keseluruhan siklus hidup middleware. Implementasi berikut mencatat informasi waktu, metadata request, dan status code response -- sebuah contoh yang sering diminta oleh pewawancara untuk ditulis secara langsung.
# apps/core/middleware.py
import logging
import time
logger = logging.getLogger('django.request')
class RequestLoggingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
start_time = time.monotonic()
# Attach metadata before view processing
request.start_time = start_time
response = self.get_response(request)
duration = time.monotonic() - start_time
logger.info(
'method=%s path=%s status=%d duration=%.3fs ip=%s',
request.method,
request.get_full_path(),
response.status_code,
duration,
request.META.get('REMOTE_ADDR'),
)
return response
def process_exception(self, request, exception):
# Called only when the view raises an exception
duration = time.monotonic() - getattr(request, 'start_time', 0)
logger.error(
'method=%s path=%s exception=%s duration=%.3fs',
request.method,
request.get_full_path(),
str(exception),
duration,
)
return None # Let Django's default exception handling continuePenggunaan time.monotonic() alih-alih time.time() merupakan praktik terbaik karena monotonic clock tidak terpengaruh oleh perubahan jam sistem (misalnya sinkronisasi NTP). Hook process_exception adalah method khusus middleware yang dipanggil oleh Django hanya ketika view menghasilkan exception yang tidak tertangani. Mengembalikan None dari method ini memungkinkan exception mengalir ke exception handler bawaan Django, sedangkan mengembalikan HttpResponse akan memutus rantai exception handling secara langsung.
Perlu diperhatikan bahwa request.start_time merupakan atribut kustom yang ditambahkan pada objek request. Django mengizinkan penambahan atribut semacam ini, dan teknik ini sering digunakan untuk meneruskan data antar fase middleware.
Urutan Middleware dan Konfigurasi MIDDLEWARE
Urutan middleware dalam settings.py memiliki dampak langsung terhadap perilaku aplikasi. SecurityMiddleware harus ditempatkan paling awal untuk memastikan security headers diterapkan pada setiap response. SessionMiddleware wajib berada sebelum AuthenticationMiddleware karena proses autentikasi bergantung pada data session yang harus dimuat terlebih dahulu.
# settings.py - Middleware ordering matters
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
# Custom middleware placed after Django's core stack
'apps.core.middleware.RequestLoggingMiddleware',
]Salah satu pertanyaan wawancara yang sangat umum adalah mengapa CsrfViewMiddleware harus berada setelah SessionMiddleware. Jawabannya terletak pada mekanisme proteksi CSRF di Django yang dapat menggunakan session-based tokens, sehingga session harus sudah dimuat sebelum validasi CSRF dilakukan.
Custom middleware umumnya ditempatkan setelah middleware bawaan Django. Pendekatan ini memastikan bahwa fitur-fitur inti seperti session, autentikasi, dan proteksi CSRF sudah tersedia ketika custom middleware dieksekusi. Namun, terdapat pengecualian -- middleware untuk logging atau timing sebaiknya ditempatkan seawal mungkin agar dapat mengukur durasi total pemrosesan request secara akurat.
Async Middleware di Django 5.2
Django 5.2 mendukung asynchronous middleware secara penuh, sebuah kemampuan yang sangat penting untuk operasi I/O-bound seperti pemanggilan API eksternal atau cache lookups. Async middleware mendeklarasikan dirinya melalui flag async_capable dan menggunakan method __call__ yang bersifat async.
# apps/core/middleware.py
import asyncio
import time
class AsyncTimingMiddleware:
# Mark this middleware as async-capable
async_capable = True
sync_capable = False
def __init__(self, get_response):
self.get_response = get_response
async def __call__(self, request):
start = time.monotonic()
# get_response is awaitable in async context
response = await self.get_response(request)
duration = time.monotonic() - start
response['X-Request-Duration'] = f'{duration:.4f}s'
return responsePengaturan async_capable = True dan sync_capable = False memberitahu Django bahwa middleware ini hanya beroperasi dalam mode async. Apabila aplikasi memiliki campuran sync dan async views, kedua flag dapat diatur ke True sehingga Django secara otomatis memanggil method yang sesuai berdasarkan tipe view yang sedang diproses.
Dalam konteks wawancara, pemahaman tentang implikasi performa async middleware sangat dihargai. Ketika middleware berjalan dalam mode async pada ASGI server seperti Uvicorn atau Daphne, operasi I/O tidak memblokir event loop, sehingga server dapat menangani request lain secara bersamaan. Hal ini berbeda secara fundamental dengan middleware sinkron yang memblokir thread hingga operasi selesai.
Siap menguasai wawancara Django Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Django Signals: Pola Publish-Subscribe
Django signals merupakan mekanisme yang memungkinkan komponen-komponen yang terpisah untuk saling berkomunikasi. Ketika sebuah model disimpan, dihapus, atau ketika sebuah request dimulai maupun selesai, Django menyebarkan signal kepada receiver yang telah terdaftar. Pemisahan (decoupling) ini merupakan keunggulan utama -- sender tidak perlu mengetahui receiver mana saja yang ada dalam sistem.
Signal yang paling sering dibahas dalam wawancara adalah pre_save, post_save, pre_delete, dan post_delete dari modul django.db.models.signals. Memahami kapan masing-masing signal dipicu dan apa saja parameter yang dikirimkan merupakan pengetahuan dasar yang wajib dikuasai.
Pola publish-subscribe yang diterapkan oleh signal Django mengikuti prinsip Open-Closed Principle. Fungsionalitas baru dapat ditambahkan dengan mendaftarkan receiver baru tanpa mengubah kode sender yang sudah ada. Pendekatan ini sangat berharga dalam proyek berskala besar di mana modifikasi pada model inti dapat berdampak luas.
Menggunakan post_save untuk Pembuatan Profil Otomatis
Pola klasik post_save adalah pembuatan objek terkait ketika sebuah model instance disimpan untuk pertama kalinya. Pendekatan ini menjaga model User tetap bersih dan mendelegasikan pembuatan profil kepada signal layer.
# apps/accounts/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from apps.accounts.models import UserProfile
User = get_user_model()
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
# 'created' is True only on INSERT, False on UPDATE
if created:
UserProfile.objects.create(
user=instance,
display_name=instance.get_full_name() or instance.username,
)Parameter boolean created membedakan antara record baru (INSERT) dan pembaruan (UPDATE). Pengecekan flag ini setiap kali signal dipanggil mencegah pembuatan profil ganda pada setiap operasi save. Parameter **kwargs bersifat wajib untuk setiap signal receiver karena Django dapat mengirimkan keyword arguments tambahan pada versi-versi mendatang.
Dalam wawancara, kandidat sering ditanya tentang perbedaan antara menempatkan logika pembuatan profil di post_save signal versus di method save() model. Jawaban yang tepat adalah: signal memberikan decoupling yang lebih baik karena model User tidak perlu mengetahui keberadaan model UserProfile. Selain itu, signal memungkinkan beberapa receiver yang berbeda untuk merespons event yang sama secara independen.
Menggunakan pre_save untuk Validasi dan Transformasi Data
Signal pre_save dijalankan sebelum Django menulis data ke database, sehingga sangat sesuai untuk transformasi data, pembuatan slug, atau validasi lintas-field yang melampaui kemampuan model-level clean methods.
# apps/blog/signals.py
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.utils.text import slugify
from apps.blog.models import Article
@receiver(pre_save, sender=Article)
def auto_generate_slug(sender, instance, **kwargs):
if not instance.slug:
base_slug = slugify(instance.title)
slug = base_slug
counter = 1
# Ensure slug uniqueness
while Article.objects.filter(slug=slug).exclude(pk=instance.pk).exists():
slug = f'{base_slug}-{counter}'
counter += 1
instance.slug = slugImplementasi di atas mendemonstrasikan beberapa konsep penting sekaligus. Pertama, slug hanya dihasilkan ketika field slug masih kosong, sehingga slug yang sudah ditetapkan secara manual tidak akan ditimpa. Kedua, loop while memastikan keunikan slug dengan menambahkan counter apabila slug dasar sudah digunakan. Ketiga, penggunaan exclude(pk=instance.pk) mencegah konflik dengan instance itu sendiri pada saat pembaruan.
Pewawancara terkadang menanyakan mengapa pendekatan ini lebih baik daripada menggunakan override save(). Alasan utamanya adalah signal memisahkan logika pembuatan slug dari model itu sendiri, memungkinkan pengujian dan pemeliharaan yang lebih mudah. Selain itu, pre_save signal selalu dipanggil sebelum data disimpan, termasuk ketika save() dipanggil dari admin interface atau shell interaktif.
Mendaftarkan Signal pada AppConfig.ready()
Pendekatan yang direkomendasikan Django untuk mendaftarkan signal adalah melalui method AppConfig.ready(). Pendekatan ini menjamin bahwa signal terhubung hanya sekali dan dilakukan setelah semua model telah dimuat sepenuhnya.
# apps/accounts/apps.py
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.accounts'
def ready(self):
# Import signals module to register receivers
import apps.accounts.signals # noqa: F401Import modul signals di dalam method ready() merupakan pola yang sangat penting. Melakukan import di tingkat modul (di luar ready()) dapat menyebabkan error AppRegistryNotReady karena model mungkin belum dimuat pada saat itu. Komentar # noqa: F401 memberitahu linter bahwa import tersebut memang disengaja meskipun modul yang diimpor tidak digunakan secara eksplisit dalam file tersebut.
Dalam proyek besar, konsistensi dalam pendaftaran signal sangat penting. Setiap aplikasi Django yang memiliki signal handler harus mengikuti pola yang sama -- mendefinisikan signal receivers dalam file signals.py dan mengimpornya melalui AppConfig.ready(). Pendekatan ini membuat lokasi signal handler mudah diprediksi bagi seluruh anggota tim pengembangan.
Custom Signals untuk Domain Events
Selain built-in model signals, custom signals dapat menangani event spesifik domain seperti konfirmasi pembayaran, perubahan status langganan, atau pengiriman notifikasi. Custom signals memungkinkan pemisahan yang bersih antara business logic inti dan side effects yang menyertainya.
# apps/orders/signals.py
from django.dispatch import Signal
# Define custom signals with documentation
order_completed = Signal() # Sent after payment confirmation
order_refunded = Signal() # Sent after refund processing
# apps/orders/services.py
from apps.orders.signals import order_completed
def complete_order(order):
order.status = 'completed'
order.save()
# Dispatch signal with relevant context
order_completed.send(
sender=order.__class__,
order=order,
total=order.total_amount,
)
# apps/notifications/receivers.py
from django.dispatch import receiver
from apps.orders.signals import order_completed
@receiver(order_completed)
def send_order_confirmation_email(sender, order, **kwargs):
# Email logic decoupled from order processing
from apps.notifications.services import send_email
send_email(
to=order.customer.email,
template='order_confirmation',
context={'order': order},
)Arsitektur di atas menunjukkan pemisahan tanggung jawab yang jelas. Modul orders bertanggung jawab atas logika bisnis pemesanan dan mengirimkan signal ketika pesanan selesai diproses. Modul notifications, yang sepenuhnya terpisah, mendengarkan signal tersebut dan menangani pengiriman email. Kedua modul tidak saling bergantung secara langsung, sehingga penambahan side effect baru (misalnya pembaruan inventaris atau pencatatan analitik) cukup dilakukan dengan mendaftarkan receiver baru tanpa mengubah kode di modul orders.
Penggunaan sender=order.__class__ pada method send() memungkinkan receiver untuk memfilter signal berdasarkan tipe sender apabila diperlukan. Sementara itu, keyword arguments tambahan seperti order dan total menyediakan konteks yang diperlukan oleh receiver untuk menjalankan tugasnya.
Kesalahan Umum dalam Wawancara tentang Middleware dan Signals
Method QuerySet.update() dan bulk_create() di Django melewati method save() pada model sepenuhnya, yang berarti signal pre_save dan post_save tidak akan dipanggil. Hal yang sama berlaku untuk delete() pada QuerySet dibandingkan dengan memanggil delete() pada instance model tunggal. Ini merupakan salah satu pertanyaan tentang Django signal yang paling sering diajukan dalam wawancara teknis.
Berikut adalah kesalahan-kesalahan umum lainnya yang perlu diwaspadai:
- Circular import pada signals: Melakukan import model di tingkat modul dalam file signal dapat menyebabkan error
AppRegistryNotReady. Selalu lakukan import di dalam fungsi receiver atau gunakanAppConfig.ready(). - Tidak menyertakan
**kwargs: Menghilangkan**kwargsdari signature receiver akan menyebabkan error ketika Django menambahkan parameter baru pada signal di versi mendatang. - Logika berat dalam signals: Signal dijalankan secara sinkron dalam transaksi database yang sama secara default. Untuk operasi yang memakan waktu lama, sebaiknya pekerjaan tersebut didelegasikan ke task queue seperti Celery.
- Middleware yang memodifikasi response content: Mengubah
response.contentsetelah view menetapkannya dapat merusak streaming responses dan membuat headerContent-Lengthmenjadi tidak akurat. - Mendaftarkan signal lebih dari sekali: Tanpa penggunaan
AppConfig.ready()yang benar, receiver dapat terdaftar beberapa kali dan dijalankan berkali-kali untuk satu event yang sama.
Perbandingan Middleware dan Signals: Kapan Menggunakan yang Mana
Memahami perbedaan dan kesesuaian penggunaan middleware versus signals merupakan pertanyaan arsitektural yang sering muncul dalam wawancara. Tabel berikut menyajikan panduan keputusan yang komprehensif:
| Aspek | Middleware | Signals | |---|---|---| | Modifikasi request/response | Mendukung | Tidak mendukung | | Autentikasi dan otorisasi | Mendukung | Tidak mendukung | | Model lifecycle hooks | Tidak mendukung | Mendukung | | Cross-cutting concerns (logging, timing) | Mendukung | Dapat dilakukan tetapi kurang tepat | | Business logic yang terpisah (decoupled) | Tidak mendukung | Mendukung | | Dukungan async di Django 5.2 | Mendukung penuh | Mendukung penuh (asend/asend_robust) | | Cakupan eksekusi | Setiap HTTP request | Event spesifik (save, delete, custom) |
Secara ringkas, middleware digunakan ketika logika harus diterapkan pada setiap HTTP request yang masuk atau keluar, seperti logging, penambahan security headers, atau rate limiting. Signals digunakan ketika logika perlu dijalankan sebagai respons terhadap event spesifik dalam siklus hidup model atau domain event kustom, seperti pembuatan profil otomatis, pengiriman notifikasi, atau pembaruan cache.
Kesimpulan
Berikut adalah poin-poin utama yang perlu dikuasai untuk persiapan wawancara teknis Django:
- Django middleware membentuk pipeline berlapis yang memproses request dari atas ke bawah dan response dari bawah ke atas. Pemanggilan
get_responsedalam__call__merupakan titik pembatas antara fase sebelum dan sesudah view. - Custom middleware sangat sesuai untuk cross-cutting concerns seperti logging, timing, security headers, dan rate limiting yang harus berlaku untuk setiap HTTP request.
- Django 5.2 mendukung async middleware secara penuh, memungkinkan penanganan operasi I/O-bound tanpa memblokir request pipeline pada aplikasi yang berjalan di ASGI server.
- Signal
post_savedanpre_savemenangani model lifecycle events --pre_saveuntuk transformasi data sebelum penulisan ke database,post_saveuntuk pembuatan objek terkait dan pengiriman notifikasi. QuerySet.update()danbulk_create()melewati seluruh signal, menjadikan hal ini sebagai jebakan paling umum dalam pertanyaan wawancara tentang Django signals.- Signal harus didaftarkan melalui
AppConfig.ready()untuk menjamin koneksi yang dilakukan hanya sekali setelah semua model selesai dimuat. - Custom signals memungkinkan pemisahan domain events seperti konfirmasi pembayaran atau perubahan langganan dari business logic inti, menghasilkan arsitektur yang mudah diperluas sesuai prinsip Open-Closed Principle.
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Tag
Bagikan
Artikel terkait

Pertanyaan Wawancara Django: ORM, Middleware, dan DRF Secara Mendalam
Pertanyaan wawancara Django mencakup optimasi ORM dengan select_related dan prefetch_related, arsitektur middleware, serta performa serializer Django REST Framework, permissions, dan pola pagination.

Pertanyaan Wawancara Django dan Python: 25 Teratas di 2026
25 pertanyaan wawancara Django dan Python yang paling sering ditanyakan. ORM, views, middleware, DRF, signals, dan optimasi dengan jawaban lengkap beserta contoh kode.

Django ORM: optimasi query untuk performa maksimal
Panduan lengkap untuk mengoptimalkan query Django ORM. select_related, prefetch_related, indeks, analisis masalah N+1, dan teknik lanjutan untuk aplikasi berperforma tinggi.