-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathchapter_08_events_and_message_bus.html
1305 lines (1211 loc) · 82.8 KB
/
chapter_08_events_and_message_bus.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 2.0.20">
<title>Events and the Message Bus</title>
<style>
/* Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
@import url("//fonts.googleapis.com/css?family=Noto+Sans:300,600italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700");
@import url(//asciidoctor.org/stylesheets/asciidoctor.css); /* Default asciidoc style framework - important */
/* customisations by harry */
h1, h2, h3, h4, h5, h6 {
position: relative;
}
a.anchor {
top: 0;
}
/* hide inline ditaa/plantuml source listings for images */
.image-source {
display: none
}
/* make formal codeblocks a bit nicer */
.exampleblock > .content {
padding: 2px;
background-color: white;
border: 0;
margin-bottom: 2em;
}
.exampleblock .title {
text-align: right;
}
/* prev/next chapter links at bottom of page */
.prev_and_next_chapter_links {
margin: 10px;
}
.prev_chapter_link {
float: left;
}
.next_chapter_link {
float: right;
}
/* a few tweaks to existing styles */
#toc li {
margin-top: 0.5em;
}
#footnotes hr {
width: 100%;
}
/* end customisations by harry */
/* CUSTOMISATIONS */
/* Change the values in root for quick customisation. If you want even more fine grain... venture further. */
:root{
--maincolor:#FFFFFF;
--primarycolor:#2c3e50;
--secondarycolor:#ba3925;
--tertiarycolor: #186d7a;
--sidebarbackground:#CCC;
--linkcolor:#b71c1c;
--linkcoloralternate:#f44336;
--white:#FFFFFF;
--black:#000000;
}
/* Text styles */
h1{color:var(--primarycolor) !important;}
h2,h3,h4,h5,h6{color:var(--secondarycolor) !important;}
.title{color:var(--tertiarycolor) !important; font-family:"Noto Sans",sans-serif !important;font-style: normal !important; font-weight: normal !important;}
p{font-family: "Noto Sans",sans-serif !important}
/* Table styles */
th{font-family: "Noto Sans",sans-serif !important}
/* Responsiveness fixes */
video {
max-width: 100%;
}
@media all and (max-width: 600px) {
table {
width: 55vw!important;
font-size: 3vw;
}
</style>
<style>
pre.pygments .hll { background-color: #ffffcc }
pre.pygments { background: #f0f0f0; }
pre.pygments .tok-c { color: #60a0b0; font-style: italic } /* Comment */
pre.pygments .tok-err { border: 1px solid #FF0000 } /* Error */
pre.pygments .tok-k { color: #007020; font-weight: bold } /* Keyword */
pre.pygments .tok-o { color: #666666 } /* Operator */
pre.pygments .tok-ch { color: #60a0b0; font-style: italic } /* Comment.Hashbang */
pre.pygments .tok-cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */
pre.pygments .tok-cp { color: #007020 } /* Comment.Preproc */
pre.pygments .tok-cpf { color: #60a0b0; font-style: italic } /* Comment.PreprocFile */
pre.pygments .tok-c1 { color: #60a0b0; font-style: italic } /* Comment.Single */
pre.pygments .tok-cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */
pre.pygments .tok-gd { color: #A00000 } /* Generic.Deleted */
pre.pygments .tok-ge { font-style: italic } /* Generic.Emph */
pre.pygments .tok-gr { color: #FF0000 } /* Generic.Error */
pre.pygments .tok-gh { color: #000080; font-weight: bold } /* Generic.Heading */
pre.pygments .tok-gi { color: #00A000 } /* Generic.Inserted */
pre.pygments .tok-go { color: #888888 } /* Generic.Output */
pre.pygments .tok-gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
pre.pygments .tok-gs { font-weight: bold } /* Generic.Strong */
pre.pygments .tok-gu { color: #800080; font-weight: bold } /* Generic.Subheading */
pre.pygments .tok-gt { color: #0044DD } /* Generic.Traceback */
pre.pygments .tok-kc { color: #007020; font-weight: bold } /* Keyword.Constant */
pre.pygments .tok-kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
pre.pygments .tok-kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
pre.pygments .tok-kp { color: #007020 } /* Keyword.Pseudo */
pre.pygments .tok-kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
pre.pygments .tok-kt { color: #902000 } /* Keyword.Type */
pre.pygments .tok-m { color: #40a070 } /* Literal.Number */
pre.pygments .tok-s { color: #4070a0 } /* Literal.String */
pre.pygments .tok-na { color: #4070a0 } /* Name.Attribute */
pre.pygments .tok-nb { color: #007020 } /* Name.Builtin */
pre.pygments .tok-nc { color: #0e84b5; font-weight: bold } /* Name.Class */
pre.pygments .tok-no { color: #60add5 } /* Name.Constant */
pre.pygments .tok-nd { color: #555555; font-weight: bold } /* Name.Decorator */
pre.pygments .tok-ni { color: #d55537; font-weight: bold } /* Name.Entity */
pre.pygments .tok-ne { color: #007020 } /* Name.Exception */
pre.pygments .tok-nf { color: #06287e } /* Name.Function */
pre.pygments .tok-nl { color: #002070; font-weight: bold } /* Name.Label */
pre.pygments .tok-nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
pre.pygments .tok-nt { color: #062873; font-weight: bold } /* Name.Tag */
pre.pygments .tok-nv { color: #bb60d5 } /* Name.Variable */
pre.pygments .tok-ow { color: #007020; font-weight: bold } /* Operator.Word */
pre.pygments .tok-w { color: #bbbbbb } /* Text.Whitespace */
pre.pygments .tok-mb { color: #40a070 } /* Literal.Number.Bin */
pre.pygments .tok-mf { color: #40a070 } /* Literal.Number.Float */
pre.pygments .tok-mh { color: #40a070 } /* Literal.Number.Hex */
pre.pygments .tok-mi { color: #40a070 } /* Literal.Number.Integer */
pre.pygments .tok-mo { color: #40a070 } /* Literal.Number.Oct */
pre.pygments .tok-sa { color: #4070a0 } /* Literal.String.Affix */
pre.pygments .tok-sb { color: #4070a0 } /* Literal.String.Backtick */
pre.pygments .tok-sc { color: #4070a0 } /* Literal.String.Char */
pre.pygments .tok-dl { color: #4070a0 } /* Literal.String.Delimiter */
pre.pygments .tok-sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
pre.pygments .tok-s2 { color: #4070a0 } /* Literal.String.Double */
pre.pygments .tok-se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
pre.pygments .tok-sh { color: #4070a0 } /* Literal.String.Heredoc */
pre.pygments .tok-si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
pre.pygments .tok-sx { color: #c65d09 } /* Literal.String.Other */
pre.pygments .tok-sr { color: #235388 } /* Literal.String.Regex */
pre.pygments .tok-s1 { color: #4070a0 } /* Literal.String.Single */
pre.pygments .tok-ss { color: #517918 } /* Literal.String.Symbol */
pre.pygments .tok-bp { color: #007020 } /* Name.Builtin.Pseudo */
pre.pygments .tok-fm { color: #06287e } /* Name.Function.Magic */
pre.pygments .tok-vc { color: #bb60d5 } /* Name.Variable.Class */
pre.pygments .tok-vg { color: #bb60d5 } /* Name.Variable.Global */
pre.pygments .tok-vi { color: #bb60d5 } /* Name.Variable.Instance */
pre.pygments .tok-vm { color: #bb60d5 } /* Name.Variable.Magic */
pre.pygments .tok-il { color: #40a070 } /* Literal.Number.Integer.Long */
</style>
</head>
<body class="article toc2 toc-left">
<div id="buy_the_book" style="position: absolute; top: 0; right: 0; z-index:100">
<a href="/#buy_the_book">
<img src="/images/buy_the_book.svg" alt="buy the book ribbon">
</a>
</div>
<div id="header">
<div id="toc" class="toc2">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="/book/preface.html">Preface</a></li>
<li><a href="/book/introduction.html">Introduction</a></li>
<li><a href="/book/part1.html">Part 1: Building an Architecture to Support Domain Modeling</a></li>
<li><a href="/book/chapter_01_domain_model.html">1. Domain Modeling</a></li>
<li><a href="/book/chapter_02_repository.html">2. Repository Pattern</a></li>
<li><a href="/book/chapter_03_abstractions.html">3. A Brief Interlude: On Coupling <span class="keep-together">and Abstractions</span></a></li>
<li><a href="/book/chapter_04_service_layer.html">4. Our First Use Case: <span class="keep-together">Flask API and Service Layer</span></a></li>
<li><a href="/book/chapter_05_high_gear_low_gear.html">5. TDD in High Gear and Low Gear</a></li>
<li><a href="/book/chapter_06_uow.html">6. Unit of Work Pattern</a></li>
<li><a href="/book/chapter_07_aggregate.html">7. Aggregates and Consistency Boundaries</a></li>
<li><a href="/book/part2.html">Part 2: Event-Driven Architecture</a></li>
<li><a href="/book/chapter_08_events_and_message_bus.html">8. Events and the Message Bus</a></li>
<li><a href="/book/chapter_09_all_messagebus.html">9. Going to Town on the Message Bus</a></li>
<li><a href="/book/chapter_10_commands.html">10. Commands and Command Handler</a></li>
<li><a href="/book/chapter_11_external_events.html">11. Event-Driven Architecture: Using Events to Integrate Microservices</a></li>
<li><a href="/book/chapter_12_cqrs.html">12. Command-Query Responsibility Segregation (CQRS)</a></li>
<li><a href="/book/chapter_13_dependency_injection.html">13. Dependency Injection (and Bootstrapping)</a></li>
<li><a href="/book/epilogue_1_how_to_get_there_from_here.html">Epilogue: Epilogue</a></li>
<li><a href="/book/appendix_ds1_table.html">Appendix A: Summary Diagram and Table</a></li>
<li><a href="/book/appendix_project_structure.html">Appendix B: A Template Project Structure</a></li>
<li><a href="/book/appendix_csvs.html">Appendix C: Swapping Out the Infrastructure: Do Everything with CSVs</a></li>
<li><a href="/book/appendix_django.html">Appendix D: Repository and Unit of Work Patterns with Django</a></li>
<li><a href="/book/appendix_validation.html">Appendix E: Validation</a></li>
</ul>
</div>
</div>
<div id="content">
<div class="sect1">
<h2 id="chapter_08_events_and_message_bus">8: Events and the Message Bus<a class="anchor" href="#chapter_08_events_and_message_bus"></a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>
So far we’ve spent a lot of time and energy on a simple problem that we could
easily have solved with Django. You might be asking if the increased testability
and expressiveness are <em>really</em> worth all the effort.</p>
</div>
<div class="paragraph">
<p>In practice, though, we find that it’s not the obvious features that make a mess
of our codebases: it’s the goop around the edge. It’s reporting, and permissions,
and workflows that touch a zillion objects.</p>
</div>
<div class="paragraph">
<p>Our example will be a typical notification requirement: when we can’t allocate
an order because we’re out of stock, we should alert the buying team. They’ll
go and fix the problem by buying more stock, and all will be well.</p>
</div>
<div class="paragraph">
<p>For a first version, our product owner says we can just send the alert by email.</p>
</div>
<div class="paragraph">
<p>Let’s see how our architecture holds up when we need to plug in some of the
mundane stuff that makes up so much of our systems.</p>
</div>
<div class="paragraph">
<p>We’ll start by doing the simplest, most expeditious thing, and talk about
why it’s exactly this kind of decision that leads us to the Big Ball of Mud.</p>
</div>
<div class="paragraph">
<p>
Then we’ll show how to use the <em>Domain Events</em> pattern to separate side effects from our
use cases, and how to use a simple <em>Message Bus</em> pattern for triggering behavior
based on those events. We’ll show a few options for creating
those events and how to pass them to the message bus, and finally we’ll show
how the Unit of Work pattern can be modified to connect the two together elegantly,
as previewed in <a href="#message_bus_diagram">Events flowing through the system</a>.</p>
</div>
<div id="message_bus_diagram" class="imageblock">
<div class="content">
<img src="images/apwp_0801.png" alt="apwp 0801">
</div>
<div class="title">Figure 1. Events flowing through the system</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">Tip</div>
</td>
<td class="content">
<div class="paragraph">
<p>The code for this chapter is in the
chapter_08_events_and_message_bus branch <a href="https://oreil.ly/M-JuL">on GitHub</a>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>git clone https://github.com/cosmicpython/code.git
cd code
git checkout chapter_08_events_and_message_bus
# or to code along, checkout the previous chapter:
git checkout chapter_07_aggregate</pre>
</div>
</div>
</td>
</tr>
</table>
</div>
<div class="sect2">
<h3 id="_avoiding_making_a_mess"><a class="anchor" href="#_avoiding_making_a_mess"></a>Avoiding Making a Mess</h3>
<div class="paragraph">
<p>
So. Email alerts when we run out of stock. When we have new requirements like ones that <em>really</em> have nothing to do with the core domain, it’s all too easy to
start dumping these things into our web controllers.</p>
</div>
<div class="sect3">
<h4 id="_first_lets_avoid_making_a_mess_of_our_web_controllers"><a class="anchor" href="#_first_lets_avoid_making_a_mess_of_our_web_controllers"></a>First, Let’s Avoid Making a Mess of Our Web Controllers</h4>
<div class="paragraph">
<p>
As a one-off hack, this <em>might</em> be OK:</p>
</div>
<div id="email_in_flask" class="exampleblock">
<div class="title">Just whack it in the endpoint—what could go wrong? (src/allocation/entrypoints/flask_app.py)</div>
<div class="content">
<div class="listingblock skip">
<div class="content">
<pre class="pygments highlight"><code data-lang="python"><span></span><span class="tok-nd">@app</span><span class="tok-o">.</span><span class="tok-n">route</span><span class="tok-p">(</span><span class="tok-s2">"/allocate"</span><span class="tok-p">,</span> <span class="tok-n">methods</span><span class="tok-o">=</span><span class="tok-p">[</span><span class="tok-s2">"POST"</span><span class="tok-p">])</span>
<span class="tok-k">def</span> <span class="tok-nf">allocate_endpoint</span><span class="tok-p">():</span>
<span class="tok-n">line</span> <span class="tok-o">=</span> <span class="tok-n">model</span><span class="tok-o">.</span><span class="tok-n">OrderLine</span><span class="tok-p">(</span>
<span class="tok-n">request</span><span class="tok-o">.</span><span class="tok-n">json</span><span class="tok-p">[</span><span class="tok-s2">"orderid"</span><span class="tok-p">],</span>
<span class="tok-n">request</span><span class="tok-o">.</span><span class="tok-n">json</span><span class="tok-p">[</span><span class="tok-s2">"sku"</span><span class="tok-p">],</span>
<span class="tok-n">request</span><span class="tok-o">.</span><span class="tok-n">json</span><span class="tok-p">[</span><span class="tok-s2">"qty"</span><span class="tok-p">],</span>
<span class="tok-p">)</span>
<span class="tok-k">try</span><span class="tok-p">:</span>
<span class="tok-n">uow</span> <span class="tok-o">=</span> <span class="tok-n">unit_of_work</span><span class="tok-o">.</span><span class="tok-n">SqlAlchemyUnitOfWork</span><span class="tok-p">()</span>
<span class="tok-n">batchref</span> <span class="tok-o">=</span> <span class="tok-n">services</span><span class="tok-o">.</span><span class="tok-n">allocate</span><span class="tok-p">(</span><span class="tok-n">line</span><span class="tok-p">,</span> <span class="tok-n">uow</span><span class="tok-p">)</span>
<span class="tok-k">except</span> <span class="tok-p">(</span><span class="tok-n">model</span><span class="tok-o">.</span><span class="tok-n">OutOfStock</span><span class="tok-p">,</span> <span class="tok-n">services</span><span class="tok-o">.</span><span class="tok-n">InvalidSku</span><span class="tok-p">)</span> <span class="tok-k">as</span> <span class="tok-n">e</span><span class="tok-p">:</span>
<span class="tok-n">send_mail</span><span class="tok-p">(</span>
<span class="tok-s2">"out of stock"</span><span class="tok-p">,</span>
<span class="tok-s2">"[email protected]"</span><span class="tok-p">,</span>
<span class="tok-sa">f</span><span class="tok-s2">"</span><span class="tok-si">{</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">orderid</span><span class="tok-si">}</span><span class="tok-s2"> - </span><span class="tok-si">{</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-si">}</span><span class="tok-s2">"</span>
<span class="tok-p">)</span>
<span class="tok-k">return</span> <span class="tok-p">{</span><span class="tok-s2">"message"</span><span class="tok-p">:</span> <span class="tok-nb">str</span><span class="tok-p">(</span><span class="tok-n">e</span><span class="tok-p">)},</span> <span class="tok-mi">400</span>
<span class="tok-k">return</span> <span class="tok-p">{</span><span class="tok-s2">"batchref"</span><span class="tok-p">:</span> <span class="tok-n">batchref</span><span class="tok-p">},</span> <span class="tok-mi">201</span></code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>…​but it’s easy to see how we can quickly end up in a mess by patching things up
like this. Sending email isn’t the job of our HTTP layer, and we’d like to be
able to unit test this new feature.</p>
</div>
</div>
<div class="sect3">
<h4 id="_and_lets_not_make_a_mess_of_our_model_either"><a class="anchor" href="#_and_lets_not_make_a_mess_of_our_model_either"></a>And Let’s Not Make a Mess of Our Model Either</h4>
<div class="paragraph">
<p>
Assuming we don’t want to put this code into our web controllers, because
we want them to be as thin as possible, we may look at putting it right at
the source, in the model:</p>
</div>
<div id="email_in_model" class="exampleblock">
<div class="title">Email-sending code in our model isn’t lovely either (src/allocation/domain/model.py)</div>
<div class="content">
<div class="listingblock non-head">
<div class="content">
<pre class="pygments highlight"><code data-lang="python"><span></span> <span class="tok-k">def</span> <span class="tok-nf">allocate</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">,</span> <span class="tok-n">line</span><span class="tok-p">:</span> <span class="tok-n">OrderLine</span><span class="tok-p">)</span> <span class="tok-o">-></span> <span class="tok-nb">str</span><span class="tok-p">:</span>
<span class="tok-k">try</span><span class="tok-p">:</span>
<span class="tok-n">batch</span> <span class="tok-o">=</span> <span class="tok-nb">next</span><span class="tok-p">(</span><span class="tok-n">b</span> <span class="tok-k">for</span> <span class="tok-n">b</span> <span class="tok-ow">in</span> <span class="tok-nb">sorted</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">batches</span><span class="tok-p">)</span> <span class="tok-k">if</span> <span class="tok-n">b</span><span class="tok-o">.</span><span class="tok-n">can_allocate</span><span class="tok-p">(</span><span class="tok-n">line</span><span class="tok-p">))</span>
<span class="tok-c1">#...</span>
<span class="tok-k">except</span> <span class="tok-ne">StopIteration</span><span class="tok-p">:</span>
<span class="tok-n">email</span><span class="tok-o">.</span><span class="tok-n">send_mail</span><span class="tok-p">(</span><span class="tok-s2">"[email protected]"</span><span class="tok-p">,</span> <span class="tok-sa">f</span><span class="tok-s2">"Out of stock for </span><span class="tok-si">{</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-si">}</span><span class="tok-s2">"</span><span class="tok-p">)</span>
<span class="tok-k">raise</span> <span class="tok-n">OutOfStock</span><span class="tok-p">(</span><span class="tok-sa">f</span><span class="tok-s2">"Out of stock for sku </span><span class="tok-si">{</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-si">}</span><span class="tok-s2">"</span><span class="tok-p">)</span></code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>But that’s even worse! We don’t want our model to have any dependencies on
infrastructure concerns like <code>email.send_mail</code>.</p>
</div>
<div class="paragraph">
<p>This email-sending thing is unwelcome <em>goop</em> messing up the nice clean flow
of our system. What we’d like is to keep our domain model focused on the rule
"You can’t allocate more stuff than is actually available."</p>
</div>
</div>
<div class="sect3">
<h4 id="_or_the_service_layer"><a class="anchor" href="#_or_the_service_layer"></a>Or the Service Layer!</h4>
<div class="paragraph">
<p>
The requirement "Try to allocate some stock, and send an email if it fails" is
an example of workflow orchestration: it’s a set of steps that the system has
to follow to <span class="keep-together">achieve</span> a goal.</p>
</div>
<div class="paragraph">
<p>We’ve written a service layer to manage orchestration for us, but even here
the feature feels out of place:</p>
</div>
<div id="email_in_services" class="exampleblock">
<div class="title">And in the service layer, it’s out of place (src/allocation/service_layer/services.py)</div>
<div class="content">
<div class="listingblock non-head">
<div class="content">
<pre class="pygments highlight"><code data-lang="python"><span></span><span class="tok-k">def</span> <span class="tok-nf">allocate</span><span class="tok-p">(</span>
<span class="tok-n">orderid</span><span class="tok-p">:</span> <span class="tok-nb">str</span><span class="tok-p">,</span> <span class="tok-n">sku</span><span class="tok-p">:</span> <span class="tok-nb">str</span><span class="tok-p">,</span> <span class="tok-n">qty</span><span class="tok-p">:</span> <span class="tok-nb">int</span><span class="tok-p">,</span>
<span class="tok-n">uow</span><span class="tok-p">:</span> <span class="tok-n">unit_of_work</span><span class="tok-o">.</span><span class="tok-n">AbstractUnitOfWork</span><span class="tok-p">,</span>
<span class="tok-p">)</span> <span class="tok-o">-></span> <span class="tok-nb">str</span><span class="tok-p">:</span>
<span class="tok-n">line</span> <span class="tok-o">=</span> <span class="tok-n">OrderLine</span><span class="tok-p">(</span><span class="tok-n">orderid</span><span class="tok-p">,</span> <span class="tok-n">sku</span><span class="tok-p">,</span> <span class="tok-n">qty</span><span class="tok-p">)</span>
<span class="tok-k">with</span> <span class="tok-n">uow</span><span class="tok-p">:</span>
<span class="tok-n">product</span> <span class="tok-o">=</span> <span class="tok-n">uow</span><span class="tok-o">.</span><span class="tok-n">products</span><span class="tok-o">.</span><span class="tok-n">get</span><span class="tok-p">(</span><span class="tok-n">sku</span><span class="tok-o">=</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-p">)</span>
<span class="tok-k">if</span> <span class="tok-n">product</span> <span class="tok-ow">is</span> <span class="tok-kc">None</span><span class="tok-p">:</span>
<span class="tok-k">raise</span> <span class="tok-n">InvalidSku</span><span class="tok-p">(</span><span class="tok-sa">f</span><span class="tok-s2">"Invalid sku </span><span class="tok-si">{</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-si">}</span><span class="tok-s2">"</span><span class="tok-p">)</span>
<span class="tok-k">try</span><span class="tok-p">:</span>
<span class="tok-n">batchref</span> <span class="tok-o">=</span> <span class="tok-n">product</span><span class="tok-o">.</span><span class="tok-n">allocate</span><span class="tok-p">(</span><span class="tok-n">line</span><span class="tok-p">)</span>
<span class="tok-n">uow</span><span class="tok-o">.</span><span class="tok-n">commit</span><span class="tok-p">()</span>
<span class="tok-k">return</span> <span class="tok-n">batchref</span>
<span class="tok-k">except</span> <span class="tok-n">model</span><span class="tok-o">.</span><span class="tok-n">OutOfStock</span><span class="tok-p">:</span>
<span class="tok-n">email</span><span class="tok-o">.</span><span class="tok-n">send_mail</span><span class="tok-p">(</span><span class="tok-s2">"[email protected]"</span><span class="tok-p">,</span> <span class="tok-sa">f</span><span class="tok-s2">"Out of stock for </span><span class="tok-si">{</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-si">}</span><span class="tok-s2">"</span><span class="tok-p">)</span>
<span class="tok-k">raise</span></code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>
Catching an exception and reraising it? It could be worse, but it’s
definitely making us unhappy. Why is it so hard to find a suitable home for
this code?</p>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_single_responsibility_principle"><a class="anchor" href="#_single_responsibility_principle"></a>Single Responsibility Principle</h3>
<div class="paragraph">
<p>
Really, this is a violation of the <em>single responsibility principle</em> (SRP).<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>
Our use case is allocation. Our endpoint, service function, and domain methods
are all called <span class="keep-together"><code>allocate</code></span>, not
<code>allocate_and_send_mail_if_out_of_stock</code>.</p>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">Tip</div>
</td>
<td class="content">
Rule of thumb: if you can’t describe what your function does without using
words like "then" or "and," you might be violating the SRP.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>One formulation of the SRP is that each class should have only a single reason
to change. When we switch from email to SMS, we shouldn’t have to update our
<code>allocate()</code> function, because that’s clearly a separate responsibility.</p>
</div>
<div class="paragraph">
<p>
To solve the problem, we’re going to split the orchestration
into separate steps so that the different concerns don’t get tangled up.<sup class="footnote">[<a id="_footnoteref_2" class="footnote" href="#_footnotedef_2" title="View footnote.">2</a>]</sup>
The domain model’s job is to know that we’re out of stock, but the responsibility
of sending an alert belongs elsewhere. We should be able to turn this feature
on or off, or to switch to SMS notifications instead, without needing to change
the rules of our domain model.</p>
</div>
<div class="paragraph">
<p>We’d also like to keep the service layer free of implementation details. We
want to apply the dependency inversion principle to notifications so that our
service layer depends on an abstraction, in the same way as we avoid depending
on the database by using a unit of work.</p>
</div>
</div>
<div class="sect2">
<h3 id="_all_aboard_the_message_bus"><a class="anchor" href="#_all_aboard_the_message_bus"></a>All Aboard the Message Bus!</h3>
<div class="paragraph">
<p>The patterns we’re going to introduce here are <em>Domain Events</em> and the <em>Message Bus</em>.
We can implement them in a few ways, so we’ll show a couple before settling on
the one we like most.</p>
</div>
<div class="sect3">
<h4 id="_the_model_records_events"><a class="anchor" href="#_the_model_records_events"></a>The Model Records Events</h4>
<div class="paragraph">
<p>
First, rather than being concerned about emails, our model will be in charge of
recording <em>events</em>—facts about things that have happened. We’ll use a message
bus to respond to events and invoke a new operation.</p>
</div>
</div>
<div class="sect3">
<h4 id="_events_are_simple_dataclasses"><a class="anchor" href="#_events_are_simple_dataclasses"></a>Events Are Simple Dataclasses</h4>
<div class="paragraph">
<p>
An <em>event</em> is a kind of <em>value object</em>. Events don’t have any behavior, because
they’re pure data structures. We always name events in the language of the
domain, and we think of them as part of our domain model.</p>
</div>
<div class="paragraph">
<p>We could store them in <em>model.py</em>, but we may as well keep them in their own file
(this might be a good time to consider refactoring out a directory called
<em>domain</em> so that we have <em>domain/model.py</em> and <em>domain/events.py</em>):</p>
</div>
<div id="events_dot_py" class="exampleblock nobreakinside less_space">
<div class="title">Event classes (src/allocation/domain/events.py)</div>
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code data-lang="python"><span></span><span class="tok-kn">from</span> <span class="tok-nn">dataclasses</span> <span class="tok-kn">import</span> <span class="tok-n">dataclass</span>
<span class="tok-k">class</span> <span class="tok-nc">Event</span><span class="tok-p">:</span> #<b class="conum">(1)</b>
<span class="tok-k">pass</span>
<span class="tok-nd">@dataclass</span>
<span class="tok-k">class</span> <span class="tok-nc">OutOfStock</span><span class="tok-p">(</span><span class="tok-n">Event</span><span class="tok-p">):</span> #<b class="conum">(2)</b>
<span class="tok-n">sku</span><span class="tok-p">:</span> <span class="tok-nb">str</span></code></pre>
</div>
</div>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Once we have a number of events, we’ll find it useful to have a parent
class that can store common attributes. It’s also useful for type
hints in our message bus, as you’ll see shortly.</p>
</li>
<li>
<p><code>dataclasses</code> are great for domain events too.</p>
</li>
</ol>
</div>
</div>
<div class="sect3">
<h4 id="_the_model_raises_events"><a class="anchor" href="#_the_model_raises_events"></a>The Model Raises Events</h4>
<div class="paragraph">
<p>
When our domain model records a fact that happened, we say it <em>raises</em> an event.</p>
</div>
<div class="paragraph">
<p>
Here’s what it will look like from the outside; if we ask <code>Product</code> to allocate
but it can’t, it should <em>raise</em> an event:</p>
</div>
<div id="test_raising_event" class="exampleblock">
<div class="title">Test our aggregate to raise events (tests/unit/test_product.py)</div>
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code data-lang="python"><span></span><span class="tok-k">def</span> <span class="tok-nf">test_records_out_of_stock_event_if_cannot_allocate</span><span class="tok-p">():</span>
<span class="tok-n">batch</span> <span class="tok-o">=</span> <span class="tok-n">Batch</span><span class="tok-p">(</span><span class="tok-s2">"batch1"</span><span class="tok-p">,</span> <span class="tok-s2">"SMALL-FORK"</span><span class="tok-p">,</span> <span class="tok-mi">10</span><span class="tok-p">,</span> <span class="tok-n">eta</span><span class="tok-o">=</span><span class="tok-n">today</span><span class="tok-p">)</span>
<span class="tok-n">product</span> <span class="tok-o">=</span> <span class="tok-n">Product</span><span class="tok-p">(</span><span class="tok-n">sku</span><span class="tok-o">=</span><span class="tok-s2">"SMALL-FORK"</span><span class="tok-p">,</span> <span class="tok-n">batches</span><span class="tok-o">=</span><span class="tok-p">[</span><span class="tok-n">batch</span><span class="tok-p">])</span>
<span class="tok-n">product</span><span class="tok-o">.</span><span class="tok-n">allocate</span><span class="tok-p">(</span><span class="tok-n">OrderLine</span><span class="tok-p">(</span><span class="tok-s2">"order1"</span><span class="tok-p">,</span> <span class="tok-s2">"SMALL-FORK"</span><span class="tok-p">,</span> <span class="tok-mi">10</span><span class="tok-p">))</span>
<span class="tok-n">allocation</span> <span class="tok-o">=</span> <span class="tok-n">product</span><span class="tok-o">.</span><span class="tok-n">allocate</span><span class="tok-p">(</span><span class="tok-n">OrderLine</span><span class="tok-p">(</span><span class="tok-s2">"order2"</span><span class="tok-p">,</span> <span class="tok-s2">"SMALL-FORK"</span><span class="tok-p">,</span> <span class="tok-mi">1</span><span class="tok-p">))</span>
<span class="tok-k">assert</span> <span class="tok-n">product</span><span class="tok-o">.</span><span class="tok-n">events</span><span class="tok-p">[</span><span class="tok-o">-</span><span class="tok-mi">1</span><span class="tok-p">]</span> <span class="tok-o">==</span> <span class="tok-n">events</span><span class="tok-o">.</span><span class="tok-n">OutOfStock</span><span class="tok-p">(</span><span class="tok-n">sku</span><span class="tok-o">=</span><span class="tok-s2">"SMALL-FORK"</span><span class="tok-p">)</span> #<b class="conum">(1)</b>
<span class="tok-k">assert</span> <span class="tok-n">allocation</span> <span class="tok-ow">is</span> <span class="tok-kc">None</span></code></pre>
</div>
</div>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Our aggregate will expose a new attribute called <code>.events</code> that will contain
a list of facts about what has happened, in the form of <code>Event</code> objects.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>Here’s what the model looks like on the inside:</p>
</div>
<div id="domain_event" class="exampleblock">
<div class="title">The model raises a domain event (src/allocation/domain/model.py)</div>
<div class="content">
<div class="listingblock non-head">
<div class="content">
<pre class="pygments highlight"><code data-lang="python"><span></span><span class="tok-k">class</span> <span class="tok-nc">Product</span><span class="tok-p">:</span>
<span class="tok-k">def</span> <span class="tok-fm">__init__</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">,</span> <span class="tok-n">sku</span><span class="tok-p">:</span> <span class="tok-nb">str</span><span class="tok-p">,</span> <span class="tok-n">batches</span><span class="tok-p">:</span> <span class="tok-n">List</span><span class="tok-p">[</span><span class="tok-n">Batch</span><span class="tok-p">],</span> <span class="tok-n">version_number</span><span class="tok-p">:</span> <span class="tok-nb">int</span> <span class="tok-o">=</span> <span class="tok-mi">0</span><span class="tok-p">):</span>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">sku</span> <span class="tok-o">=</span> <span class="tok-n">sku</span>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">batches</span> <span class="tok-o">=</span> <span class="tok-n">batches</span>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">version_number</span> <span class="tok-o">=</span> <span class="tok-n">version_number</span>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">events</span> <span class="tok-o">=</span> <span class="tok-p">[]</span> <span class="tok-c1"># type: List[events.Event] </span>#<b class="conum">(1)</b>
<span class="tok-k">def</span> <span class="tok-nf">allocate</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">,</span> <span class="tok-n">line</span><span class="tok-p">:</span> <span class="tok-n">OrderLine</span><span class="tok-p">)</span> <span class="tok-o">-></span> <span class="tok-nb">str</span><span class="tok-p">:</span>
<span class="tok-k">try</span><span class="tok-p">:</span>
<span class="tok-c1">#...</span>
<span class="tok-k">except</span> <span class="tok-ne">StopIteration</span><span class="tok-p">:</span>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">events</span><span class="tok-o">.</span><span class="tok-n">append</span><span class="tok-p">(</span><span class="tok-n">events</span><span class="tok-o">.</span><span class="tok-n">OutOfStock</span><span class="tok-p">(</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-p">))</span> #<b class="conum">(2)</b>
<span class="tok-c1"># raise OutOfStock(f"Out of stock for sku {line.sku}") </span>#<b class="conum">(3)</b>
<span class="tok-k">return</span> <span class="tok-kc">None</span></code></pre>
</div>
</div>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>Here’s our new <code>.events</code> attribute in use.</p>
</li>
<li>
<p>Rather than invoking some email-sending code directly, we record those
events at the place they occur, using only the language of the domain.</p>
</li>
<li>
<p>We’re also going to stop raising an exception for the out-of-stock
case. The event will do the job the exception was doing.</p>
</li>
</ol>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
We’re actually addressing a code smell we had until now, which is that we were
<a href="https://oreil.ly/IQB51">using
exceptions for control flow</a>. In general, if you’re implementing domain
events, don’t raise exceptions to describe the same domain concept.
As you’ll see later when we handle events in the Unit of Work pattern, it’s
confusing to have to reason about events and exceptions together.
</td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="_the_message_bus_maps_events_to_handlers"><a class="anchor" href="#_the_message_bus_maps_events_to_handlers"></a>The Message Bus Maps Events to Handlers</h4>
<div class="paragraph">
<p>
A message bus basically says, "When I see this event, I should invoke the following
handler function." In other words, it’s a simple publish-subscribe system.
Handlers are <em>subscribed</em> to receive events, which we publish to the bus. It
sounds harder than it is, and we usually implement it with a dict:</p>
</div>
<div id="messagebus" class="exampleblock">
<div class="title">Simple message bus (src/allocation/service_layer/messagebus.py)</div>
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code data-lang="python"><span></span><span class="tok-k">def</span> <span class="tok-nf">handle</span><span class="tok-p">(</span><span class="tok-n">event</span><span class="tok-p">:</span> <span class="tok-n">events</span><span class="tok-o">.</span><span class="tok-n">Event</span><span class="tok-p">):</span>
<span class="tok-k">for</span> <span class="tok-n">handler</span> <span class="tok-ow">in</span> <span class="tok-n">HANDLERS</span><span class="tok-p">[</span><span class="tok-nb">type</span><span class="tok-p">(</span><span class="tok-n">event</span><span class="tok-p">)]:</span>
<span class="tok-n">handler</span><span class="tok-p">(</span><span class="tok-n">event</span><span class="tok-p">)</span>
<span class="tok-k">def</span> <span class="tok-nf">send_out_of_stock_notification</span><span class="tok-p">(</span><span class="tok-n">event</span><span class="tok-p">:</span> <span class="tok-n">events</span><span class="tok-o">.</span><span class="tok-n">OutOfStock</span><span class="tok-p">):</span>
<span class="tok-n">email</span><span class="tok-o">.</span><span class="tok-n">send_mail</span><span class="tok-p">(</span>
<span class="tok-s2">"[email protected]"</span><span class="tok-p">,</span>
<span class="tok-sa">f</span><span class="tok-s2">"Out of stock for </span><span class="tok-si">{</span><span class="tok-n">event</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-si">}</span><span class="tok-s2">"</span><span class="tok-p">,</span>
<span class="tok-p">)</span>
<span class="tok-n">HANDLERS</span> <span class="tok-o">=</span> <span class="tok-p">{</span>
<span class="tok-n">events</span><span class="tok-o">.</span><span class="tok-n">OutOfStock</span><span class="tok-p">:</span> <span class="tok-p">[</span><span class="tok-n">send_out_of_stock_notification</span><span class="tok-p">],</span>
<span class="tok-p">}</span> <span class="tok-c1"># type: Dict[Type[events.Event], List[Callable]]</span></code></pre>
</div>
</div>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
Note that the message bus as implemented doesn’t give us concurrency because
only one handler will run at a time. Our objective isn’t to support
parallel threads but to separate tasks conceptually, and to keep each UoW
as small as possible. This helps us to understand the codebase because the
"recipe" for how to run each use case is written in a single place. See the
following sidebar.
</td>
</tr>
</table>
</div>
<div id="celery_sidebar" class="sidebarblock nobreakinside less_space">
<div class="content">
<div class="title">Is This Like Celery?</div>
<div class="paragraph">
<p>
<em>Celery</em> is a popular tool in the Python world for deferring self-contained
chunks of work to an asynchronous task queue. The message bus we’re
presenting here is very different, so the short answer to the above question is no; our message bus
has more in common with an Express.js app, a UI event loop, or an actor framework.</p>
</div>
<div class="paragraph">
<p>
If you do have a requirement for moving work off the main thread, you
can still use our event-based metaphors, but we suggest you
use <em>external events</em> for that. There’s more discussion in
<a href="/book/chapter_11_external_events.html#chapter_11_external_events_tradeoffs">[chapter_11_external_events_tradeoffs]</a>, but essentially, if you
implement a way of persisting events to a centralized store, you
can subscribe other containers or other microservices to them. Then
that same concept of using events to separate responsibilities
across units of work within a single process/service can be extended across
multiple processes—​which may be different containers within the same
service, or totally different microservices.</p>
</div>
<div class="paragraph">
<p>If you follow us in this approach, your API for distributing tasks
is your event <span class="keep-together">classes—</span>or a JSON representation of them. This allows
you a lot of flexibility in who you distribute tasks to; they need not
necessarily be Python services. Celery’s API for distributing tasks is
essentially "function name plus arguments," which is more restrictive,
and Python-only.</p>
</div>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_option_1_the_service_layer_takes_events_from_the_model_and_puts_them_on_the_message_bus"><a class="anchor" href="#_option_1_the_service_layer_takes_events_from_the_model_and_puts_them_on_the_message_bus"></a>Option 1: The Service Layer Takes Events from the Model and Puts Them on the Message Bus</h3>
<div class="paragraph">
<p>
Our domain model raises events, and our message bus will call the right
handlers whenever an event happens. Now all we need is to connect the two. We
need something to catch events from the model and pass them to the message
bus—​the <em>publishing</em> step.</p>
</div>
<div class="paragraph">
<p>The simplest way to do this is by adding some code into our service layer:</p>
</div>
<div id="service_talks_to_messagebus" class="exampleblock">
<div class="title">The service layer with an explicit message bus (src/allocation/service_layer/services.py)</div>
<div class="content">
<div class="listingblock non-head">
<div class="content">
<pre class="pygments highlight"><code data-lang="python"><span></span><span class="tok-kn">from</span> <span class="tok-nn">.</span> <span class="tok-kn">import</span> <span class="tok-n">messagebus</span>
<span class="tok-o">...</span>
<span class="tok-k">def</span> <span class="tok-nf">allocate</span><span class="tok-p">(</span>
<span class="tok-n">orderid</span><span class="tok-p">:</span> <span class="tok-nb">str</span><span class="tok-p">,</span> <span class="tok-n">sku</span><span class="tok-p">:</span> <span class="tok-nb">str</span><span class="tok-p">,</span> <span class="tok-n">qty</span><span class="tok-p">:</span> <span class="tok-nb">int</span><span class="tok-p">,</span>
<span class="tok-n">uow</span><span class="tok-p">:</span> <span class="tok-n">unit_of_work</span><span class="tok-o">.</span><span class="tok-n">AbstractUnitOfWork</span><span class="tok-p">,</span>
<span class="tok-p">)</span> <span class="tok-o">-></span> <span class="tok-nb">str</span><span class="tok-p">:</span>
<span class="tok-n">line</span> <span class="tok-o">=</span> <span class="tok-n">OrderLine</span><span class="tok-p">(</span><span class="tok-n">orderid</span><span class="tok-p">,</span> <span class="tok-n">sku</span><span class="tok-p">,</span> <span class="tok-n">qty</span><span class="tok-p">)</span>
<span class="tok-k">with</span> <span class="tok-n">uow</span><span class="tok-p">:</span>
<span class="tok-n">product</span> <span class="tok-o">=</span> <span class="tok-n">uow</span><span class="tok-o">.</span><span class="tok-n">products</span><span class="tok-o">.</span><span class="tok-n">get</span><span class="tok-p">(</span><span class="tok-n">sku</span><span class="tok-o">=</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-p">)</span>
<span class="tok-k">if</span> <span class="tok-n">product</span> <span class="tok-ow">is</span> <span class="tok-kc">None</span><span class="tok-p">:</span>
<span class="tok-k">raise</span> <span class="tok-n">InvalidSku</span><span class="tok-p">(</span><span class="tok-sa">f</span><span class="tok-s2">"Invalid sku </span><span class="tok-si">{</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-si">}</span><span class="tok-s2">"</span><span class="tok-p">)</span>
<span class="tok-k">try</span><span class="tok-p">:</span> #<b class="conum">(1)</b>
<span class="tok-n">batchref</span> <span class="tok-o">=</span> <span class="tok-n">product</span><span class="tok-o">.</span><span class="tok-n">allocate</span><span class="tok-p">(</span><span class="tok-n">line</span><span class="tok-p">)</span>
<span class="tok-n">uow</span><span class="tok-o">.</span><span class="tok-n">commit</span><span class="tok-p">()</span>
<span class="tok-k">return</span> <span class="tok-n">batchref</span>
<span class="tok-k">finally</span><span class="tok-p">:</span> #<b class="conum">(1)</b>
<span class="tok-n">messagebus</span><span class="tok-o">.</span><span class="tok-n">handle</span><span class="tok-p">(</span><span class="tok-n">product</span><span class="tok-o">.</span><span class="tok-n">events</span><span class="tok-p">)</span> #<b class="conum">(2)</b></code></pre>
</div>
</div>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>We keep the <code>try/finally</code> from our ugly earlier implementation (we haven’t
gotten rid of <em>all</em> exceptions yet, just <code>OutOfStock</code>).</p>
</li>
<li>
<p>But now, instead of depending directly on an email infrastructure,
the service layer is just in charge of passing events from the model
up to the message bus.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>That already avoids some of the ugliness that we had in our naive
implementation, and we have several systems that work like this one, in which the
service layer explicitly collects events from aggregates and passes them to
the message bus.</p>
</div>
</div>
<div class="sect2">
<h3 id="_option_2_the_service_layer_raises_its_own_events"><a class="anchor" href="#_option_2_the_service_layer_raises_its_own_events"></a>Option 2: The Service Layer Raises Its Own Events</h3>
<div class="paragraph">
<p>
Another variant on this that we’ve used is to have the service layer
in charge of creating and raising events directly, rather than having them
raised by the domain model:</p>
</div>
<div id="service_layer_raises_events" class="exampleblock">
<div class="title">Service layer calls messagebus.handle directly (src/allocation/service_layer/services.py)</div>
<div class="content">
<div class="listingblock skip">
<div class="content">
<pre class="pygments highlight"><code data-lang="python"><span></span><span class="tok-k">def</span> <span class="tok-nf">allocate</span><span class="tok-p">(</span>
<span class="tok-n">orderid</span><span class="tok-p">:</span> <span class="tok-nb">str</span><span class="tok-p">,</span> <span class="tok-n">sku</span><span class="tok-p">:</span> <span class="tok-nb">str</span><span class="tok-p">,</span> <span class="tok-n">qty</span><span class="tok-p">:</span> <span class="tok-nb">int</span><span class="tok-p">,</span>
<span class="tok-n">uow</span><span class="tok-p">:</span> <span class="tok-n">unit_of_work</span><span class="tok-o">.</span><span class="tok-n">AbstractUnitOfWork</span><span class="tok-p">,</span>
<span class="tok-p">)</span> <span class="tok-o">-></span> <span class="tok-nb">str</span><span class="tok-p">:</span>
<span class="tok-n">line</span> <span class="tok-o">=</span> <span class="tok-n">OrderLine</span><span class="tok-p">(</span><span class="tok-n">orderid</span><span class="tok-p">,</span> <span class="tok-n">sku</span><span class="tok-p">,</span> <span class="tok-n">qty</span><span class="tok-p">)</span>
<span class="tok-k">with</span> <span class="tok-n">uow</span><span class="tok-p">:</span>
<span class="tok-n">product</span> <span class="tok-o">=</span> <span class="tok-n">uow</span><span class="tok-o">.</span><span class="tok-n">products</span><span class="tok-o">.</span><span class="tok-n">get</span><span class="tok-p">(</span><span class="tok-n">sku</span><span class="tok-o">=</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-p">)</span>
<span class="tok-k">if</span> <span class="tok-n">product</span> <span class="tok-ow">is</span> <span class="tok-kc">None</span><span class="tok-p">:</span>
<span class="tok-k">raise</span> <span class="tok-n">InvalidSku</span><span class="tok-p">(</span><span class="tok-sa">f</span><span class="tok-s2">"Invalid sku </span><span class="tok-si">{</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-si">}</span><span class="tok-s2">"</span><span class="tok-p">)</span>
<span class="tok-n">batchref</span> <span class="tok-o">=</span> <span class="tok-n">product</span><span class="tok-o">.</span><span class="tok-n">allocate</span><span class="tok-p">(</span><span class="tok-n">line</span><span class="tok-p">)</span>
<span class="tok-n">uow</span><span class="tok-o">.</span><span class="tok-n">commit</span><span class="tok-p">()</span> #<b class="conum">(1)</b>
<span class="tok-k">if</span> <span class="tok-n">batchref</span> <span class="tok-ow">is</span> <span class="tok-kc">None</span><span class="tok-p">:</span>
<span class="tok-n">messagebus</span><span class="tok-o">.</span><span class="tok-n">handle</span><span class="tok-p">(</span><span class="tok-n">events</span><span class="tok-o">.</span><span class="tok-n">OutOfStock</span><span class="tok-p">(</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-p">))</span>
<span class="tok-k">return</span> <span class="tok-n">batchref</span></code></pre>
</div>
</div>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>As before, we commit even if we fail to allocate because the code is simpler this way
and it’s easier to reason about: we always commit unless something goes
wrong. Committing when we haven’t changed anything is safe and keeps the
code uncluttered.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>Again, we have applications in production that implement the pattern in this
way. What works for you will depend on the particular trade-offs you face, but
we’d like to show you what we think is the most elegant solution, in which we
put the unit of work in charge of collecting and raising events.</p>
</div>
</div>
<div class="sect2">
<h3 id="_option_3_the_uow_publishes_events_to_the_message_bus"><a class="anchor" href="#_option_3_the_uow_publishes_events_to_the_message_bus"></a>Option 3: The UoW Publishes Events to the Message Bus</h3>
<div class="paragraph">
<p>
The UoW already has a <code>try/finally</code>, and it knows about all the aggregates
currently in play because it provides access to the repository. So it’s
a good place to spot events and pass them to the message bus:</p>
</div>
<div id="uow_with_messagebus" class="exampleblock">
<div class="title">The UoW meets the message bus (src/allocation/service_layer/unit_of_work.py)</div>
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code data-lang="python"><span></span><span class="tok-k">class</span> <span class="tok-nc">AbstractUnitOfWork</span><span class="tok-p">(</span><span class="tok-n">abc</span><span class="tok-o">.</span><span class="tok-n">ABC</span><span class="tok-p">):</span>
<span class="tok-o">...</span>
<span class="tok-k">def</span> <span class="tok-nf">commit</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">):</span>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">_commit</span><span class="tok-p">()</span> #<b class="conum">(1)</b>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">publish_events</span><span class="tok-p">()</span> #<b class="conum">(2)</b>
<span class="tok-k">def</span> <span class="tok-nf">publish_events</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">):</span> #<b class="conum">(2)</b>
<span class="tok-k">for</span> <span class="tok-n">product</span> <span class="tok-ow">in</span> <span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">products</span><span class="tok-o">.</span><span class="tok-n">seen</span><span class="tok-p">:</span> #<b class="conum">(3)</b>
<span class="tok-k">while</span> <span class="tok-n">product</span><span class="tok-o">.</span><span class="tok-n">events</span><span class="tok-p">:</span>
<span class="tok-n">event</span> <span class="tok-o">=</span> <span class="tok-n">product</span><span class="tok-o">.</span><span class="tok-n">events</span><span class="tok-o">.</span><span class="tok-n">pop</span><span class="tok-p">(</span><span class="tok-mi">0</span><span class="tok-p">)</span>
<span class="tok-n">messagebus</span><span class="tok-o">.</span><span class="tok-n">handle</span><span class="tok-p">(</span><span class="tok-n">event</span><span class="tok-p">)</span>
<span class="tok-nd">@abc</span><span class="tok-o">.</span><span class="tok-n">abstractmethod</span>
<span class="tok-k">def</span> <span class="tok-nf">_commit</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">):</span>
<span class="tok-k">raise</span> <span class="tok-ne">NotImplementedError</span>
<span class="tok-o">...</span>
<span class="tok-k">class</span> <span class="tok-nc">SqlAlchemyUnitOfWork</span><span class="tok-p">(</span><span class="tok-n">AbstractUnitOfWork</span><span class="tok-p">):</span>
<span class="tok-o">...</span>
<span class="tok-k">def</span> <span class="tok-nf">_commit</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">):</span> #<b class="conum">(1)</b>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">session</span><span class="tok-o">.</span><span class="tok-n">commit</span><span class="tok-p">()</span></code></pre>
</div>
</div>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>We’ll change our commit method to require a private <code>._commit()</code>
method from subclasses.</p>
</li>
<li>
<p>After committing, we run through all the objects that our
repository has seen and pass their events to the message bus.</p>
</li>
<li>
<p>That relies on the repository keeping track of aggregates that have been loaded
using a new attribute, <code>.seen</code>, as you’ll see in the next listing.
</p>
</li>
</ol>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
Are you wondering what happens if one of the
handlers fails? We’ll discuss error handling in detail in <a href="/book/chapter_10_commands.html">[chapter_10_commands]</a>.
</td>
</tr>
</table>
</div>
<div id="repository_tracks_seen" class="exampleblock">
<div class="title">Repository tracks aggregates that pass through it (src/allocation/adapters/repository.py)</div>
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code data-lang="python"><span></span><span class="tok-k">class</span> <span class="tok-nc">AbstractRepository</span><span class="tok-p">(</span><span class="tok-n">abc</span><span class="tok-o">.</span><span class="tok-n">ABC</span><span class="tok-p">):</span>
<span class="tok-k">def</span> <span class="tok-fm">__init__</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">):</span>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">seen</span> <span class="tok-o">=</span> <span class="tok-nb">set</span><span class="tok-p">()</span> <span class="tok-c1"># type: Set[model.Product] </span>#<b class="conum">(1)</b>
<span class="tok-k">def</span> <span class="tok-nf">add</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">,</span> <span class="tok-n">product</span><span class="tok-p">:</span> <span class="tok-n">model</span><span class="tok-o">.</span><span class="tok-n">Product</span><span class="tok-p">):</span> #<b class="conum">(2)</b>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">_add</span><span class="tok-p">(</span><span class="tok-n">product</span><span class="tok-p">)</span>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">seen</span><span class="tok-o">.</span><span class="tok-n">add</span><span class="tok-p">(</span><span class="tok-n">product</span><span class="tok-p">)</span>
<span class="tok-k">def</span> <span class="tok-nf">get</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">,</span> <span class="tok-n">sku</span><span class="tok-p">)</span> <span class="tok-o">-></span> <span class="tok-n">model</span><span class="tok-o">.</span><span class="tok-n">Product</span><span class="tok-p">:</span> #<b class="conum">(3)</b>
<span class="tok-n">product</span> <span class="tok-o">=</span> <span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">_get</span><span class="tok-p">(</span><span class="tok-n">sku</span><span class="tok-p">)</span>
<span class="tok-k">if</span> <span class="tok-n">product</span><span class="tok-p">:</span>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">seen</span><span class="tok-o">.</span><span class="tok-n">add</span><span class="tok-p">(</span><span class="tok-n">product</span><span class="tok-p">)</span>
<span class="tok-k">return</span> <span class="tok-n">product</span>
<span class="tok-nd">@abc</span><span class="tok-o">.</span><span class="tok-n">abstractmethod</span>
<span class="tok-k">def</span> <span class="tok-nf">_add</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">,</span> <span class="tok-n">product</span><span class="tok-p">:</span> <span class="tok-n">model</span><span class="tok-o">.</span><span class="tok-n">Product</span><span class="tok-p">):</span> #<b class="conum">(2)</b>
<span class="tok-k">raise</span> <span class="tok-ne">NotImplementedError</span>
<span class="tok-nd">@abc</span><span class="tok-o">.</span><span class="tok-n">abstractmethod</span> #<b class="conum">(3)</b>
<span class="tok-k">def</span> <span class="tok-nf">_get</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">,</span> <span class="tok-n">sku</span><span class="tok-p">)</span> <span class="tok-o">-></span> <span class="tok-n">model</span><span class="tok-o">.</span><span class="tok-n">Product</span><span class="tok-p">:</span>
<span class="tok-k">raise</span> <span class="tok-ne">NotImplementedError</span>
<span class="tok-k">class</span> <span class="tok-nc">SqlAlchemyRepository</span><span class="tok-p">(</span><span class="tok-n">AbstractRepository</span><span class="tok-p">):</span>
<span class="tok-k">def</span> <span class="tok-fm">__init__</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">,</span> <span class="tok-n">session</span><span class="tok-p">):</span>
<span class="tok-nb">super</span><span class="tok-p">()</span><span class="tok-o">.</span><span class="tok-fm">__init__</span><span class="tok-p">()</span>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">session</span> <span class="tok-o">=</span> <span class="tok-n">session</span>
<span class="tok-k">def</span> <span class="tok-nf">_add</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">,</span> <span class="tok-n">product</span><span class="tok-p">):</span> #<b class="conum">(2)</b>
<span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">session</span><span class="tok-o">.</span><span class="tok-n">add</span><span class="tok-p">(</span><span class="tok-n">product</span><span class="tok-p">)</span>
<span class="tok-k">def</span> <span class="tok-nf">_get</span><span class="tok-p">(</span><span class="tok-bp">self</span><span class="tok-p">,</span> <span class="tok-n">sku</span><span class="tok-p">):</span> #<b class="conum">(3)</b>
<span class="tok-k">return</span> <span class="tok-bp">self</span><span class="tok-o">.</span><span class="tok-n">session</span><span class="tok-o">.</span><span class="tok-n">query</span><span class="tok-p">(</span><span class="tok-n">model</span><span class="tok-o">.</span><span class="tok-n">Product</span><span class="tok-p">)</span><span class="tok-o">.</span><span class="tok-n">filter_by</span><span class="tok-p">(</span><span class="tok-n">sku</span><span class="tok-o">=</span><span class="tok-n">sku</span><span class="tok-p">)</span><span class="tok-o">.</span><span class="tok-n">first</span><span class="tok-p">()</span></code></pre>
</div>
</div>
</div>
</div>
<div class="colist arabic">
<ol>
<li>
<p>For the UoW to be able to publish new events, it needs to be able to ask
the repository for which <code>Product</code> objects have been used during this session.
We use a <code>set</code> called <code>.seen</code> to store them. That means our implementations
need to call <code>super().__init__()</code>.
</p>
</li>
<li>
<p>The parent <code>add()</code> method adds things to <code>.seen</code>, and now requires subclasses
to implement <code>._add()</code>.</p>
</li>
<li>
<p>Similarly, <code>.get()</code> delegates to a <code>._get()</code> function, to be implemented by
subclasses, in order to capture objects seen.</p>
</li>
</ol>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
The use of <code><em>._underscorey()</em></code> methods and subclassing is definitely not
the only way you could implement these patterns. Have a go at the
<a href="#get_rid_of_commit">"Exercise for the Reader"</a> in this chapter and experiment
with some alternatives.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>After the UoW and repository collaborate in this way to automatically keep
track of live objects and process their events, the service layer can be
totally free of event-handling concerns:
</p>
</div>
<div id="services_clean" class="exampleblock">
<div class="title">Service layer is clean again (src/allocation/service_layer/services.py)</div>
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="pygments highlight"><code data-lang="python"><span></span><span class="tok-k">def</span> <span class="tok-nf">allocate</span><span class="tok-p">(</span>
<span class="tok-n">orderid</span><span class="tok-p">:</span> <span class="tok-nb">str</span><span class="tok-p">,</span> <span class="tok-n">sku</span><span class="tok-p">:</span> <span class="tok-nb">str</span><span class="tok-p">,</span> <span class="tok-n">qty</span><span class="tok-p">:</span> <span class="tok-nb">int</span><span class="tok-p">,</span>
<span class="tok-n">uow</span><span class="tok-p">:</span> <span class="tok-n">unit_of_work</span><span class="tok-o">.</span><span class="tok-n">AbstractUnitOfWork</span><span class="tok-p">,</span>
<span class="tok-p">)</span> <span class="tok-o">-></span> <span class="tok-nb">str</span><span class="tok-p">:</span>
<span class="tok-n">line</span> <span class="tok-o">=</span> <span class="tok-n">OrderLine</span><span class="tok-p">(</span><span class="tok-n">orderid</span><span class="tok-p">,</span> <span class="tok-n">sku</span><span class="tok-p">,</span> <span class="tok-n">qty</span><span class="tok-p">)</span>
<span class="tok-k">with</span> <span class="tok-n">uow</span><span class="tok-p">:</span>
<span class="tok-n">product</span> <span class="tok-o">=</span> <span class="tok-n">uow</span><span class="tok-o">.</span><span class="tok-n">products</span><span class="tok-o">.</span><span class="tok-n">get</span><span class="tok-p">(</span><span class="tok-n">sku</span><span class="tok-o">=</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-p">)</span>
<span class="tok-k">if</span> <span class="tok-n">product</span> <span class="tok-ow">is</span> <span class="tok-kc">None</span><span class="tok-p">:</span>
<span class="tok-k">raise</span> <span class="tok-n">InvalidSku</span><span class="tok-p">(</span><span class="tok-sa">f</span><span class="tok-s2">"Invalid sku </span><span class="tok-si">{</span><span class="tok-n">line</span><span class="tok-o">.</span><span class="tok-n">sku</span><span class="tok-si">}</span><span class="tok-s2">"</span><span class="tok-p">)</span>
<span class="tok-n">batchref</span> <span class="tok-o">=</span> <span class="tok-n">product</span><span class="tok-o">.</span><span class="tok-n">allocate</span><span class="tok-p">(</span><span class="tok-n">line</span><span class="tok-p">)</span>
<span class="tok-n">uow</span><span class="tok-o">.</span><span class="tok-n">commit</span><span class="tok-p">()</span>
<span class="tok-k">return</span> <span class="tok-n">batchref</span></code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>