Skip to content

Commit

Permalink
Merge remote-tracking branch 'atlas/master' into indexing
Browse files Browse the repository at this point in the history
  • Loading branch information
hjwp committed Mar 4, 2020
2 parents 895a4a9 + f39f1b7 commit d42ca0c
Show file tree
Hide file tree
Showing 67 changed files with 117 additions and 59 deletions.
1 change: 1 addition & 0 deletions appendix_django.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ term and if you want to migrate to some of our patterns later.((("Django", "appl
* A business-logic layer might start out working with Django model objects and only later become fully decoupled from the framework and work on
plain Python data structures.

[role="pagebreak-before"]
* For the read side, you can get some of the benefits of CQRS by putting reads
into one place, avoiding ORM calls sprinkled all over the place.

Expand Down
1 change: 1 addition & 0 deletions appendix_validation.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ structure of grammatical sentences. For example, in English, the sentence
sound, while the phrase "hat hat hat hat hat hat wibble" is not. We can describe
grammatically correct sentences as _well formed_.

[role="pagebreak-before"]
How does this map to our application? Here are some examples of syntactic rules:

* An `Allocate` command must have an order ID, a SKU, and a quantity.
Expand Down
4 changes: 2 additions & 2 deletions atlas.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
],
"formats": {
"pdf": {
"version": "web",
"color_count": "4",
"version": "print",
"color_count": "1",
"index": true,
"toc": true,
"syntaxhighlighting": true,
Expand Down
9 changes: 5 additions & 4 deletions chapter_02_repository.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ As mentioned in the <<introduction, introduction>>, a layered architecture is ((
approach to structuring a system that has a UI, some logic, and a database (see
<<layered_architecture2>>).


[role="width-75"]
[[layered_architecture2]]
.Layered architecture
image::images/apwp_0202.png[]
Expand All @@ -116,6 +116,7 @@ Instead, as discussed in the introduction, we'll ((("onion architecture")))think
"inside," and dependencies flowing inward to it; this is what people sometimes call
_onion architecture_ (see <<onion_architecture>>).

[role="width-75"]
[[onion_architecture]]
.Onion architecture
image::images/apwp_0203.png[]
Expand Down Expand Up @@ -506,10 +507,9 @@ class AbstractRepository(abc.ABC):
ABCs actually "work" in Python.((("abstract methods")))((("@abc.abstractmethod"))) Python will refuse to let you instantiate
a class that does not implement all the `abstractmethods` defined in its
parent class.footnote:[To really reap the benefits of ABCs (such as they
may be), you'll want to be running some helpers like `pylint` and `mypy`.]
may be), be running helpers like `pylint` and `mypy`.]

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

[role="pagebreak-before less_space"]
Expand Down Expand Up @@ -566,6 +566,7 @@ In addition, the ((("domain driven design (DDD)", "Repository pattern and")))Rep
do collaborate with programmers who have come to Python from the Java and C#
worlds, they're likely to recognize it. <<repository_pattern_diagram>> illustrates the pattern.

[role="width-60"]
[[repository_pattern_diagram]]
.Repository pattern
image::images/apwp_0205.png[]
Expand Down
18 changes: 7 additions & 11 deletions chapter_03_abstractions.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ We can reduce the degree of coupling within((("coupling", "reducing by abstracti
(<<coupling_illustration1>>) by abstracting away the details
(<<coupling_illustration2>>).


[role="width-40"]
[[coupling_illustration1]]
.Lots of coupling
image::images/apwp_0301.png[]
Expand All @@ -60,7 +60,7 @@ image::images/apwp_0301.png[]
+--------+ +--------+
----


[role="width-75"]
[[coupling_illustration2]]
.Less coupling
image::images/apwp_0302.png[]
Expand Down Expand Up @@ -351,9 +351,9 @@ https://oreil.ly/wnad4[Functional
Core, Imperative Shell], or FCIS).

Let's start off by splitting the code ((("business logic", "separating from state in code")))((("state", "splitting off from logic in the program")))((("I/O", "disentangling details from program logic")))to separate the stateful parts from
the logic:
the logic.

And our top-level function will contains almost no logic at all; it's just an
And our top-level function will contain almost no logic at all; it's just an
imperative series of steps: gather inputs, call our logic, apply outputs:

[[three_parts]]
Expand All @@ -379,11 +379,10 @@ def sync(source, dest):
os.remove(paths[0])
----
====

<1> Here's the first function we factor out, `read_paths_and_hashes()`, which
isolates the I/O part of our application
isolates the I/O part of our application.

<2> And here is where carve out the functional core, the business logic.
<2> Here is where carve out the functional core, the business logic.


The code to build up the dictionary of paths and hashes is now trivially easy
Expand Down Expand Up @@ -714,7 +713,4 @@ a few heuristics and questions to ask yourself:

* What are the dependencies, and what is the core business logic?


Practice makes less imperfect!((("abstractions", startref="ix_abs")))

And now back to our regular programming...
Practice makes less imperfect!((("abstractions", startref="ix_abs"))) And now back to our regular programming...
4 changes: 4 additions & 0 deletions chapter_04_service_layer.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

Back to our allocations project! <<maps_service_layer_before>> shows the point we reached at the end of <<chapter_02_repository>>, which covered the Repository pattern.((("Flask framework", "Flask API and service layer", id="ix_Flskapp")))((("service layer", id="ix_serlay")))

[role="width-75"]
[[maps_service_layer_before]]
.Before: we drive our app by talking to repositories and the domain model
image::images/apwp_0401.png[]
Expand Down Expand Up @@ -720,6 +721,7 @@ Adding the service layer((("service layer", "benefits of")))((("Flask framework"
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
and `AbstractRepository` (the port, in ports and adapters terminology).

[role="width-75"]
[[service_layer_diagram_abstract_dependencies]]
.Abstract dependencies of the service layer
image::images/apwp_0403.png[]
Expand All @@ -743,6 +745,7 @@ When we run the tests, <<service_layer_diagram_test_dependencies>> shows
how we implement the abstract dependencies by using `FakeRepository` (the
adapter).((("service layer", "dependencies of", "testing")))((("dependencies", "abstract dependencies of service layer", "testing")))

[role="width-75"]
[[service_layer_diagram_test_dependencies]]
.Tests provide an implementation of the abstract dependency
image::images/apwp_0404.png[]
Expand Down Expand Up @@ -774,6 +777,7 @@ image::images/apwp_0404.png[]
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
<<service_layer_diagram_runtime_dependencies>>.

[role="width-75"]
[[service_layer_diagram_runtime_dependencies]]
.Dependencies at runtime
image::images/apwp_0405.png[]
Expand Down
1 change: 0 additions & 1 deletion chapter_05_high_gear_low_gear.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,6 @@ Once you have a service layer in place, you really can move the majority
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
things will help along the way:

[role="pagebreak-before less_space"]
* Express your service layer in terms of primitives rather than domain objects.

* In an ideal world, you'll have all the services you need to be able to test
Expand Down
70 changes: 35 additions & 35 deletions chapter_07_aggregate.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ So the plan is this: when we want to allocate an order line, instead of
<<before_aggregates_diagram>>, where we look up all the `Batch` objects in
the world and pass((("batches", "allocating against all batches using domain service")))((("allocate service", "allocating against all batches with"))) them to the `allocate()` domain service...


[role="width-60"]
[[before_aggregates_diagram]]
.Before: allocate against all batches using the domain service
image::images/apwp_0702.png[]
Expand Down Expand Up @@ -277,6 +277,7 @@ allocate --> allocate_domain_service: allocate(orderline, batches)
of all the batches _for that SKU_, and we can call a `.allocate()` method on that
instead.((("Product object", "asking Product to allocate against its batches")))((("batches", "asking Product to allocate against")))

[role="width-75"]
[[after_aggregates_diagram]]
.After: ask Product to allocate against its batches
image::images/apwp_0703.png[]
Expand Down Expand Up @@ -365,6 +366,15 @@ class Product:
// are never supposed to have a collection of aggregates, they could return
// an error for __hash__. or sumfink.

NOTE: This `Product` might not look like what you'd expect a `Product`
model to look like. No price, no description, no dimensions.
Our allocation service doesn't care about any of those things.
This is the((("bounded contexts", "product concept and"))) power of bounded contexts; the concept
of a product in one app can be very different from another.
See the following sidebar for more
discussion.


[role="nobreakinside less_space"]
[[bounded_contexts_sidebar]]
.Aggregates, Bounded Contexts, and Microservices
Expand Down Expand Up @@ -404,17 +414,6 @@ elsewhere. The Fowler link at the start of this sidebar is a good starting point
*******************************************************************************


NOTE: This `Product` might not look like what you'd expect a `Product`
model to look like. No price, no description, no dimensions.
Our allocation service doesn't care about any of those things.
This is the((("bounded contexts", "product concept and"))) power of bounded contexts; the concept
of a product in one app can be very different from another.
See the following sidebar for more
discussion.



=== One Aggregate = One Repository

Once you define certain entities to be aggregates, we need to apply the rule
Expand Down Expand Up @@ -893,28 +892,6 @@ We also recommend these three online papers on
https://dddcommunity.org/library/vernon_2011[effective aggregate design]
by Vaughn Vernon (the "red book" author).

[role="nobreakinside less_space"]
.Aggregates and Consistency Boundaries Recap
*****************************************************************
Aggregates are your entrypoints into the domain model::
By restricting the number of ways that things can be changed,
we make the system easier to reason about.
Aggregates are in charge of a consistency boundary::
An aggregate's((("consistency boundaries", "recap"))) job is to be able to manage our business rules
about invariants as they apply to a group of related objects.
It's the aggregate's job to check that the objects within its
remit are consistent with each other and with our rules, and
to reject changes that would break the rules.
Aggregates and concurrency issues go together::
When thinking about implementing these consistency checks, we
end up thinking about transactions and locks.((("concurrency", "aggregates and concurrency issues"))) Choosing the
right aggregate is about performance as well as conceptual
organization of your domain.
*****************************************************************

<<chapter_07_aggregate_tradoffs>> has some thoughts on the trade-offs of implementing the Aggregate pattern.((("aggregates", "pros and cons or trade-offs")))

[[chapter_07_aggregate_tradoffs]]
Expand Down Expand Up @@ -947,15 +924,38 @@ a|
|===


[role="nobreakinside less_space"]
.Aggregates and Consistency Boundaries Recap
*****************************************************************
Aggregates are your entrypoints into the domain model::
By restricting the number of ways that things can be changed,
we make the system easier to reason about.
Aggregates are in charge of a consistency boundary::
An aggregate's((("consistency boundaries", "recap"))) job is to be able to manage our business rules
about invariants as they apply to a group of related objects.
It's the aggregate's job to check that the objects within its
remit are consistent with each other and with our rules, and
to reject changes that would break the rules.
Aggregates and concurrency issues go together::
When thinking about implementing these consistency checks, we
end up thinking about transactions and locks.((("concurrency", "aggregates and concurrency issues"))) Choosing the
right aggregate is about performance as well as conceptual
organization of your domain.
*****************************************************************


=== Part I Recap

Do you remember <<recap_components_diagram>>, the diagram we showed at the
beginning of <<part1>> to preview where we were heading?((("component diagram at end of Part One")))

[role="width-75"]
[[recap_components_diagram]]
.A component diagram for our app at the end of Part I
image::images/apwp_p101.png[]
image::images/apwp_0705.png[]

So that's where we are at the end of Part I. What have we achieved? We've
seen how to build a domain model that's exercised by a set of
Expand Down
2 changes: 1 addition & 1 deletion chapter_09_all_messagebus.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ inputs some new `BatchQuantityChanged` events and pass them to a handler, which
turn might emit some `AllocationRequired` events, and those in turn will go
back to our existing handler for reallocation.((("reallocation", "sequence diagram for flow")))

[role="width-75"]
[[reallocation_sequence_diagram]]
.Sequence diagram for reallocation flow
image::images/apwp_0904.png[]
Expand Down Expand Up @@ -883,7 +884,6 @@ We use a class-based message bus in <<chapter_13_dependency_injection>>,
if you need more inspiration.
*******************************************************************************

[role="pagebreak-before less_space"]
=== Wrap-Up

Let's look back at what we've achieved, and think about why we did it.
Expand Down
1 change: 1 addition & 0 deletions chapter_11_external_events.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ Our new flow will look like <<reallocation_sequence_diagram_with_redis>>:
Redis provides the `BatchQuantityChanged` event that kicks off the whole process, and our `Allocated` event is published back out to Redis again at the
end.

[role="width-75"]
[[reallocation_sequence_diagram_with_redis]]
.Sequence diagram for reallocation flow
image::images/apwp_1106.png[]
Expand Down
2 changes: 2 additions & 0 deletions epilogue_1_how_to_get_there_from_here.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ we'll need to start separating out responsibilities and introducing clear
boundaries. One of the first things we can do is to start building a service
layer (<<collaboration_app_model>>).

[role="width-60"]
[[collaboration_app_model]]
.Domain of a collaboration system
image::images/apwp_ep01.png[]
Expand Down Expand Up @@ -258,6 +259,7 @@ reading data for commands.

Mostly we did this by replacing direct references with identifiers.

[role="pagebreak-before"]
Before aggregates:

[[aggregates_before]]
Expand Down
Binary file modified images/apwp_0001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0002.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0101.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0102.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0103.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0104.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0201.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0202.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0203.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0204.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0205.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0301.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0302.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0401.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0402.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0403.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0404.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0405.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0501.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0601.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0602.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0701.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0702.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0703.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/apwp_0704.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/apwp_0705.png
Binary file modified images/apwp_0801.png
Binary file modified images/apwp_0901.png
Binary file modified images/apwp_0902.png
Binary file modified images/apwp_0903.png
Binary file modified images/apwp_0904.png
Binary file modified images/apwp_1101.png
Binary file modified images/apwp_1102.png
Binary file modified images/apwp_1103.png
Binary file modified images/apwp_1104.png
Binary file modified images/apwp_1105.png
Binary file modified images/apwp_1106.png
Binary file modified images/apwp_1201.png
Binary file modified images/apwp_1202.png
Binary file modified images/apwp_1301.png
Binary file modified images/apwp_1302.png
Binary file modified images/apwp_1303.png
Binary file modified images/apwp_aa01.png
Binary file modified images/apwp_ep01.png
Binary file modified images/apwp_ep02.png
Binary file modified images/apwp_ep03.png
Binary file modified images/apwp_ep04.png
Binary file modified images/apwp_ep05.png
Binary file modified images/apwp_ep06.png
Binary file modified images/apwp_p101.png
Binary file modified images/apwp_p201.png
1 change: 1 addition & 0 deletions introduction.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ about which categories of code can call each other.
One of the most common examples is the _three-layered architecture_ shown in
<<layered_architecture1>>.

[role="width-75"]
[[layered_architecture1]]
.Layered architecture
image::images/apwp_0002.png[]
Expand Down
6 changes: 2 additions & 4 deletions part1.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ but they have no idea how to fix it.
We've found that many developers, when asked to design a new system, will
immediately start to build a database schema, with the object model treated
as an afterthought. This is where it all starts to go wrong. Instead, _behavior
should come first and drive our storage requirements._

After all, our customers don't care about the data model. They care about what
should come first and drive our storage requirements._ After all, our customers don't care about the data model. They care about what
the system _does_; otherwise they'd just use a spreadsheet.

The first part of the book looks at how to build a rich object model
Expand All @@ -29,7 +27,6 @@ to keep that model decoupled from technical concerns. We show how to build
persistence-ignorant code and how to create stable APIs around our domain so
that we can refactor aggressively.

[role="pagebreak-before less_space"]
To do that, we present four key design patterns:

* The <<chapter_02_repository,Repository pattern>>, an abstraction over the
Expand All @@ -47,6 +44,7 @@ If you'd like a picture of where we're going, take a look at
<<part1_components_diagram>>, but don't worry if none of it makes sense
yet! We introduce each box in the figure, one by one, throughout this part of the book.

[role="width-75"]
[[part1_components_diagram]]
.A component diagram for our app at the end of <<part1>>
image::images/apwp_p101.png[]
Expand Down
2 changes: 1 addition & 1 deletion preface.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ Ben Judson, James Gregory, Łukasz Lechowicz, Clinton Roy, Vitorino Araújo,
Susan Goodbody, Josh Harwood, Daniel Butler, Liu Haibin, Jimmy Davies, Ignacio
Vergara Kausel, Gaia Canestrani, Renne Rocha, pedroabi, Ashia Zawaduk, Jostein
Leira, Brandon Rhodes,
and many more; our apologies if we've missed your name on this list.
and many more; our apologies if we missed you on this list.

Super-mega-thanks to our editor Corbin Collins for his gentle chivvying, and
for being a tireless advocate of the reader. Similarly-superlative thanks to the production staff, Katherine Tozer, Sharon Wilkey, Ellen Troutman-Zaig, and Rebecca Demarest, for your dedication, professionalism, and attention to detail. This book is immeasurably improved thanks to you.
Expand Down
14 changes: 14 additions & 0 deletions theme/epub/epub.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,17 @@
.image-source {
display: none !important;
}

/* Custom widths */
.width-10 { width: 10% !important; }
.width-20 { width: 20% !important; }
.width-30 { width: 30% !important; }
.width-40 { width: 40% !important; }
.width-50 { width: 50% !important; }
.width-60 { width: 60% !important; }
.width-70 { width: 70% !important; }
.width-75 { width: 75% !important; }
.width-80 { width: 80% !important; }
.width-90 { width: 90% !important; }
.width-full,
.width-100 { width: 100% !important; }
Loading

0 comments on commit d42ca0c

Please sign in to comment.