Skip to content

Commit ff146a9

Browse files
committed
Merge remote-tracking branch 'atlas/master' into indexing
2 parents d42ca0c + ade928a commit ff146a9

36 files changed

+147
-175
lines changed

appendix_django.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def test_repository_can_retrieve_a_batch_with_allocations():
107107
d_b1 = django_models.Batch.objects.create(
108108
reference="batch1", sku=sku, qty=100, eta=None
109109
)
110-
d_b2 = django_models.Batch.objects.create(
110+
d_b2 = django_models.Batch.objects.create(
111111
reference="batch2", sku=sku, qty=100, eta=None
112112
)
113113
django_models.Allocation.objects.create(line=d_line, batch=d_batch1)

appendix_project_structure.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ config settings for the following:
123123
- Different container environments (dev, staging, prod, and so on)
124124

125125
Configuration through environment variables as suggested by the
126-
https://12factor.net/config[12-factor] manifesto will solve this problem,
126+
https://12factor.net/config[12-factor manifesto] will solve this problem,
127127
but concretely, how do we implement it in our code and our containers?
128128

129129

appendix_validation.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ TIP: Validate as little as possible. Read only the fields you need, and don't
193193
systems change over time. Resist the temptation to share message
194194
definitions between systems: instead, make it easy to define the data you
195195
depend on. For more info, see Martin Fowler's article on the
196-
https://martinfowler.com/bliki/TolerantReader.html[Tolerant Reader pattern].
196+
https://oreil.ly/YL_La[Tolerant Reader pattern].
197197

198198
[role="pagebreak-before less_space"]
199199
.Is Postel Always Right?

chapter_01_domain_model.asciidoc

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ But there's a lot more to DDD and to the processes, tools, and techniques for
9191
developing a domain model. We hope to give you a taste of it, though,
9292
and cannot encourage you enough to go on and read a proper DDD book:
9393
94-
* The original "blue book,"_Domain-Driven Design_ by Eric Evans (Addison-Wesley Professional)
94+
* The original "blue book," _Domain-Driven Design_ by Eric Evans (Addison-Wesley Professional)
9595
* The "red book," _Implementing Domain-Driven Design_
9696
by Vaughn Vernon (Addison-Wesley Professional)
9797
@@ -179,7 +179,6 @@ so that the examples are easier to talk about.
179179
pass:[<a data-type="xref" href="#allocation_notes" data-xrefstyle="select:nopage">#allocation_notes</a>] shows some notes we might have taken while having a
180180
conversation with our domain experts about allocation.
181181

182-
[role="small"]
183182
[[allocation_notes]]
184183
.Some Notes on Allocation
185184
****
@@ -254,6 +253,7 @@ system, and the names of the classes and variables that we use are taken from th
254253
business jargon. We could show this code to our nontechnical coworkers, and
255254
they would agree that this correctly describes the behavior of the system.
256255

256+
[role="pagebreak-before"]
257257
And here is a domain model that meets our requirements:
258258

259259
[[domain_model_1]]
@@ -368,7 +368,7 @@ So far, we can manage the implementation by just incrementing and decrementing
368368
`Batch.available_quantity`, but as we get into `deallocate()` tests, we'll be
369369
forced into a more intelligent solution:
370370

371-
371+
[role="pagebreak-before"]
372372
[[test_deallocate_unallocated]]
373373
.This test is going to require a smarter model (test_batches.py)
374374
====
@@ -913,6 +913,38 @@ def test_raises_out_of_stock_exception_if_cannot_allocate():
913913
----
914914
====
915915

916+
917+
[role="nobreakinside"]
918+
.Domain Modeling Recap
919+
*****************************************************************
920+
Domain modeling::
921+
This is the part of your code that is closest to the business,
922+
the most likely to change, and the place where you deliver the
923+
most value to the business. Make it easy to understand and modify.
924+
925+
Distinguish entities from value objects::
926+
A value object is defined by its attributes.((("value objects", "entities versus")))((("entities", "value objects versus"))) It's usually best
927+
implemented as an immutable type. If you change an attribute on
928+
a Value Object, it represents a different object. In contrast,
929+
an entity has attributes that may vary over time and it will still be the
930+
same entity. It's important to define what _does_ uniquely identify
931+
an entity (usually some sort of name or reference field).
932+
933+
Not everything has to be an object::
934+
Python is a multiparadigm language, so let the "verbs" in your
935+
code be functions. For every `FooManager`, `BarBuilder`, or `BazFactory`,
936+
there's often a more expressive and readable `manage_foo()`, `build_bar()`,
937+
or `get_baz()` waiting to happen.((("functions")))
938+
939+
This is the time to apply your best OO design principles::
940+
Revisit the ((("object-oriented design principles")))SOLID principles and all the other good heuristics like "has a versus is-a,"
941+
"prefer composition over inheritance," and so on.
942+
943+
You'll((("domain modeling", startref="ix_dommod"))) also want to think about consistency boundaries and aggregates::
944+
But that's a topic for <<chapter_07_aggregate>>.
945+
946+
*****************************************************************
947+
916948
We won't bore you too much with the implementation, but the main thing
917949
to note is that we take care in naming our exceptions in the ubiquitous
918950
language, just as we do our entities, value objects, and services:
@@ -946,33 +978,3 @@ image::images/apwp_0104.png[]
946978
That'll probably do for now! We have a domain service that we can use for our
947979
first use case.((("domain modeling", "functions for domain services", startref="ix_dommodfnc"))) But first we'll need a database...
948980

949-
[role="nobreakinside"]
950-
.Domain Modeling Recap
951-
*****************************************************************
952-
Domain modeling::
953-
This is the part of your code that is closest to the business,
954-
the most likely to change, and the place where you deliver the
955-
most value to the business. Make it easy to understand and modify.
956-
957-
Distinguish entities from value objects::
958-
A value object is defined by its attributes.((("value objects", "entities versus")))((("entities", "value objects versus"))) It's usually best
959-
implemented as an immutable type. If you change an attribute on
960-
a Value Object, it represents a different object. In contrast,
961-
an entity has attributes that may vary over time and it will still be the
962-
same entity. It's important to define what _does_ uniquely identify
963-
an entity (usually some sort of name or reference field).
964-
965-
Not everything has to be an object::
966-
Python is a multiparadigm language, so let the "verbs" in your
967-
code be functions. For every `FooManager`, `BarBuilder`, or `BazFactory`,
968-
there's often a more expressive and readable `manage_foo()`, `build_bar()`,
969-
or `get_baz()` waiting to happen.((("functions")))
970-
971-
This is the time to apply your best OO design principles::
972-
Revisit the ((("object-oriented design principles")))SOLID principles and all the other good heuristics like "has a versus is-a,"
973-
"prefer composition over inheritance," and so on.
974-
975-
You'll((("domain modeling", startref="ix_dommod"))) also want to think about consistency boundaries and aggregates::
976-
But that's a topic for <<chapter_07_aggregate>>.
977-
978-
*****************************************************************

chapter_02_repository.asciidoc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ class AbstractRepository(abc.ABC):
509509
parent class.footnote:[To really reap the benefits of ABCs (such as they
510510
may be), be running helpers like `pylint` and `mypy`.]
511511

512-
<2> `raise NotImplementedError` is nice, but it's neither necessary nor sufficient. Your abstract methods can have real behavior that subclasses
512+
<2> `raise NotImplementedError` is nice, but it's neither necessary nor sufficient. In fact, your abstract methods can have real behavior that subclasses
513513
can call out to, if you really want.
514514

515515
[role="pagebreak-before less_space"]
@@ -838,7 +838,9 @@ summarize the costs and benefits((("Repository pattern", "and persistence ignora
838838
We want to be clear that we're not saying every single application needs
839839
to be built this way; only sometimes does the complexity of the app and domain
840840
make it worth investing the time and effort in adding these extra layers of
841-
indirection. With that in mind, <<chapter_02_repository_tradeoffs>> shows
841+
indirection.
842+
843+
With that in mind, <<chapter_02_repository_tradeoffs>> shows
842844
some of the pros and cons of the Repository pattern and our persistence-ignorant
843845
model.
844846

chapter_03_abstractions.asciidoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ We can reduce the degree of coupling within((("coupling", "reducing by abstracti
4444
(<<coupling_illustration1>>) by abstracting away the details
4545
(<<coupling_illustration2>>).
4646

47-
[role="width-40"]
47+
[role="width-50"]
4848
[[coupling_illustration1]]
4949
.Lots of coupling
5050
image::images/apwp_0301.png[]
@@ -60,7 +60,7 @@ image::images/apwp_0301.png[]
6060
+--------+ +--------+
6161
----
6262

63-
[role="width-75"]
63+
[role="width-90"]
6464
[[coupling_illustration2]]
6565
.Less coupling
6666
image::images/apwp_0302.png[]

chapter_04_service_layer.asciidoc

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,15 @@ add a Flask API that will talk to the service layer, which will serve as the
2323
entrypoint to our domain model. Because our service layer depends on the
2424
`AbstractRepository`, we can unit test it by using `FakeRepository` but run our production code using `SqlAlchemyRepository`.
2525

26-
NOTE: In our diagrams, we are using the convention that new components
27-
are highlighted with bold text/lines (and yellow/orange color, if you're
28-
reading a digital version).
29-
3026
[[maps_service_layer_after]]
3127
.The service layer will become the main way into our app
3228
image::images/apwp_0402.png[]
3329

3430
// IDEA more detailed legend
3531

32+
In our diagrams, we are using the convention that new components
33+
are highlighted with bold text/lines (and yellow/orange color, if you're
34+
reading a digital version).
3635

3736
[TIP]
3837
====
@@ -721,6 +720,13 @@ Adding the service layer((("service layer", "benefits of")))((("Flask framework"
721720
dependencies of our service ((("Flask framework", "Flask API and service layer", "service layer dependencies")))((("service layer", "dependencies of")))((("dependencies", "abstract dependencies of service layer")))layer: the domain model
722721
and `AbstractRepository` (the port, in ports and adapters terminology).
723722

723+
When we run the tests, <<service_layer_diagram_test_dependencies>> shows
724+
how we implement the abstract dependencies by using `FakeRepository` (the
725+
adapter).((("service layer", "dependencies of", "testing")))((("dependencies", "abstract dependencies of service layer", "testing")))
726+
727+
And when we actually run our app, we swap in the "real" dependency((("service layer", "dependencies of", "real dependencies at runtime")))((("dependencies", "real service layer dependencies at runtime"))) shown in
728+
<<service_layer_diagram_runtime_dependencies>>.
729+
724730
[role="width-75"]
725731
[[service_layer_diagram_abstract_dependencies]]
726732
.Abstract dependencies of the service layer
@@ -741,10 +747,6 @@ image::images/apwp_0403.png[]
741747
----
742748

743749

744-
When we run the tests, <<service_layer_diagram_test_dependencies>> shows
745-
how we implement the abstract dependencies by using `FakeRepository` (the
746-
adapter).((("service layer", "dependencies of", "testing")))((("dependencies", "abstract dependencies of service layer", "testing")))
747-
748750
[role="width-75"]
749751
[[service_layer_diagram_test_dependencies]]
750752
.Tests provide an implementation of the abstract dependency
@@ -774,9 +776,6 @@ image::images/apwp_0404.png[]
774776
+----------------------+
775777
----
776778

777-
And when we actually run our app, we swap in the "real" dependency((("service layer", "dependencies of", "real dependencies at runtime")))((("dependencies", "real service layer dependencies at runtime"))) shown in
778-
<<service_layer_diagram_runtime_dependencies>>.
779-
780779
[role="width-75"]
781780
[[service_layer_diagram_runtime_dependencies]]
782781
.Dependencies at runtime
@@ -857,7 +856,6 @@ a|
857856
controllers").((("Flask framework", "Flask API and service layer", startref="ix_Flskapp")))((("service layer", startref="ix_serlay")))
858857
|===
859858

860-
[role="pagebreak-before less_space"]
861859
But there are still some bits of awkwardness to tidy up:
862860

863861
* The service layer is still tightly coupled to the domain, because

chapter_05_high_gear_low_gear.asciidoc

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ understand how the system works and how the core concepts interrelate.
181181
We often "sketch" new behaviors by writing tests at this level to see how the
182182
code might look. When we want to improve the design of the code, though, we will need to replace
183183
or delete these tests, because they are tightly coupled to a particular
184-
implementation.
184+
pass:[<span class="keep-together">implementation</span>].
185185

186186
// IDEA: (EJ3) an example that is overmocked would be good here if you decide to
187187
// add one. Ch12 already has one that could be expanded.
@@ -334,7 +334,7 @@ TIP: In general, if you find yourself needing to do domain-layer stuff directly
334334
in your service-layer tests, it may be an indication that your service
335335
layer is incomplete.
336336

337-
337+
[role="pagebreak-before"]
338338
And the implementation is just two lines:
339339

340340
[[add_batch_service]]
@@ -392,7 +392,7 @@ This is a really nice place to be in. Our service-layer tests depend on only
392392
the service layer itself, leaving us completely free to refactor the model as
393393
we see fit.((("test-driven development (TDD)", "fully decoupling service layer from the domain", startref="ix_TDDdecser")))((("domain layer", "fully decoupling service layer from", startref="ix_domlaydec")))((("service layer", "fully decoupling from the domain", startref="ix_serlaydec")))
394394

395-
395+
[role="pagebreak-before less_space"]
396396
=== Carrying the Improvement Through to the E2E Tests
397397

398398
In the same way that adding `add_batch` helped decouple our service-layer
@@ -470,15 +470,7 @@ def test_happy_path_returns_201_and_allocated_batch():
470470
=== Wrap-Up
471471

472472
Once you have a service layer in place, you really can move the majority
473-
of your test coverage to unit tests and develop a healthy test pyramid.((("test-driven development (TDD)", "benefits of service layer to")))((("service layer", "benefits to test-driven development"))) A few
474-
things will help along the way:
475-
476-
* Express your service layer in terms of primitives rather than domain objects.
477-
478-
* In an ideal world, you'll have all the services you need to be able to test
479-
entirely against the service layer, rather than hacking state via
480-
repositories or the database.((("test-driven development (TDD)", "types of tests, rules of thumb for"))) This pays off in your end-to-end tests as well.
481-
473+
of your test coverage to unit tests and develop a healthy test pyramid.((("test-driven development (TDD)", "benefits of service layer to")))((("service layer", "benefits to test-driven development")))
482474

483475
[role="nobreakinside less_space"]
484476
[[types_of_test_rules_of_thumb]]
@@ -515,4 +507,13 @@ Error handling counts as a feature::
515507
516508
******************************************************************************
517509

510+
A few
511+
things will help along the way:
512+
513+
* Express your service layer in terms of primitives rather than domain objects.
514+
515+
* In an ideal world, you'll have all the services you need to be able to test
516+
entirely against the service layer, rather than hacking state via
517+
repositories or the database.((("test-driven development (TDD)", "types of tests, rules of thumb for"))) This pays off in your end-to-end tests as well.
518+
518519
Onto the next chapter!

chapter_06_uow.asciidoc

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ layer to start a session, it talks to the repository layer to initialize
1616
[TIP]
1717
====
1818
The code for this chapter is in the
19-
chapter_06_uow branch https://oreil.ly/MoWdZ[on GitHub]:
19+
chapter_06_uow branch https://oreil.ly/MoWdZ[on pass:[<span class="keep-together">GitHub</span>]]:
2020
2121
----
2222
git clone https://github.com/cosmicpython/code.git
@@ -27,6 +27,7 @@ git checkout chapter_04_service_layer
2727
----
2828
====
2929

30+
[role="width-75"]
3031
[[before_uow_diagram]]
3132
.Without UoW: API talks directly to three layers
3233
image::images/apwp_0601.png[]
@@ -39,6 +40,7 @@ to talk directly to the database.((("Unit of Work pattern", "managing database s
3940

4041
And we'll do it all using a lovely piece((("context manager"))) of Python syntax, a context manager.
4142

43+
[role="width-75"]
4244
[[after_uow_diagram]]
4345
.With UoW: UoW now manages database state
4446
image::images/apwp_0602.png[]
@@ -309,7 +311,7 @@ def test_allocate_returns_allocation():
309311

310312
<2> Notice the similarity with the fake `commit()` function
311313
from `FakeSession` (which we can now get rid of). But it's
312-
a substantial improvement because we're now faking out
314+
a substantial improvement because we're now pass:[<span class="keep-together">faking</span>] out
313315
code that we wrote rather than third-party code. Some
314316
people say, https://oreil.ly/0LVj3["Don't mock what you don't own"].
315317

@@ -578,10 +580,6 @@ it's doing are covered in _test_repository.py_. That last test, you might keep a
578580
but we could certainly see an argument for just keeping everything at the highest
579581
possible level of abstraction (just as we did for the unit tests).
580582

581-
TIP: This is another example of the lesson from <<chapter_05_high_gear_low_gear>>:
582-
as we build better abstractions, we can move our tests to run against them,
583-
which leaves us free to change the underlying details.
584-
585583
[role="nobreakinside less_space"]
586584
.Exercise for the Reader
587585
******************************************************************************
@@ -599,6 +597,10 @@ the abstract UoW. Why not send us a link to your repo if you come up with
599597
something you're particularly proud of?
600598
******************************************************************************
601599

600+
TIP: This is another example of the lesson from <<chapter_05_high_gear_low_gear>>:
601+
as we build better abstractions, we can move our tests to run against them,
602+
which leaves us free to change the underlying details.
603+
602604

603605
=== Wrap-Up
604606

0 commit comments

Comments
 (0)