-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdomain.html
More file actions
223 lines (199 loc) · 8.02 KB
/
domain.html
File metadata and controls
223 lines (199 loc) · 8.02 KB
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
<html>
<head>
<title>Domain Design</title>
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
</head>
<body>
<header>
<div class="container">
<div class="row">
<div class="prev col-md-3">
<label for="">Prev</label>
<a href="/intro.html">Introduction</a> </div>
<div class="current col-md-6">
<h3 class="title">
2. Domain Design </h3>
<small class="parent">Radar for PHP</small>
</div>
<div class="next col-md-3">
<label for="">Next</label>
<a href="/routing.html">Routing</a> </div>
<div class="clearfix"></div>
</div>
</div>
</header>
<section id="content">
<div class="container">
<div class="row">
<div class="col-md-12">
<h1 id="2">2. Domain Design</h1>
<p>Radar concentrates exclusively the HTTP request/response cycle. This means that,
for Radar to be useful, you need to build your <em>Domain</em> outside of, and probably
in parallel with, your Radar wrapper around that <em>Domain</em>.</p>
<p>With that in mind, this is a minimalist primer on building a <em>Domain</em> service.
For more information, please consult Domain Driven Design and similar works.</p>
<h2 id="2-1">2.1. Application Service</h2>
<p>All Radar cares about is the outermost (or topmost) entry point into the
<em>Domain</em> layer. This entry point is likely to be something like an
<em>ApplicationService</em>.</p>
<p>Your ADR <em>Action</em> will pass user input into the <em>ApplicationService</em>. The
<em>ApplicationService</em> will initiate and coordinate all the underlying activity in
the <em>Domain</em>, and return a <em>Payload</em> back to the <em>Action</em> for the <em>Responder</em> to
use.</p>
<p>The <em>ApplicationService</em> should never access anything directly in the HTTP or
CLI layer. Everything it needs should be injected from the outside, either at
construction time or through a method call. For example, no superglobal should
ever appear in an <em>ApplicationService</em> (or anywhere else in the <em>Domain</em>
either). This is to make sure the <em>ApplicationService</em>, and by extension the
<em>Domain</em> as a whole, is independent from any particular user interface system.</p>
<p>Each <em>ApplicationService</em> should be as narrowly-purposed as possible, handling
either a single activity, or a limited set of related activities.</p>
<h2 id="2-2">2.2. Class Structure</h2>
<p>In a todo system, for example, there might be a single <em>TodoApplicationService</em>
with methods for browse, read, edit, add, and delete:</p>
<pre><code class="language-php">namespace Domain\Todo;
class TodoApplicationService
{
// fetch a list of todo items
public function getList(array $input) { ... }
// edit a todo item
public function editItem(array $input) { ... }
// mark a todo item as done or not
public function markItem(array $input) { ... }
// add a new todo item
public function addItem(array $input) { ... }
// delete a todo item
public function deleteItem(array $input) { ... }
}
</code></pre>
<p>Alternatively, and perhaps preferably, there might be a series of single-purpose
<em>Todo</em> application services:</p>
<pre><code class="language-php">namespace Domain\Todo\ApplicationService;
class GetList
{
public function __invoke(array $input) { ... }
}
class EditItem
{
public function __invoke(array $input) { ... }
}
class AddItem
{
public function __invoke(array $input) { ... }
}
class DeleteItem
{
public function __invoke(array $input) { ... }
}
</code></pre>
<h3 id="2-2-1">2.2.1. Domain Logic</h3>
<p>The logic inside the <em>ApplicationService</em> is entirely up to you. You can use
anything from a plain-old database connection to a formal DDD approach. As long
as the <em>ApplicationService</em> returns a <em>Payload</em>, the internals of the
<em>ApplicationService</em> and its interactions do not matter to Radar.</p>
<p>Here is a naive bit of logic for a <em>Fetch</em> service in our todo application. It
guards against several error conditions (anonymous user, invalid input, user
attempting to edit a todo item they do not own, and database update failures).
It returns a <em>Payload</em> that describes exactly what happened inside the
<em>Domain</em>. Also notice how it is completely independent from HTTP or CLI
elements; this makes it easier to test in isolation, and to reuse in different
interfaces.</p>
<pre><code class="language-php">namespace Domain\Todo\ApplicationService;
use Aura\Payload\Payload;
use Aura\Payload_Interface\PayloadStatus;
use Exception;
use Todo\User;
use Todo\Mapper;
class EditItem
{
public function __construct(
User $user,
Mapper $mapper,
Payload $payload
) {
$this->user = $user;
$this->mapper = $mapper;
$this->payload = $payload;
}
public function __invoke(array $input)
{
if (! $this->user->isAuthenticated()) {
return $this->payload
->setStatus(PayloadStatus::NOT_AUTHENTICATED);
}
if (empty($input['id'])) {
return $this->payload
->setStatus(PayloadStatus::NOT_VALID)
->setInput($input)
->setMessages([
'id' => 'Todo ID not set.'
]);
}
$todo = $this->mapper->fetchById($input['id']);
if (! $todo) {
return $this->payload
->setStatus(PayloadStatus::NOT_FOUND)
->setInput($input);
}
if ($this->user->userId !== $todo->userId) {
return $this->payload
->setStatus(PayloadStatus::NOT_AUTHORIZED)
->setInput($input);
}
try {
$todo->description = $input['description'];
$this->mapper->update($todo);
return $this->payload
->setStatus(PayloadStatus::UPDATED)
->setOutput($todo);
} catch (Exception $e) {
return $this->payload
->setStatus(PayloadStatus::ERROR)
->setInput($input)
->setOutput($e);
}
}
}
</code></pre>
<h3 id="2-2-2">2.2.2. Domain Packaging</h3>
<p>Although you can place the <em>Domain</em> layer in the Radar <code>src/</code> directory, it may
be wiser to create it as a separate package, and import it via Composer. This
will help to enforce the separation between the core application and the Radar
user-interface wrapper around it, along with test suites independent from the
Radar project.</p>
</div>
</div>
</div>
</section>
<footer>
<section id="links">
<div class="container">
<div class="row">
<div class="prev col-md-3">
<label for="">Prev</label>
<a href="/intro.html">1. Introduction</a> </div>
<div class="parent col-md-6">
<label for="">Up</label>
<a href="/">Radar for PHP</a> </div>
<div class="next col-md-3">
<label for="">Next</label>
<a href="/routing.html">3. Routing</a> </div>
<div class="clearfix"></div>
</div>
</div>
</section>
<section id="copyright">
<div class="container">
<div class="row">
<div class="col-md-12">
<span>
Powered by <a href="">Bookdown.io</a> | Designed by <a href="">yuripertamax</a>
</span>
</div>
</div>
</div>
</section>
</footer>
</body>
</html>