Skip to content

Commit e4d64c0

Browse files
committed
add explanation for the problem with atomic
cleanup temp cleanup for merge in rebase
1 parent 089d133 commit e4d64c0

File tree

1 file changed

+36
-0
lines changed

1 file changed

+36
-0
lines changed

docs/why.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
## Django's Atomic
2+
Django's `atomic` ensures database changes are committed together-or-not-at-all. It creates a savepoint or a transaction depending on two factors:
3+
4+
- The arguments passed to it (`durable` and `savepoint`).
5+
- If a database transaction is already open.
6+
7+
Specifically, the **Behaviours** which `atomic` exhibits are:
8+
9+
| | `durable=False` (default) | `durable=True` |
10+
| --- | --- | --- |
11+
| `savepoint=True` (default) | **A**. Begin a transaction if needed. Creates a savepoint if already in a transaction. | **B**. Begin a transaction, or throw an error if one is already open. Never creates a savepoint. (The `savepoint` flag is ignored.) |
12+
| `savepoint=False` | **C**. Begin a transaction if needed. Never creates a savepoint. | **D**. Same as **B**. |
13+
14+
Uses of `atomic` fall into three broad **Categories**:
15+
16+
1. Create a *transaction* to wrap multiple changes (such as in durable use-cases).
17+
2. Create a *savepoint* so we can roll back to in order to continue with a transaction after failure.
18+
3. Changes to be committed *atomically*, but not specific about where the transaction is created, as long as there is one (Such as domain operations.)
19+
20+
## Problems
21+
1. Django's atomic creates many savepoints that causes significant database-pressure issues.
22+
2. The majority of the savepoints are never used. There are a couple of main causes:
23+
1. We create savepoints with decorators. *Linting for this is possible, but each existing case requires investigation.*
24+
2. `atomic` creates savepoints by default. The default arguments (*Behaviour* **A**) are an [attractive nuisance](https://blog.ganssle.io/articles/2023/01/attractive-nuisances.html) because they make us create savepoints when we don't need them.
25+
> … if you have two ways to accomplish a task and one is a simple way that *looks* like the right thing but is subtly wrong, and the other is correct but
26+
more complicated, the majority of people will end up doing the wrong
27+
thing.
28+
[**Attractive nuisances in software design](https://blog.ganssle.io/articles/2023/01/attractive-nuisances.html) -** [Paul Ganssle](https://blog.ganssle.io/author/paul-ganssle.html)
29+
>
30+
3. We have no easy way to indicate the creation of a savepoint that doesn't have the potential to create a transaction instead. The only tool we have to create a savepoint is *Behaviour* **A**, which can create a transaction.
31+
32+
## What Subatomic implements
33+
- `transaction()`. Begin a transaction, or throw an error if a transaction is already open. Just like *Behaviour* **B**.
34+
- `savepoint()`. Create a savepoint, or throw an error if we're not already in a transaction. This is not in the table of *Behaviours* (the closest we have is *Behaviour* **A**, but that can create transactions).
35+
- `transaction_if_not_already()`. Begin a transaction if we're not already in one. Just like *Behaviour* **C**. This has a bit of a clunky name. This is deliberate, and reflects that it's a bit of a clunky thing to do. To be used with caution because the creation of a transaction is implicit. For a stricter alternative, see `transaction_required()` below.
36+
- `transaction_required()`. Throw an error if we're not already in a transaction. Does not create savepoints *or* transactions. *Most likely to be useful in the domain layer, which should not be responsible for the creation of transactions.*

0 commit comments

Comments
 (0)