-
Notifications
You must be signed in to change notification settings - Fork 64
/
original.html
331 lines (255 loc) · 24.2 KB
/
original.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
<html>
<head>
<title>Action-Domain-Response: A Tentative MVC Refinement</title>
</head>
<body>
<h1>Action-Domain-Response: A Tentative MVC Refinement</h1>
<p><em>(This post is an offering of a first draft, not a final version. I feel very much like I am slowly feeling my way toward something, rather than declaring a completed definition out-of-hand. It may end up being a dead end, or it may already exist in some form with which I am not familiar. Even so, I want to present it for thoughtful, considerate commentary.</em></p>
<p>The term MVC has experienced some <a href="http://martinfowler.com/bliki/SemanticDiffusion.html">semantic diffusion</a> from its original meaning, especially in a web context. Because of this diffusion, the <em>Action-Domain-Response</em> pattern description is intended as a web-specific refinement of the MVC pattern. </p>
<p>I think ADR more closely fits what we actually do in web development on a daily basis. For example, this pattern is partly revealed by how we generally do web routing and dispatch. We generally route and dispatch <em>not</em> to a controller class per se, but to a particular action method within a controller class. </p>
<p>It is also partly revealed by the fact that we commonly think of the <em>template</em> as the <em>View</em>, when in a web context it may be more accurate to say that the <em>HTTP response</em> is the <em>View</em>. As such, I think ADR may represent a better separation of concerns than MVC does in a web context.</p>
<h1>
<a name="user-content-action-domain-response" class="anchor" href="#action-domain-response"><span class="octicon octicon-link"></span></a>Action-Domain-Response</h1>
<p>Organizes a single interaction between a web client and a web application into three distinct roles.</p>
<pre><code> Action ---> Response
||
Domain
</code></pre>
<h2>
<a name="user-content-terms" class="anchor" href="#terms"><span class="octicon octicon-link"></span></a>Terms</h2>
<p><em>Action</em> (taken from <code><form action="..."></code>) is the logic that connects the <em>Domain</em> and <em>Response</em>.</p>
<p><em>Domain</em> is the domain logic. It manipulates the domain, session, application, and environment data, modifying state and persistence as needed.</p>
<p><em>Response</em> is logic to build an HTTP response or response description. It deals with body content, templates and views, headers and cookies, status codes, and so on.</p>
<h2>
<a name="user-content-narrative" class="anchor" href="#narrative"><span class="octicon octicon-link"></span></a>Narrative</h2>
<h3>
<a name="user-content-operational-description" class="anchor" href="#operational-description"><span class="octicon octicon-link"></span></a>Operational Description</h3>
<ol>
<li><p>The web handler dispatches the incoming request to an <em>Action</em>.</p></li>
<li><p>The <em>Action</em> interacts with the <em>Domain</em> and gets back <em>Domain</em> data.</p></li>
<li><p>The <em>Action</em> feeds the <em>Domain</em> data to the <em>Response</em> logic, and then gives control to the <em>Reponse</em>.</p></li>
<li><p>The web handler sends the response back to the client.</p></li>
</ol><h3>
<a name="user-content-compare-and-contrast-with-mvc" class="anchor" href="#compare-and-contrast-with-mvc"><span class="octicon octicon-link"></span></a>Compare and Contrast With MVC</h3>
<p>The dominant pattern describing web interactions is <em>Model-View-Controller</em>. Is <em>Action-Domain-Response</em> really just <em>Model-View-Controller</em> in drag? We can see that the ADR terms map very neatly to MVC terms:</p>
<pre><code>Model <--> Domain
View <--> Response
Controller <--> Action
</code></pre>
<p>The two seem very similar. How are they different?</p>
<p>Typically, the <em>View</em> does not consider response headers, only the response body. Also typically, a <em>Controller</em> has more than one action method in it, and managing the different preconditions for these action methods carries its own overhead.</p>
<h4>
<a name="user-content-model-vs-domain" class="anchor" href="#model-vs-domain"><span class="octicon octicon-link"></span></a><em>Model</em> vs <em>Domain</em>
</h4>
<p>I can think of no significant differences here, other than that the <em>Response</em> does not interact with the <em>Domain</em> in meaningful ways. The <em>Response</em> might use <em>Domain</em> objects like entities and collections, but only for presentation purposes; it does not modify the <em>Domain</em> or feed information back to the <em>Domain</em> as described under MVC.</p>
<h4>
<a name="user-content-controller-vs-action" class="anchor" href="#controller-vs-action"><span class="octicon octicon-link"></span></a><em>Controller</em> vs <em>Action</em>
</h4>
<p>In common usage, most <em>Controller</em> classes in an MVC architecture contain several methods corresponding to different actions. Because these differing action methods reside in the same <em>Controller</em>, the <em>Controller</em> ends up needing additional wrapper logic to deal with each method properly, such as pre- and post-action hooks. A notable exception here is in micro-frameworks, where each <em>Controller</em> is an individual closure or invokable object, mapping more closely to a single <em>Action</em> (cf. <a href="http://www.slimframework.com">Slim</a>).</p>
<p>In an ADR architecture, a single <em>Action</em> is the main purpose of a class or closure. Multiple <em>Action</em>s would be represented by multiple classes.</p>
<p>The <em>Action</em> interacts with the <em>Domain</em> in the same way a <em>Controller</em> interacts with a <em>Model</em>, but does <em>not</em> interact with a <em>View</em> or template system. It sets data on the <em>Response</em> and hands over control to it.</p>
<h4>
<a name="user-content-view-vs-response" class="anchor" href="#view-vs-response"><span class="octicon octicon-link"></span></a><em>View</em> vs <em>Response</em>
</h4>
<p>In an MVC architecture, a <em>Controller</em> method will usually generate body content via a <em>View</em> (e.g. a <em>Template View</em> or a <em>Two Step View</em>). The <em>Controller</em> then injects the generated body content into the response. The <em>Controller</em> action method will manipulate the response directly to set any needed headers.</p>
<p>Some <em>Controller</em> action methods may present alternative content-types for the same domain data. Because these alternatives may not be consistent over all the different methods, this leads to the presentation logic being somewhat different in each method, each with its own preconditions.</p>
<p>In an ADR architecture, each <em>Action</em> has a separate corresponding <em>Response</em>. When the <em>Action</em> is done with the <em>Domain</em>, it delivers any needed <em>Domain</em> data to the <em>Response</em> and then hands off to the <em>Response</em> completely. The <em>Response</em> is entirely in charge of setting headers, picking content types, rendering a <em>View</em> for the body content, and so on.</p>
<h3>
<a name="user-content-other-mvc-pattern-alternatives" class="anchor" href="#other-mvc-pattern-alternatives"><span class="octicon octicon-link"></span></a>Other MVC Pattern Alternatives</h3>
<p>How does ADR compare to other MVC alternatives?</p>
<h4>
<a name="user-content-dci-data-context-interaction" class="anchor" href="#dci-data-context-interaction"><span class="octicon octicon-link"></span></a>DCI (Data-Context-Interaction)</h4>
<p><a href="https://en.wikipedia.org/wiki/Data,_Context,_and_Interaction">DCI is described as a complement to MVC</a>, not a replacement for MVC. I think it is fair to call it a complement to ADR as well.</p>
<h4>
<a name="user-content-mvp-model-view-presenter" class="anchor" href="#mvp-model-view-presenter"><span class="octicon octicon-link"></span></a>MVP (Model-View-Presenter)</h4>
<p><a href="http://www.martinfowler.com/eaaDev/ModelViewPresenter.html">MVP has been retired</a> in favor of <a href="http://www.martinfowler.com/eaaDev/SupervisingPresenter.html"><em>Supervising Controller</em></a> and <a href="http://www.martinfowler.com/eaaDev/PassiveScreen.html"><em>Passive View</em></a>, neither of which seem to fit the ADR description very closely.</p>
<h4>
<a name="user-content-pac-presentation-abstraction-control" class="anchor" href="#pac-presentation-abstraction-control"><span class="octicon octicon-link"></span></a>PAC (Presentation-Abstraction-Control)</h4>
<p><a href="https://en.wikipedia.org/wiki/Presentation-abstraction-control">From Wikipedia</a>:</p>
<blockquote>
<p>PAC is used as a hierarchical structure of agents, each consisting of a triad of presentation, abstraction and control parts. The agents (or triads) communicate with each other only through the control part of each triad. It also differs from MVC in that within each triad, it completely insulates the presentation (view in MVC) and the abstraction (model in MVC). This provides the option to separately multithread the model and view which can give the user experience of very short program start times, as the user interface (presentation) can be shown before the abstraction has fully initialized.</p>
</blockquote>
<p>This does not seem to fit the description of ADR very well.</p>
<h4>
<a name="user-content-model-view-viewmodel" class="anchor" href="#model-view-viewmodel"><span class="octicon octicon-link"></span></a>Model-View-ViewModel</h4>
<p><a href="https://en.wikipedia.org/wiki/Model_View_ViewModel">MVVM</a> seems more suited to desktop applications than web interactions. (Recall that ADR is specifically intended for web interactions.)</p>
<h3>
<a name="user-content-examples-of-mvc-vs-adr" class="anchor" href="#examples-of-mvc-vs-adr"><span class="octicon octicon-link"></span></a>Examples of MVC vs ADR</h3>
<h4>
<a name="user-content-mvc-starting-point" class="anchor" href="#mvc-starting-point"><span class="octicon octicon-link"></span></a>MVC Starting Point</h4>
<p>An MVC directory structure for a naive blogging system might look like the following. Note that the <code>index</code> and <code>read</code> views present an alternative JSON type, and the <code>comments</code> template is a "partial" that also presents an alternative JSON type.</p>
<pre><code>controllers/
BlogController.php # index(), create(), read(), update(), delete()
models/
BlogModel.php
views/
blog/
index.html.php
index.json.php
create.html.php
read.html.php
read.json.php
update.html.php
delete.html.php
_comments.html.php
_comments.json.php
</code></pre>
<p>Here's another type of MVC directory structure:</p>
<pre><code>Blog/
BlogController.php # index(), create(), read(), update(), delete()
BlogModel.php
views/
index.html.php
index.json.php
create.html.php
read.html.php
read.json.php
update.html.php
delete.html.php
_comments.html.php
_comments.json.php
</code></pre>
<p>A typical <em>Controller</em> class in MVC might looks something like the following. Note that there are multiple actions within the <em>Controller</em> class, and that the action method deals with the response headers.</p>
<div class="highlight highlight-php"><pre><span class="o"><?</span><span class="nx">php</span>
<span class="k">use</span> <span class="nx">FrameworkController</span><span class="p">;</span>
<span class="k">class</span> <span class="nc">BlogController</span> <span class="k">extends</span> <span class="nx">Controller</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">create</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// is this a POST request?</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">request</span><span class="o">-></span><span class="na">isPost</span><span class="p">())</span> <span class="p">{</span>
<span class="c1">// retain incoming data</span>
<span class="nv">$data</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">request</span><span class="o">-></span><span class="na">getPost</span><span class="p">(</span><span class="s1">'blog'</span><span class="p">);</span>
<span class="c1">// create a blog post instance</span>
<span class="nv">$blog</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">blog_model</span><span class="o">-></span><span class="na">newInstance</span><span class="p">(</span><span class="nv">$data</span><span class="p">);</span>
<span class="c1">// is the new instance valid?</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$blog</span><span class="o">-></span><span class="na">isValid</span><span class="p">())</span> <span class="p">{</span>
<span class="c1">// yes, save and redirect to editing</span>
<span class="nv">$blog</span><span class="o">-></span><span class="na">save</span><span class="p">();</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">response</span><span class="o">-></span><span class="na">redirect</span><span class="p">(</span><span class="s1">'/blog/edit/{$blog->id}'</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// no, show the "create" form with the blog instance</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">response</span><span class="o">-></span><span class="na">setContent</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">view</span><span class="o">-></span><span class="na">render</span><span class="p">(</span>
<span class="s1">'create.html.php'</span><span class="p">,</span>
<span class="k">array</span><span class="p">(</span><span class="s1">'blog'</span> <span class="o">=></span> <span class="nv">$blog</span><span class="p">),</span>
<span class="p">));</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// not a POST request, show the "create" form with defaults</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">response</span><span class="o">-></span><span class="na">setContent</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">view</span><span class="o">-></span><span class="na">render</span><span class="p">(</span>
<span class="s1">'create.html.php'</span><span class="p">,</span>
<span class="k">array</span><span class="p">(</span><span class="s1">'blog'</span> <span class="o">=></span> <span class="nv">$this</span><span class="o">-></span><span class="na">blog_model</span><span class="o">-></span><span class="na">getDefault</span><span class="p">())</span>
<span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">index</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">read</span><span class="p">(</span><span class="nv">$id</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">update</span><span class="p">(</span><span class="nv">$id</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">delete</span><span class="p">(</span><span class="nv">$id</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cp">?></span><span class="x"></span>
</pre></div>
<p>The <code>create()</code> logic could be reduced somewhat by moving even more of the model interactions into a <em>Service Layer</em>, but the point remains that the <em>Controller</em> typically sets the response headers and content.</p>
<h3>
<a name="user-content-adr-comparison" class="anchor" href="#adr-comparison"><span class="octicon octicon-link"></span></a>ADR Comparison</h3>
<p>In comparison, an ADR directory structure might instead look like this. Note how each <em>Action</em> has a corresponding <em>Response</em>.</p>
<pre><code>Blog/
Action/
BlogIndexAction.php
BlogCreateAction.php
BlogReadAction.php
BlogUpdateAction.php
BlogDeleteAction.php
Domain/
# Model, Gateway, Mapper, Entity, Collection, Service, etc.
Response/
BlogIndexResponse.php
BlogCreateResponse.php
BlogReadResponse.php
BlogUpdateResponse.php
BlogDeleteResponse.php
html/
index.html.php
create.html.php
read.html.php
update.html.php
delete.html.php
_comments.html.php
json/
index.json.php
read.json.php
_comments.json.php
</code></pre>
<p>The <em>Action</em> and <em>Response</em> class pair corresponding to the above <em>Controller</em> <code>create()</code> example might look like this:</p>
<div class="highlight highlight-php"><pre><span class="o"><?</span><span class="nx">php</span>
<span class="k">use</span> <span class="nx">FrameworkAction</span><span class="p">;</span>
<span class="k">class</span> <span class="nc">BlogCreateAction</span> <span class="k">extends</span> <span class="nx">Action</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">__invoke</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// is this a POST request?</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">request</span><span class="o">-></span><span class="na">isPost</span><span class="p">())</span> <span class="p">{</span>
<span class="c1">// yes, retain incoming data</span>
<span class="nv">$data</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">request</span><span class="o">-></span><span class="na">getPost</span><span class="p">(</span><span class="s1">'blog'</span><span class="p">);</span>
<span class="c1">// create a blog post instance</span>
<span class="nv">$blog</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">blog_model</span><span class="o">-></span><span class="na">newInstance</span><span class="p">(</span><span class="nv">$data</span><span class="p">);</span>
<span class="c1">// is the new instance valid?</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$blog</span><span class="o">-></span><span class="na">isValid</span><span class="p">())</span> <span class="p">{</span>
<span class="nv">$blog</span><span class="o">-></span><span class="na">save</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// not a POST request, use default values</span>
<span class="nv">$blog</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">blog_model</span><span class="o">-></span><span class="na">getDefault</span><span class="p">();</span>
<span class="p">}</span>
<span class="c1">// set data into the response</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">response</span><span class="o">-></span><span class="na">setData</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="s1">'blog'</span> <span class="o">=></span> <span class="nv">$blog</span><span class="p">));</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">response</span><span class="o">-></span><span class="na">__invoke</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cp">?></span><span class="x"></span>
</pre></div>
<div class="highlight highlight-php"><pre><span class="o"><?</span><span class="nx">php</span>
<span class="k">use</span> <span class="nx">FrameworkResponse</span><span class="p">;</span>
<span class="k">class</span> <span class="nc">BlogCreateResponse</span> <span class="k">extends</span> <span class="nx">Response</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">__invoke</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// is there an ID on the blog instance?</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">data</span><span class="o">-></span><span class="na">blog</span><span class="o">-></span><span class="na">id</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// yes, which means it was saved already.</span>
<span class="c1">// redirect to editing.</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">setRedirect</span><span class="p">(</span><span class="s1">'/blog/edit/{$blog->id}'</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// no, which means it has not been saved yet.</span>
<span class="c1">// show the creation form with the current response data.</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">setContent</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">view</span><span class="o">-></span><span class="na">render</span><span class="p">(</span>
<span class="s1">'create.html.php'</span><span class="p">,</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">data</span>
<span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cp">?></span><span class="x"></span>
</pre></div>
<p>Again, we can see numerous refactoring opportunities here, especially in the domain model work. The point is that the <em>Action</em> does not perform any <em>Response</em> work at all. That work is handled entirely by the <em>Response</em> logic.</p>
<h2>
<a name="user-content-benefits-and-drawbacks" class="anchor" href="#benefits-and-drawbacks"><span class="octicon octicon-link"></span></a>Benefits and Drawbacks</h2>
<p>One benefit overall is that the pattern more closely describes the day-to-day work of web interactions. A request comes in and gets dispatched to an action; the action interacts with the domain, and then builds a response. The response work, including both headers and content, is cleanly separated from the action work.</p>
<p>One drawback is that we end up with more classes in the application. Not only does each <em>Action</em> go in its own class, each <em>Response</em> also goes in its own class.</p>
<p>This drawback may not be so terrible in the longer term. Invididual classes may lead cleaner or less-deep inheritance hierachies. It may also lead to better testability of the <em>Action</em> separate from the <em>Response</em>. These will play themselves out differently in different systems.</p>
</body>
</html>