Skip to content

Commit 978b21e

Browse files
authored
Merge pull request #73 from kraken-tech/meshy/docs/links
Link to docs where we discuss APIs
2 parents 7ee172d + 03a7b5b commit 978b21e

File tree

2 files changed

+36
-31
lines changed

2 files changed

+36
-31
lines changed

docs/transactions-savepoints-and-atomic.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ and is committed with `COMMIT`.
1616
A transaction can be ended without committing using `ROLLBACK`.
1717

1818
When using Django Subatomic,
19-
transactions are created with `django_subatomic.db.transaction`,
19+
transactions are created with [`transaction`][django_subatomic.db.transaction],
2020
which can be used as both a decorator and a context manager.
2121

2222
!!! Note
2323

2424
SQL does not support nested transactions,
2525
so nesting is not supported by `transaction`.
26-
It acts like [Django's `atomic` with `durable=True`](https://docs.djangoproject.com/en/5.0/topics/db/transactions/#controlling-transactions-explicitly).
26+
It acts like [Django's `atomic` with `durable=True`][django-atomic]
2727

2828
See [_Atomic code_](#atomic-code)
2929
for code which requires a transaction, but doesn't require partial rollback.
@@ -51,7 +51,7 @@ In SQL terms, a savepoint is created with `SAVEPOINT <name>`.
5151
It is rolled back with `ROLLBACK TO <name>`
5252
and discarded with `RELEASE SAVEPOINT <name>`.
5353

54-
Subatomic creates savepoints using `django_subatomic.db.savepoint`.
54+
Subatomic creates savepoints using [`savepoint`][django_subatomic.db.savepoint].
5555
This is a context manager,
5656
and cannot be used as a decorator.
5757

@@ -65,23 +65,23 @@ and cannot be used as a decorator.
6565
Sometimes code needs to make multiple database changes atomically
6666
in a place that should not be responsible for managing a transactions.
6767

68-
Decorate this code with `@django_subatomic.db.transaction_required`
68+
Decorate this code with [`@transaction_required`][django_subatomic.db.transaction_required]
6969
to make it raise an exception when someone tries to run it without first opening a transaction.
7070

7171
!!! Tip
7272

73-
Where possible, use `transaction_required` as a decorator.
73+
Where possible, use [`transaction_required`][django_subatomic.db.transaction_required] as a decorator.
7474

7575
This form is preferred because it fails earlier,
7676
and presents a clearer requirement to programmers.
7777

78-
You can still use `transaction_required` as a context manager though.
78+
You can still use [`transaction_required`][django_subatomic.db.transaction_required] as a context manager though.
7979
This might be useful in code where you cannot know the required database
8080
(such as when the database name is passed in as a function parameter).
8181

8282
!!! Warning
8383

84-
When testing code which uses `transaction_required`
84+
When testing code which uses [`transaction_required`][django_subatomic.db.transaction_required],
8585
you might see `_MissingRequiredTransaction`
8686
even though tests are run in a transaction by default.
8787

@@ -93,15 +93,15 @@ to make it raise an exception when someone tries to run it without first opening
9393
then you have probably forgotten to open a transaction.
9494

9595
The trade-off is that lower-level tests will see this error too.
96-
If you're testing `transaction_required` code directly,
96+
If you're testing [`transaction_required`][django_subatomic.db.transaction_required] code directly,
9797
and you're _sure_ that the code shouldn't be responsible for opening a transaction,
98-
use the `django_subatomic.test.part_of_a_transaction()` decorator/context-manager
98+
use the [`part_of_a_transaction`][django_subatomic.test.part_of_a_transaction] decorator/context-manager
9999
to get things working.
100100
This will not run after-commit hooks.
101101
If you'd like those to run, create a [transaction](#transactions) instead.
102102

103103
When "create-a-transaction-if-one-doesn't-already-exist" behaviour is required,
104-
the `transaction_if_not_already` function will provide it.
104+
the [`transaction_if_not_already`][django_subatomic.db.transaction_if_not_already] function will provide it.
105105
This approach hints that transactional behaviour is not well-defined:
106106
the code will do different things in different contexts,
107107
which makes it hard to know what to expect from it.

docs/why.md

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
# Django's Atomic
22

3-
This doc will discuss the behaviours available through Django's `atomic`
3+
This doc will discuss the behaviours available through [Django's `atomic`][atomic]
44
and the outcomes people are usually trying to achieve with it.
5-
It goes on to outline some pitfalls that can result from using `atomic`
5+
It goes on to outline some pitfalls that can result from using [`atomic`][atomic]
66
and how Subatomic avoids them.
77

8-
Django's `atomic` ensures database changes are committed together-or-not-at-all.
8+
Django's [`atomic`][atomic] ensures database changes are committed together-or-not-at-all.
99
It creates a savepoint or a transaction depending on two factors:
1010

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

1414
## Behaviours
1515

16-
The *Behaviours* which `atomic` exhibits are:
16+
The *Behaviours* which [`atomic`][atomic] exhibits are:
1717

1818
| `savepoint=` | `durable=False` (default) | `durable=True` |
1919
| --- | --- | --- |
@@ -22,7 +22,7 @@ The *Behaviours* which `atomic` exhibits are:
2222

2323
## Outcomes
2424

25-
When people use `atomic`,
25+
When people use [`atomic`][atomic],
2626
they're generally trying to achieve one of three *Outcomes*:
2727

2828
1. to create a *transaction*
@@ -39,15 +39,16 @@ they're generally trying to achieve one of three *Outcomes*:
3939

4040
Ideally, we should be able to look at a line of code and say what it will do.
4141

42-
Because `atomic`'s behaviour depends on whether a transaction is already open,
42+
Because [`atomic`][atomic]'s behaviour depends on whether a transaction is already open,
4343
one must know the full call stack
44-
to know what any particular `atomic` will do.
44+
to know what any particular [`atomic`][atomic] will do.
4545
If it is called in multiple code paths,
4646
developers must know that it will do different database operations
4747
depending on who calls it.
4848

4949
Subatomic avoids this issue
50-
by offering an unambiguous API (`transaction()`, `savepoint()`, etc).
50+
by offering an unambiguous API
51+
([`transaction()`][django_subatomic.db.transaction], [`savepoint()`][django_subatomic.db.savepoint], etc).
5152

5253
### Transactions without context
5354

@@ -57,7 +58,7 @@ but cannot know if it is part of a larger suite of changes
5758
managed by higher-level code
5859
which must also be committed together.
5960

60-
When low-level code uses `atomic`
61+
When low-level code uses [`atomic`][atomic]
6162
to indicate that its changes should be atomic (*Outcome* **3**),
6263
this can have one of two effects:
6364

@@ -75,22 +76,22 @@ the creation of a savepoint (*Outcome* **2**)
7576
or the need for atomicity (*Outcome* **3**)
7677
that doesn't have the potential to create a transaction instead.
7778

78-
A function decorated with Subatomic's `@transaction_required`
79+
A function decorated with Subatomic's [`@transaction_required`][django_subatomic.db.transaction_required]
7980
will raise an error when called outside of a transaction,
8081
rather than run the risk of creating a transaction with the wrong scope.
8182

8283
### Savepoints by default
8384

84-
`atomic` defaults to *Behaviour* **A**
85+
[`atomic`][atomic] defaults to *Behaviour* **A**
8586
which creates savepoints by default
8687
when there is already an open transaction.
8788

88-
It's common to decorate functions with `atomic`
89+
It's common to decorate functions with [`atomic`][atomic]
8990
to indicate that code should be atomic (*Outcome* **3**),
9091
but neglect to pass `savepoint=False`.
9192
This results in more database queries than necessary.
9293

93-
Subatomic's `@transaction_required` decorator
94+
Subatomic's [`@transaction_required`][django_subatomic.db.transaction_required] decorator
9495
gives developers an unambiguous alternative
9596
that will never open a savepoint.
9697

@@ -102,7 +103,7 @@ a safe place to continue from after a failure within a transaction.
102103
Ideally then, the logic for catching the failure and continuing a transaction
103104
should be adjacent to the logic which creates the savepoint.
104105

105-
When we use `atomic` as a decorator,
106+
When we use [`atomic`][atomic] as a decorator,
106107
we separate the savepoint creation from the error handling logic.
107108
The decorated function will not be within a `try:...except...:`.
108109

@@ -111,25 +112,25 @@ can make it difficult to know
111112
where continuing after rolling back a savepoint is intended to be handled,
112113
or even if it is handled at all.
113114
This is compounded by the fact that
114-
because `atomic`'s API is ambiguous,
115+
because [`atomic`][atomic]'s API is ambiguous,
115116
it can be hard to know the intended *Outcome*.
116117

117118
To encourage putting rollback logic alongside savepoint creation,
118-
Subatomic's `savepoint` cannot be used as a decorator.
119+
Subatomic's [`savepoint`][django_subatomic.db.savepoint] cannot be used as a decorator.
119120

120121
### Tests without after-commit callbacks
121122

122123
To avoid leaking state between tests,
123-
Django's `TestCase` runs each test within a transaction
124+
Django's [`TestCase`][TestCase] runs each test within a transaction
124125
which gets rolled back at the end of the test.
125126
As a result,
126-
`atomic` blocks encountered during the test
127+
[`atomic`][atomic] blocks encountered during the test
127128
will not create transactions
128129
so no after-commit callbacks will be run.
129130

130131
Even if Django wanted to simulate after-commit callbacks in tests,
131132
it has no way to know which *Outcome* was intended
132-
when it encounters an `atomic` block.
133+
when it encounters an [`atomic`][atomic] block.
133134
It might be running a high-level test where a transaction is intended
134135
and callbacks should be run,
135136
or a low-level test where an open transaction is assumed
@@ -138,9 +139,13 @@ and callbacks should _not_ be run.
138139
Without Subatomic,
139140
developers must either manually run after-commit callbacks in tests,
140141
which is prone to error and omission,
141-
or run the test using `TransactionTestCase`,
142+
or run the test using [`TransactionTestCase`][TransactionTestCase],
142143
which can be very slow.
143144

144-
Subatomic's `transaction()` function
145+
Subatomic's [`transaction()`][django_subatomic.db.transaction] function
145146
will run after-commit callbacks automatically in tests
146147
so that code behaves the same in tests as it does in production.
148+
149+
[atomic]: https://docs.djangoproject.com/en/stable/topics/db/transactions/#django.db.transaction.atomic
150+
[TestCase]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#django.test.TestCase
151+
[TransactionTestCase]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#django.test.TransactionTestCase

0 commit comments

Comments
 (0)