Skip to content

Commit 021b1dd

Browse files
committed
start fixing some links and formatting
1 parent 0685525 commit 021b1dd

4 files changed

Lines changed: 42 additions & 47 deletions

_posts/2017-09-07-introducing-command-handler.md

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ tags:
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
1514
maintainable, rich, software systems that solve customer's problems. The book is
1615
560 pages of dense insight, so you'll pardon me if my summary elides some
1716
details, 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
3736
solve these problems take up half of the DDD book, but it can be be difficult to
3837
understand 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
4544
day-to-day, but the concepts are applicable to any OO language, and can be
4645
massaged to work perfectly in a functional context. There might be a lot more
4746
layering and abstraction than you're used to, especially if you're coming from a
4847
Django 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

5250
The system we're going to build is an issue management system, for use by a
5351
helpdesk. We're going to be replacing an existing system, which consists of an
@@ -83,7 +81,7 @@ issue.
8381

8482
Okay, before we get to the code, let's talk about architecture. The architecture
8583
of 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
8785
constraints [https://en.wikipedia.org/wiki/Non-functional_requirement]. For our
8886
architecture, we're going to try and stick with three principles:
8987

@@ -212,7 +210,7 @@ skip ahead a little to a new command handler:
212210
class 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

223221
This handler violates our glue-code principle because it encodes a business
224222
rule: "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
226224
method 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
228226
magic method, but a function is perfectly valid as a handler, too. The major
229227
reason to prefer a class is that it can make dependency management a little
230228
easier, 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

259257
Let's take a look at the complete system, I'm concatenating all the files into a
260258
single 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
263261
world, I would probably use a single python package for the whole app, but in
264262
other 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+
298296
class ReportIssueHandler:
299297

300298
def __init__(self, issue_log):

_posts/2017-09-08-repository-and-unit-of-work-pattern-in-python.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ tags:
88
- architecture
99
---
1010

11-
In the previous part [https://io.made.com/blog/introducing-command-handler/] of
12-
this series we built a toy system that could add a new Issue to an IssueLog, but
11+
In the previous part
12+
([Introducing Command Handler]({% post_url 2017-09-07-introducing-command-handler %}))
13+
of this series we built a toy system that could add a new Issue to an IssueLog, but
1314
had no real behaviour of its own, and would lose its data every time the
1415
application restarted. We're going to extend it a little by introducing some
1516
patterns for persistent data access, and talk a little more about the ideas

_posts/2017-09-13-commands-and-queries-handlers-and-views.md

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ tags:
88
- architecture
99
---
1010

11-
In the first and second parts of this series I introduced the Command-Handler
12-
[https://io.made.com/blog/introducing-command-handler/] and Unit of Work and
13-
Repository
14-
[https://io.made.com/blog/repository-and-unit-of-work-pattern-in-python/]
15-
patterns. I was intending to write about Message Buses, and some more stuff
11+
In the first and second parts of this series I introduced the
12+
[Command-Handler]({% post_url 2017-09-07-introducing-command-handler %})
13+
and
14+
[Unit of Work and Repository patterns]({% post_url 2017-09-08-repository-and-unit-of-work-pattern-in-python %}).
15+
I was intending to write about Message Buses, and some more stuff
1616
about domain modelling, but I need to quickly skim over this first.
1717

1818
If you've just started reading the Message Buses piece, and you're here to learn
@@ -21,11 +21,10 @@ after a bunch of stuff about ORMs, CQRS, and some casual trolling of junior
2121
programmers.
2222

2323
What is CQS ?
24-
The Command Query Separation
25-
[https://martinfowler.com/bliki/CommandQuerySeparation.html] principle was
26-
first described by Bertrand Meyer in the late Eighties. Per wikipedia
27-
[https://en.wikipedia.org/wiki/Command%E2%80%93query_separation], the principle
28-
states:
24+
The [Command Query Separation](https://martinfowler.com/bliki/CommandQuerySeparation.html) principle was
25+
first described by Bertrand Meyer in the late Eighties. Per
26+
[wikipedia](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation),
27+
the principle states:
2928

3029
every method should either be a command that performs an action, or a query that
3130
returns data to the caller, but not both. In other words, "Asking a question
@@ -41,14 +40,14 @@ class LightSwitch:
4140
def toggle_light(self):
4241
self.light_is_on = not self.light_is_on
4342
return self.light_is_on
44-
43+
4544
@property
4645
def is_on(self):
4746
return self.light_is_on
4847

4948

5049
In this class, the is_on method is referentially transparent - I can replace it
51-
with the value True or False without any loss of functionality, but the method
50+
with the value True or False without any loss of functionality, but the method
5251
toggle_light is side-effectual: replacing its calls with a static value would
5352
break the contracts of the system. To comply with the Command-Query separation
5453
principle, we should not return a value from our toggle_light method.
@@ -72,8 +71,8 @@ data back out of our model? What is the equivalent port for queries?
7271
The answer is "it depends". The lowest-cost option is just to re-use your
7372
repositories in your UI entrypoints.
7473

75-
@app.route("/issues")
76-
def list_issues():
74+
@app.route("/issues")
75+
def list_issues():
7776
with unit_of_work_manager.start() as unit_of_work:
7877
open_issues = unit_of_work.issues.find_by_status('open')
7978
return json.dumps(open_issues)
@@ -99,10 +98,10 @@ and an email notification.
9998
@app.route('/issues/<issue_id>', methods=['DELETE'])
10099
def delete_issue(issue_id):
101100
logging.info("Handling DELETE of issue "+str(issue_id))
102-
101+
103102
with unit_of_work_manager.start() as uow:
104103
issue = uow.issues[issue_id]
105-
104+
106105
if issue is None:
107106
logging.warn("Issue not found")
108107
flask.abort(404)
@@ -113,7 +112,7 @@ def delete_issue(issue_id):
113112
smtp.send_notification(Issue.Deleted, issue_id)
114113
except:
115114
logging.error(
116-
"Failed to send email notification for deleted issue "
115+
"Failed to send email notification for deleted issue "
117116
+ str(issue_id), exn_info=True)
118117
else:
119118
logging.info("Issue already deleted. NOOP")
@@ -138,11 +137,11 @@ class OpenIssuesList:
138137
def fetch(self):
139138
with self.sessionmaker() as session:
140139
result = session.execute(
141-
'SELECT reporter_name, timestamp, title
140+
'SELECT reporter_name, timestamp, title
142141
FROM issues WHERE state="open"')
143142
return [dict(r) for r in result.fetchall()]
144-
145-
143+
144+
146145
@api.route('/issues/')
147146
def list_issues():
148147
view_builder = OpenIssuesList(session_maker)
@@ -194,7 +193,7 @@ for assignee in task.assignees:
194193
assignee.manager.notifications.add(notification)
195194
assignee.notifications.add(notification)
196195
assignee.queues.inbox.add(task)
197-
196+
198197

199198

200199
ORMs make it very easy to "dot" through the object model this way, and pretend
@@ -283,7 +282,7 @@ def report_issue(self):
283282
# they can be shared amongst several systems and are easy
284283
# to generate.
285284
issue_id = uuid.uuid4()
286-
285+
287286
cmd = ReportIssueCommand(issue_id, **request.get_json())
288287
handler.handle(cmd)
289288
return "", 201, { 'Location': '/issues/' + str(issue_id) }

_posts/2017-09-19-why-use-domain-events.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@ Nota bene: this instalment in the Ports and Adapters with Command Handlers
1212
series is code-heavy, and isn't going to make much sense unless you've read the
1313
previous parts:
1414

15-
* Introducing Command Handler
16-
[https://io.made.com/blog/introducing-command-handler/]
17-
* Repositories and Units of Work
18-
[https://io.made.com/blog/repository-and-unit-of-work-pattern-in-python/]
19-
* Commands and Queries, Handlers and Views
20-
[https://io.made.com/blog/commands-and-queries-handlers-and-views/]
15+
* [Introducing Command Handler]({% post_url 2017-09-07-introducing-command-handler %})
16+
* [Repositories and Units of Work]({% post_url 2017-09-08-repository-and-unit-of-work-pattern-in-python %})
17+
* [Commands and Queries, Handlers and Views]({% post_url 2017-09-13-commands-and-queries-handlers-and-views %})
2118

2219
Okay, so we have a basic skeleton for an application and we can add new issues
2320
into the database, then fetch them from a Flask API. So far, though, we don't

0 commit comments

Comments
 (0)