Why I Keep Reaching for SQLAlchemy

I used to think SQLAlchemy was mostly a way to avoid writing SQL.

That wasn’t a compelling pitch. I already knew SQL. Why add another abstraction and debug whatever the ORM decided to do behind the scenes?

Then I built an endpoint that needed to:

  • Update a document’s status
  • Create signing sessions
  • Write email messages to an outbox
  • Record an audit event

If any step failed, none of it could stick.

That’s when SQLAlchemy started making sense.

The Pattern That Clicked

The useful abstraction wasn’t “tables become Python classes.” It was the unit of work.


  def send_document(
      session: Session,
      document: Document,
  ) -> None:
      ensure_ready_to_send(document)

      document.status = DocumentStatus.SENT

      for recipient in first_recipient_group(document):
          session.add(
              SigningSession(
                  document=document,
                  recipient=recipient,
              )
          )

      session.add(
          AuditEvent(
              document=document,
              event_type="sent",
          )
      )
  

These changes belong to the same database session and transaction. If creating a signing session fails, the status change and audit event can roll back with it.

Raw SQL can do this too. SQLAlchemy didn’t invent transactions. It gave us a consistent way to coordinate one transaction across multiple services and repositories.

You Still Need SQL

SQLAlchemy doesn’t remove the need to understand joins, indexes, query plans, or N+1 problems. A convenient ORM expression can still generate inefficient SQL.

That’s part of why I like it. SQLAlchemy provides useful structure without pretending the database is an implementation detail. When a query becomes unusual, you can
still use subqueries, database functions, or raw SQL.

What It Costs

SQLAlchemy has sharp edges. Lazy relationships can quietly produce dozens of queries, and automatic flushing can happen earlier than expected. Integration tests matter
because mocked sessions hide those behaviors.

I also wouldn’t use the ORM for everything. Reporting endpoints and performance-sensitive queries may be clearer as explicit SQL.

The Takeaway

SQLAlchemy isn’t valuable because it saves you from SQL. It’s valuable because it gives database work a consistent structure: models for persistence, composable queries
for reads, and a unit of work for coordinating writes.

When one request touches five tables and must succeed or fail as a whole, that structure is genuinely useful.

Conversation

Join the conversation

Your email address will not be published. Required fields are marked *