No Monologue:
Nowadays using ORM is common in web development frameworks. A few very common ORMs in Python are SQLAlchemy and Django ORM (django comes with its own ORM).
Almost all ORMs have a common feature called hooks, or signals. Their names may differ, but they work similarly. 👇🏼
This is a great idea in theory. However, maintaining them in real life is incredibly challenging and comes with various costs (of different types).
Let’s see how they look,
SQLAlchemy example
from sqlalchemy import event
from sqlalchemy.orm import sessionmaker
# Create a session factory
Session = sessionmaker()
# Before commit: Triggered right before a transaction is committed
def my_before_commit(session):
print("Before commit!")
event.listen(Session, "before_commit", my_before_commit)
# After attaching an instance to a session
@event.listens_for(Session, 'after_attach')
def receive_after_attach(session, instance):
"""Triggered when an instance is attached to the session."""
# After a transaction begins
@event.listens_for(Session, 'after_begin')
def receive_after_begin(session, transaction, connection):
"""Triggered when a transaction is initiated."""
# After a bulk delete operation
@event.listens_for(Session, 'after_bulk_delete')
def receive_after_bulk_delete(delete_context):
"""Triggered after a bulk delete operation."""
Django ORM Example
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete
from django.core.signals import request_started, request_finished
from django.dispatch import receiver
from myapp.models import MyModel
# Signal: Before saving an instance
@receiver(pre_save, sender=MyModel)
def before_save(sender, instance, **kwargs):
print(f"Before saving: {instance}")
# Signal: After saving an instance
@receiver(post_save, sender=MyModel)
def after_save(sender, instance, created, **kwargs):
if created:
print(f"New instance created: {instance}")
else:
print(f"Instance updated: {instance}")
# Signal: When a request starts
@receiver(request_started)
def on_request_start(sender, **kwargs):
print("Request started!")
The Problem
In both cases, it’s very easy to add these hooks or signals. And that’s where the next problem arises: as projects grow, this kind of code is often added quickly and scattered across the codebase. Over time, these can be forgotten.
The effects are global.
New developers: Often don’t know where these magical changes are happening.
Senior developers: Can forget their existence.
Team churn: People leave, taking undocumented knowledge with them (and even written documentation gets overlooked or lost 😅).
Debugging: It takes a lot of time to debug these issues because they work as modules in the middle.
Refactoring: Becomes difficult when these hooks/signals are deeply embedded.
Here’s a warning directly from Django’s official documentation
Conclusion
Hate is a strong word. I don’t hate these tools. But they are extremely powerful, and as Spider-Man’s uncle said:
With great power comes great responsibility.
Once you set up hooks or signals, they’re very easy to use. There’s no doubt the functionality is fantastic. The main takeaway here is that these features should be maintained properly, generationally, and with raised awareness and knowledge about their usage.
It’s very easy to avoid the pitfalls mentioned above 👆🏼—with a bit of discipline and care.
totally agree