88 - architecture
99---
1010
11- The term DDD comes from the book by Eric Evans: "Domain-Driven Design: Tackling
12- Complexity in the Heart of Software
13- [ https://www.amazon.co.uk/Domain-driven-Design-Tackling-Complexity-Software/dp/0321125215 ]
14- ". In his book he describes a set of practices that aim to help us build
11+ The term DDD comes from the book by Eric Evans: [ "Domain-Driven Design: Tackling
12+ Complexity in the Heart of Software"] ( [https://www.amazon.co.uk/Domain-driven-Design-Tackling-Complexity-Software/dp/0321125215 ) .
13+ In his book he describes a set of practices that aim to help us build
1514maintainable, rich, software systems that solve customer's problems. The book is
1615560 pages of dense insight, so you'll pardon me if my summary elides some
1716details, but in brief he suggests:
@@ -37,17 +36,16 @@ they are caused by a lack of organisation in the codebase. In fact, the tools to
3736solve these problems take up half of the DDD book, but it can be be difficult to
3837understand how to use them together in the context of a complete system.
3938
40- I want to use this series to introduce an architectural style called Ports and
41- Adapters [ http://wiki.c2.com/?PortsAndAdaptersArchitecture ] , and a design
42- pattern named Command Handler
43- [ https://matthiasnoback.nl/2015/01/responsibilities-of-the-command-bus/ ] . I'll
44- be explaining the patterns in Python because that's the language that I use
39+ I want to use this series to introduce an architectural style called
40+ [ Ports and Adapters ] ( http://wiki.c2.com/?PortsAndAdaptersArchitecture ) ,
41+ and a design pattern named
42+ [ Command Handler ] ( https://matthiasnoback.nl/2015/01/responsibilities-of-the-command-bus/ ) .
43+ I'll be explaining the patterns in Python because that's the language that I use
4544day-to-day, but the concepts are applicable to any OO language, and can be
4645massaged to work perfectly in a functional context. There might be a lot more
4746layering and abstraction than you're used to, especially if you're coming from a
4847Django background or similar, but please bear with me. In exchange for a more
49- complex system at the outset, we can avoid much of our accidental complexity
50- [ http://wiki.c2.com/?AccidentalComplexity ] later.
48+ complex system at the outset, we can avoid much of our [ accidental complexity] ( http://wiki.c2.com/?AccidentalComplexity ) later.
5149
5250The system we're going to build is an issue management system, for use by a
5351helpdesk. We're going to be replacing an existing system, which consists of an
8381
8482Okay, before we get to the code, let's talk about architecture. The architecture
8583of a software system is the overall structure - the choice of language,
86- technology, and design patterns that organise the code and satisfy our
84+ technology, and design patterns that organise the code and satisfy our
8785constraints [ https://en.wikipedia.org/wiki/Non-functional_requirement ] . For our
8886architecture, we're going to try and stick with three principles:
8987
@@ -212,7 +210,7 @@ skip ahead a little to a new command handler:
212210class MarkIssueAsResolvedHandler:
213211 def __ init__ (self, issue_log):
214212 self.issue_log = issue_log
215-
213+
216214 def __call__(self, cmd):
217215 issue = self.issue_log.get(cmd.issue_id)
218216 # the following line encodes a business rule
@@ -222,9 +220,9 @@ class MarkIssueAsResolvedHandler:
222220
223221This handler violates our glue-code principle because it encodes a business
224222rule: "If an issue is already resolved, then it can't be resolved a second
225- time". This rule belongs in our domain model, probably in the mark_as_resolved
223+ time". This rule belongs in our domain model, probably in the mark_as_resolved
226224method of our Issue object.
227- I tend to use classes for my command handlers, and to invoke them with the call
225+ I tend to use classes for my command handlers, and to invoke them with the call
228226magic method, but a function is perfectly valid as a handler, too. The major
229227reason to prefer a class is that it can make dependency management a little
230228easier, but the two approaches are completely equivalent. For example, we could
@@ -258,7 +256,7 @@ However you structure them, the important ideas of commands and handlers are:
258256
259257Let's take a look at the complete system, I'm concatenating all the files into a
260258single code listing for each of grokking, but in the git repository
261- [ https://github.com/bobthemighty/blog-code-samples/tree/master/ports-and-adapters/01 ]
259+ [ https://github.com/bobthemighty/blog-code-samples/tree/master/ports-and-adapters/01 ]
262260 I'm splitting the layers of the system into separate packages. In the real
263261world, I would probably use a single python package for the whole app, but in
264262other languages - Java, C#, C++ - I would usually have a single binary for each
@@ -294,7 +292,7 @@ class ReportIssueCommand(NamedTuple):
294292
295293
296294# Service Layer
297-
295+
298296class ReportIssueHandler:
299297
300298 def __init__(self, issue_log):
0 commit comments