Skip to content

Commit

Permalink
enters QC2 edits from AU
Browse files Browse the repository at this point in the history
  • Loading branch information
katherinetozer committed Mar 3, 2020
1 parent 471a173 commit 047e710
Show file tree
Hide file tree
Showing 18 changed files with 62 additions and 62 deletions.
2 changes: 1 addition & 1 deletion appendix_csvs.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ And here's((("Unit of Work pattern", "UoW for CSVs"))) what a UoW for CSVs would


[[csvs_uow]]
.A UoW for CSVs: commit = csv.writer. (src/allocation/service_layer/csv_uow.py)
.A UoW for CSVs: commit = csv.writer (src/allocation/service_layer/csv_uow.py)
====
[source,python]
----
Expand Down
8 changes: 6 additions & 2 deletions appendix_django.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,12 @@ but it is still made up of familiar-looking Django code:
def test_repository_can_retrieve_a_batch_with_allocations():
sku = "PONY-STATUE"
d_line = django_models.OrderLine.objects.create(orderid="order1", sku=sku, qty=12)
d_b1 = django_models.Batch.objects.create(reference="batch1", sku=sku, qty=100, eta=None)
d_b2 = django_models.Batch.objects.create(reference="batch2", sku=sku, qty=100, eta=None)
d_b1 = django_models.Batch.objects.create(
reference="batch1", sku=sku, qty=100, eta=None
)
d_b2 = django_models.Batch.objects.create(
reference="batch2", sku=sku, qty=100, eta=None
)
django_models.Allocation.objects.create(line=d_line, batch=d_batch1)
repo = repository.DjangoRepository()
Expand Down
2 changes: 1 addition & 1 deletion appendix_validation.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ bear in mind that invalid data wandering through your system is a time bomb;
the deeper it gets, the more damage it can do, and the fewer tools
you have to respond to it.

Back in <<chapter_06_uow>>, we said that the message bus was a great place to put
Back in <<chapter_08_events_and_message_bus>>, we said that the message bus was a great place to put
cross-cutting concerns, and validation is a perfect example of that. Here's how
we might change our bus to perform validation for us:

Expand Down
15 changes: 7 additions & 8 deletions chapter_01_domain_model.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ too late, and OO enthusiasts will tell you to look further back to Ivar
Jacobson and Grady Booch; the term has been around since the mid-1980s.]
and it's been a hugely successful movement in transforming the way people
design software by focusing on the core business domain. Many of the
architecture patterns that we cover in this book—including Entity, Aggregate, Value Object (see <<chapter_06_uow>>), and Repository (in
architecture patterns that we cover in this book—including Entity, Aggregate, Value Object (see <<chapter_07_aggregate>>), and Repository (in
<<chapter_02_repository,the next chapter>>)—come from the DDD tradition.
In a nutshell, DDD says that the most important thing about software is that it
Expand Down Expand Up @@ -211,6 +211,11 @@ We can't allocate the same line twice. For example:
Batches have an _ETA_ if they are currently shipping, or they may be in _warehouse stock_. We allocate to warehouse stock in preference to shipment batches. We allocate to shipment batches in order of which has the earliest ETA.
****

=== Unit Testing Domain Models

We're not going to show you how TDD works in this book, but we want to show you
how we would construct a model from this business conversation.((("unit testing", "of domain models", id="ix_UTDM")))((("domain modeling", "unit testing domain models", id="ix_dommodUT")))

[role="nobreakinside less_space"]
.Exercise for the Reader
******************************************************************************
Expand All @@ -226,12 +231,6 @@ scratch, or combine/rewrite them however you like.
******************************************************************************


=== Unit Testing Domain Models

We're not going to show you how TDD works in this book, but we want to show you
how we would construct a model from this business conversation.((("unit testing", "of domain models", id="ix_UTDM")))((("domain modeling", "unit testing domain models", id="ix_dommodUT")))

Here's what one of our first tests might look like:

[[first_test]]
Expand Down Expand Up @@ -966,7 +965,7 @@ Not everything has to be an object::
or `get_baz()` waiting to happen.((("functions")))
This is the time to apply your best OO design principles::
Revisit the ((("object-oriented design principles")))SOLID principles and all the other good heuristics like "a versus is-a,"
Revisit the ((("object-oriented design principles")))SOLID principles and all the other good heuristics like "has a versus is-a,"
"prefer composition over inheritance," and so on.
You'll((("domain modeling", startref="ix_dommod"))) also want to think about consistency boundaries and aggregates::
Expand Down
8 changes: 4 additions & 4 deletions chapter_02_repository.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ We'll need a way to retrieve batch info from the database and instantiate our do
model objects from it, and we'll also need a way of saving them back to the
database.

_(What? Oh, "gubbins" is a British word for "stuff." You can just ignore that. It's pseudocode, OK?)_
_What? Oh, "gubbins" is a British word for "stuff." You can just ignore that. It's pseudocode, OK?_


=== Applying the DIP to Data Access
Expand Down Expand Up @@ -269,7 +269,7 @@ inversion and the Repository pattern to Django.
==== Inverting the Dependency: ORM Depends on Model

Well, thankfully, that's not the only way to use SQLAlchemy.((("object-relational mappers (ORMs)", "ORM depends on the data model")))((("domain model", "translating to relational database", "ORM depends on the model")))((("dependency inversion principle", "ORM depends on the data model")))((("SQLAlchemy", "explicit ORM mapping with SQLAlchemy Table objects")))((("classical mapping")))((("mappers"))) The alternative is
to define your schema separately, and an explicit _mapper_ for how to convert
to define your schema separately, and to define an explicit _mapper_ for how to convert
between the schema and our domain model, what SQLAlchemy calls a
https://oreil.ly/ZucTG[classical mapping]:

Expand Down Expand Up @@ -663,9 +663,9 @@ def test_repository_can_retrieve_a_batch_with_allocations(session):
assert retrieved == expected # Batch.__eq__ only compares reference #<3>
assert retrieved.sku == expected.sku #<4>
assert retrieved._purchased_quantity == expected._purchased_quantity
assert retrieved._allocations == {
assert retrieved._allocations == { #<4>
model.OrderLine("order1", "GENERIC-SOFA", 12),
} #<4>
}
----
====

Expand Down
4 changes: 1 addition & 3 deletions chapter_03_abstractions.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -654,9 +654,7 @@ Steve Freeman has a great example of overmocked tests in his talk
https://oreil.ly/jAmtr["Test-Driven Development"].
You should also check out this PyCon talk, https://oreil.ly/s3e05["Mocking and Patching Pitfalls"],
by our esteemed tech reviewer, Ed Jung, which also addresses mocking and its
alternatives.((("&quot;Test-Driven Development: That&#x27;s Not What We Meant&quot;", primary-sortas="Test-Driven Development")))((("Freeman, Steve")))((("PyCon talk on Mocking Pitfalls")))((("Jung, Ed")))

And while we're recommending talks, don't miss Brandon Rhodes talking about
alternatives.((("&quot;Test-Driven Development: That&#x27;s Not What We Meant&quot;", primary-sortas="Test-Driven Development")))((("Freeman, Steve")))((("PyCon talk on Mocking Pitfalls")))((("Jung, Ed"))) And while we're recommending talks, don't miss Brandon Rhodes talking about
https://oreil.ly/oiXJM["Hoisting Your I/O"],
which really nicely covers the issues we're talking about, using another simple example.((("hoisting I/O")))((("Rhodes, Brandon")))

Expand Down
8 changes: 4 additions & 4 deletions chapter_04_service_layer.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ business logic, and interfacing code, and we introduce the _Service Layer_
pattern to take care of orchestrating our workflows and defining the use
cases of our system.

We'll also discuss testing: by combining the Service Layer with our Repository
We'll also discuss testing: by combining the Service Layer with our repository
abstraction over the database, we're able to write fast tests, not just of
our domain model but of the entire workflow for a use case.

<<maps_service_layer_after>> shows what we're aiming for: we're going to
add a Flask API that will talk to the service layer, which will serve as the
entrypoint to our domain model. Because our service layer depends on the
`AbstractRepository`, we can unit test it by using `FakeRepository` but run it our production code using `SqlAlchemyRepository`.
`AbstractRepository`, we can unit test it by using `FakeRepository` but run our production code using `SqlAlchemyRepository`.

NOTE: In our diagrams, we are using the convention that new components
are highlighted with bold text/lines (and yellow/orange color, if you're
Expand All @@ -42,7 +42,7 @@ chapter_04_service_layer branch https://oreil.ly/TBRuy[on GitHub]:
git clone https://github.com/cosmicpython/code.git
cd code
git checkout chapter_04_service_layer
# or to code along, checkout the previous chapter:
# or to code along, checkout Chapter 2:
git checkout chapter_02_repository
----
====
Expand Down Expand Up @@ -669,7 +669,7 @@ Here's one way we could organize things:
<1> Let's have a folder for our domain model.((("domain model", "folder for"))) Currently that's just one file,
but for a more complex application, you might have one file per class; you
might have helper parent classes for `Entity`, `ValueObject`, and
`Aggregate`, you might add an __exceptions.py__ for domain-layer exceptions
`Aggregate`, and you might add an __exceptions.py__ for domain-layer exceptions
and, as you'll see in <<part2>>, pass:[<span class="keep-together"><em>commands.py</em></span>] and __events.py__.

<2> We'll distinguish the service layer. Currently that's just one file
Expand Down
4 changes: 3 additions & 1 deletion chapter_05_high_gear_low_gear.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ NOTE: Are you thinking to yourself, POST to _/add_batch_? That's not
very RESTful! You're quite right. We're being happily sloppy, but
if you'd like to make it all more RESTy, maybe a POST to _/batches_,
then knock yourself out! Because Flask is a thin adapter, it'll be
easy. See the next sidebar.
easy. See <<types_of_test_rules_of_thumb, the next sidebar>>.

And our hardcoded SQL queries from _conftest.py_ get replaced with some
API calls, meaning the API tests have no dependencies other than the API,
Expand Down Expand Up @@ -515,3 +515,5 @@ Error handling counts as a feature::
unit tests, of course).((("test-driven development (TDD)", startref="ix_TDD")))
******************************************************************************

Onto the next chapter!
4 changes: 2 additions & 2 deletions chapter_06_uow.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ chapter_06_uow branch https://oreil.ly/MoWdZ[on GitHub]:
git clone https://github.com/cosmicpython/code.git
cd code
git checkout chapter_06_uow
# or to code along, check out the previous chapter:
# or to code along, checkout Chapter 4:
git checkout chapter_04_service_layer
----
====
Expand All @@ -31,7 +31,7 @@ git checkout chapter_04_service_layer
.Without UoW: API talks directly to three layers
image::images/apwp_0601.png[]

<<after_uow_diagram>> shows our target state: the Flask API now does only two
<<after_uow_diagram>> shows our target state. The Flask API now does only two
things: it initializes a unit of work, and it invokes a service. The service
collaborates with the UoW (we like to think of the UoW as being part of the
service layer), but neither the service function itself nor Flask now needs
Expand Down
8 changes: 4 additions & 4 deletions chapter_07_aggregate.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ ____

Per Evans, our aggregate has a root entity (the Cart) that encapsulates access
to items. Each item has its own identity, but other parts of the system will always
refer to the cart only as an indivisible whole.
refer to the Cart only as an indivisible whole.

TIP: Just as we sometimes use pass:[<code><em>_leading_underscores</em></code>] to mark methods or functions
as "private," you can think of aggregates as being the "public" classes of our
Expand Down Expand Up @@ -652,7 +652,7 @@ There are essentially three options((("version numbers", "implementation options
1. `version_number` lives in the domain; we add it to the `Product` constructor,
and `Product.allocate()` is responsible for incrementing it.

2. The services layer could do it! The version number isn't _strictly_ a domain
2. The service layer could do it! The version number isn't _strictly_ a domain
concern, so instead our service layer could assume that the current version number
is attached to `Product` by the repository, and the service layer will increment it
before it does the `commit()`.
Expand Down Expand Up @@ -879,7 +879,7 @@ performing some performance experiments.((("testing", "for data integrity rules"

Specific choices around concurrency control vary a lot based on business
circumstances and storage technology choices, but we'd like to bring this
chapter back to the((("aggregates", "and consistency boundaries recap"))) conceptual idea of an Aggregate: we explicitly model an
chapter back to the((("aggregates", "and consistency boundaries recap"))) conceptual idea of an aggregate: we explicitly model an
object as being the main entrypoint to some subset of our model, and as being in
charge of enforcing the invariants and business rules that apply across all of
those objects.
Expand Down Expand Up @@ -984,7 +984,7 @@ models that represent the shared language of your business experts. Hurrah!
NOTE: At the risk of laboring the point--we've been at pains to point out that
each pattern comes at a cost.((("patterns, deciding whether you need to use them"))) Each layer of indirection has a price in terms
of complexity and duplication in our code and will be confusing to programmers
who've never seen them before. If your app is essentially a simple CRUD
who've never seen these patterns before. If your app is essentially a simple CRUD
wrapper around a database and isn't likely to be anything more than that
in the foreseeable future, _you don't need these patterns_. Go ahead and
use Django, and save yourself a lot of bother.((("CRUD wrapper around a database")))
Expand Down
6 changes: 3 additions & 3 deletions chapter_08_events_and_message_bus.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ We've written a service layer to manage orchestration for us, but even here
the feature feels out of place:

[[email_in_services]]
.And in the services layer, it's out of place (src/allocation/service_layer/services.py)
.And in the service layer, it's out of place (src/allocation/service_layer/services.py)
====
[source,python]
[role="non-head"]
Expand Down Expand Up @@ -691,7 +691,7 @@ class TrackingRepository:
====
<1> By wrapping the repository, we can call the actual `.add()`
and `.get()` methods, avoiding weird underscore methods.
and `.get()` methods, avoiding weird underscorey methods.
See if you can apply a similar pattern to our UoW class in
order to get rid of those Java-y `_commit()` methods too.((("Unit of Work pattern", "getting rid of underscorey methods in UoW class"))) You can find the code on https://github.com/cosmicpython/code/tree/chapter_08_events_and_message_bus_exercise[GitHub].
Expand Down Expand Up @@ -757,7 +757,7 @@ a|
a|
|===

Events are useful for more than just sending email, though. In <<chapter_05_high_gear_low_gear>> we
Events are useful for more than just sending email, though. In <<chapter_07_aggregate>> we
spent a lot of time convincing you that you should define aggregates, or
boundaries where we guarantee consistency. People often ask, "What
should I do if I need to change multiple aggregates as part of a request?" Now
Expand Down
21 changes: 12 additions & 9 deletions chapter_09_all_messagebus.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ We'll come ((("events and the message bus", "transforming our app into message p

<2> We instantiate an event.

<3> Next, we pass it to the message bus.
<3> Then we pass it to the message bus.

And we should be back to a fully functional application, but one that's now
fully event-driven:
Expand All @@ -486,7 +486,7 @@ We're done with our refactoring phase. ((("events and the message bus", "transfo
change easy." Let's implement our new requirement, shown in <<reallocation_sequence_diagram>>: we'll receive as our
inputs some new `BatchQuantityChanged` events and pass them to a handler, which in
turn might emit some `AllocationRequired` events, and those in turn will go
back to our existing handler for allocation, to be reallocated.((("reallocation", "sequence diagram for flow")))
back to our existing handler for reallocation.((("reallocation", "sequence diagram for flow")))

[[reallocation_sequence_diagram]]
.Sequence diagram for reallocation flow
Expand Down Expand Up @@ -839,7 +839,7 @@ violates the SRP.((("message bus", "abstract message bus and its real and fake v
If we change the message bus to being a class,footnote:[The "simple"
implementation ((("Singleton pattern, messagebus.py implementing")))in this chapter essentially uses the _messagebus.py_ module
itself to implement Singleton Pattern.]
itself to implement the Singleton Pattern.]
then building a `FakeMessageBus` is more straightforward:
[[abc_for_fake_messagebus]]
Expand Down Expand Up @@ -882,9 +882,13 @@ if you need more inspiration.
*******************************************************************************

[role="pagebreak-before less_space"]
=== What Have We Achieved?
=== Wrap-Up

Events are simple dataclasses that define the data structures for inputs,
Let's look back at what we've achieved, and think about why we did it.

==== What Have We Achieved?

Events are simple dataclasses that define the data structures for inputs
and internal messages within our system. This is quite powerful from a DDD
standpoint, since events often translate really well into business language
(look up __event storming__ if you haven't already).
Expand All @@ -896,11 +900,11 @@ Handlers are the way we react to events. They can call down to our
and really stick to the SRP.


=== Why Have We Achieved?
==== Why Have We Achieved?

Our ongoing objective with these architectural patterns is to try to have
the complexity of our application grow more slowly than its size.((("message bus", "whole app as, trade-offs")))((("events and the message bus", "transforming our app into message processor", "whole app as message bus, trade-offs"))) When we
go all in on the MessageBus, as always we pay a price in terms of architectural
go all in on the message bus, as always we pay a price in terms of architectural
complexity (see <<chapter_09_all_messagebus_tradeoffs>>), but we buy ourselves a
pattern that can handle almost arbitrarily complex requirements without needing
any further conceptual or architectural change to the way we do things.
Expand All @@ -926,8 +930,7 @@ a|
a|
* A message bus is still a slightly unpredictable way of doing things from
a web point of view. You don't know in advance when things are going to end.
* The duplication/maintenance cost of having model objects _and_ events
now. Adding a field to one usually means adding a field to at least
* There will be duplication of fields and structure between model objects and events, which will have a maintenance cost. Adding a field to one usually means adding a field to at least
one of the others.
|===

Expand Down
2 changes: 1 addition & 1 deletion chapter_10_commands.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ the resilience of our software. Again, the Unit of Work and Command Handler
patterns mean that each attempt starts from a consistent state and won't leave
things half-finished.

WARNING: At some point, regardless of tenacity, we'll have to give up trying to
WARNING: At some point, regardless of `tenacity`, we'll have to give up trying to
process the message. Building reliable systems with distributed messages is
hard, and we have to skim over some tricky bits. There are pointers to more
reference materials in the <<epilogue_1_how_to_get_there_from_here, epilogue>>.
Expand Down
2 changes: 1 addition & 1 deletion chapter_11_external_events.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ def publish_allocated_event(
----
====

===== Internal versus external events
=== Internal Versus External Events

It's a good idea to keep the distinction between internal and external events
clear.((("microservices", "event-based integration", "testing with end-to-end test", startref="ix_mcroevnttst")))((("events", "internal versus external"))) Some events may come from the outside, and some events may get upgraded
Expand Down
Loading

0 comments on commit 047e710

Please sign in to comment.