Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/why.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## Django's Atomic
Django's `atomic` ensures database changes are committed together-or-not-at-all. It creates a savepoint or a transaction depending on two factors:

- The arguments passed to it (`durable` and `savepoint`).
- If a database transaction is already open.

Specifically, the **Behaviours** which `atomic` exhibits are:

| | `durable=False` (default) | `durable=True` |
| --- | --- | --- |
| `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.) |
| `savepoint=False` | **C**. Begin a transaction if needed. Never creates a savepoint. | **D**. Same as **B**. |

Uses of `atomic` fall into three broad **Categories**:

1. Create a *transaction* to wrap multiple changes.
2. Create a *savepoint* so we can roll back to in order to continue with a transaction after failure.
3. Changes to be committed *atomically*, but not specific about where the transaction is created, as long as there is one.

## Problems
1. Django's atomic creates many savepoints that are never used. There are a couple of main causes:
1. We create savepoints with decorators. *Linting for this is possible, but each existing case requires investigation.*
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds a little internal, so perhaps should be:

Suggested change
1. We create savepoints with decorators. *Linting for this is possible, but each existing case requires investigation.*
1. Indicating "this should be atomic" is often done by decorating functions with `atomic`.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I think we can just simplify this:

Suggested change
1. We create savepoints with decorators. *Linting for this is possible, but each existing case requires investigation.*
1. Savepoints are created with decorators (`@atomic`).

That point probably requires a bit of explanation, but we can add that when we revise this.

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.
> … 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
more complicated, the majority of people will end up doing the wrong
thing.
— [**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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formatting has gone a bit wrong here, perhaps an artefact of copying from Notion?

Suggested change
— [**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)
— [**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)

>
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.
Comment on lines +20 to +30
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this has got a bit mixed up in formatting.

Suggested change
## Problems
1. Django's atomic creates many savepoints that are never used. There are a couple of main causes:
1. We create savepoints with decorators. *Linting for this is possible, but each existing case requires investigation.*
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.
> … 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
more complicated, the majority of people will end up doing the wrong
thing.
— [**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)
>
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.
## Problems
Django's atomic creates many savepoints that are never used. There are a couple of main causes:
1. We create savepoints with decorators. *Linting for this is possible, but each existing case requires investigation.*
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.
> … 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
more complicated, the majority of people will end up doing the wrong
thing.
— [**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)
>
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.


## What Subatomic implements
- `transaction()`. Begin a transaction, or throw an error if a transaction is already open. Like atomic(durable=True), but with added after-commit callback support in tests.
- `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).
- `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.
- `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.*
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ extra:
alias: true
default:
- latest
markdown_extensions:
- tables
plugins:
- mike