Best practices, tips and tricks around Python, Django, Django REST Framework, FastAPI and Flask. TDD, SOLID and DDD approaches for building solid backend APIs.
Anthony.D
Python Developer · Django · FastAPI · Flask · Freelance
Python Developer · Django · FastAPI · Flask · Freelance
Best practices, tips and tricks around Python, Django, Django REST Framework, FastAPI and Flask. TDD, SOLID and DDD approaches for building solid backend APIs.

TDD draws two extreme reactions. The first: “I already write tests, so I do TDD.” The second: “writing tests first just reverses the effort without buying much.” Both miss what TDD really is. It is neither a coverage question nor a simple ordering trick. It is a design discipline that forces you to make an intention explicit before writing the code that satisfies it. This article opens a series on TDD. Before comparing the schools (Chicago, London, ATDD double loop, strict TDD), the shared trunk has to be laid down: the Red-Green-Refactor cycle, the practice of baby steps, and the FIRST properties that define a test that deserves the name. The next articles will build on this foundation to make the trade-offs tangible with concrete examples. ...

The four previous articles in the series laid the building blocks for a distributed system to stay consistent: Saga to orchestrate workflows, Outbox to publish reliable events, Inbox to consume them without duplicates, Idempotency Keys to protect the API. One question is left: what do you do with events once they are published? The most common answer: you use them to build read views. That is exactly what the CQRS pattern (Command Query Responsibility Segregation) proposes: separate the write model from the read model when both diverge enough that forcing them into a single structure costs more than splitting them. ...

The previous two articles tackled idempotency on the event side: the Outbox pattern guarantees a message is published at least once, and the Inbox pattern guarantees it is consumed only once. One last place where the same problem shows up sits further upstream: the HTTP API itself. When a client fires a POST /api/payments and the connection drops before the response comes back, the client has no way to know whether the payment was created. If it retries, it risks paying twice. If it does not retry, it risks not paying at all. The Idempotency Key pattern, popularized by Stripe and adopted since by most payment APIs, solves that dilemma by putting retry control in the client’s hands. ...

The previous article on the Transactional Outbox set a clear guarantee: every event written to the database will eventually be published. That guarantee is intentionally at-least-once. A consumer may receive the same event two times, three times, or more if the network behaves badly. The Outbox pattern never promises uniqueness. The consequence follows immediately: if the consumer applies the effect of the message twice, it bills twice, sends two emails, decreases stock twice. The consistency guaranteed on the producer side collapses on the reader side. ...

When a service updates its database and wants to notify the rest of the system by emitting an event to Kafka, RabbitMQ or SQS, the naive code looks like this: write to the database, then publish. If publishing fails after the commit, the event is lost. If publishing succeeds but the commit fails, the event refers to a state that does not exist. Both cases define the dual-write problem. The Transactional Outbox pattern fixes that inconsistency with a simple idea: never publish directly. The event is written to an outbox table within the same SQL transaction as the business change. A separate process reads that table and publishes to the broker. As long as the SQL transaction is atomic, the database and the future event are consistent by construction. ...

A business operation that spans several services raises a question SQL has been answering for fifty years inside a single database: what happens when one step succeeds and the next one fails? As long as everything lives in the same database, BEGIN ... ROLLBACK is enough. The moment you call an external service, a third-party API or another database, that safety net disappears. The Saga pattern answers that question. Rather than attempting an impossible ACID transaction, it breaks the operation into local steps, each paired with a compensating transaction that knows how to undo its effect. If step 4 fails, the compensations for steps 1, 2 and 3 are replayed in reverse order. ...

itertools is a standard library module that exposes composable iteration building blocks. Its value is not in replacing a for loop with a cryptically named function, but in processing data streams without ever loading them fully into memory. Every function returns a lazy iterator: nothing is computed until you consume the result. That is what lets you chain transformations over millions of elements with a constant memory footprint. Here are the functions I actually use, grouped by purpose, along with the pitfalls that waste time. ...

A test that has to isolate a function from its dependencies often ends up stacking patch() calls. Three dependencies, three nested with blocks. Five dependencies, a pyramid that pushes the useful code ten levels deep. The test becomes unreadable even though its intent is simple: verify a single behavior at a precise boundary. contextlib.ExitStack solves exactly this problem. It is a context manager that aggregates any number of others and closes them all cleanly on exit. Here is how I use it to keep a test focused on its boundary, with a concrete example on authentication. ...

Consuming an external API looks harmless at first. You run a requests.get, get a dictionary back, and use it directly throughout the code. The problem starts when that same JSON structure ends up scattered across ten files, and the API renames a field or switches price from float to string. Fixing it becomes a treasure hunt. The anti-corruption layer (ACL) addresses this problem. Borrowed from Domain-Driven Design, it acts as a translator between an external system and your business logic. One contact point, one place to update when the API changes. ...

Django REST Framework permissions work, but they show their limits as soon as access rules get moderately complex. Multiple roles, objects belonging to a specific user, custom actions on a ViewSet: you end up with has_permission and has_object_permission classes mixing heterogeneous checks, hard to read and even harder to test. rest_access_policy (package djangorestframework-access-policy) takes a different approach: declare access rules as statements, similar to AWS IAM policies. The result is readable at a glance, testable independently of the ViewSet, and extensible without rewriting the entire class. ...