@rmrdeveloper/sideroom-pi 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,463 @@
1
+ # Guidelines Template
2
+
3
+ How to write code in this repo: the principles and style rules every change must follow.
4
+
5
+ ## Quick reference
6
+
7
+ Each row is expanded, with examples, in the matching section below.
8
+
9
+ | Do | Don't |
10
+ | -------------------------------------------------- | ------------------------------------------------------------------------------------------- |
11
+ | Early return on bad input | Pyramid `if/else` nesting |
12
+ | Explicit error, fail now | Multiple fallbacks that hide the real failure |
13
+ | One responsibility per unit | Validate + transform + persist + notify in one place |
14
+ | Extract when duplication repeats | Abstract before a second real use |
15
+ | Ship the simplest solution for the current problem | Add layers, hooks, or config "just in case" |
16
+ | Build only what's needed today | Add fields/params/branches for a future case that hasn't arrived |
17
+ | Compose small, focused units | Build deep inheritance chains for unrelated behavior |
18
+ | Talk only to immediate collaborators | Reach through several levels of another object's internal structure |
19
+ | A function either does or returns, not both | Mix a side effect into what looks like a getter |
20
+ | Validate once at the edge | Re-validate the same invariant in every layer |
21
+ | One validator per input | Two validators for the same body/query |
22
+ | Let errors surface with context | Swallow errors in an empty or generic `catch`, or catch-and-continue as if nothing happened |
23
+ | Return new values instead of mutating input | Mutate parameters and hide the side effect from the caller |
24
+ | One consistent meaning per null/undefined | Overload null/undefined to mean several different business states |
25
+ | Inject dependencies so units are easy to test | Hardcode dependencies that force hitting real infra to test |
26
+ | Inner layers depend on nothing outward | Let domain logic import framework/DB/HTTP details directly |
27
+ | Names that reveal role or domain meaning | Vague names (`data`, `info`, `temp`, `result`, `obj`) |
28
+ | Named const / enum / contract for domain literals | Magic strings scattered through the codebase |
29
+ | Depend on interfaces/ports where variation is real | Couple a use case directly to a concrete implementation |
30
+ | Comments only for important non-obvious intent | Narrating comments, noise, or stale TODOs |
31
+
32
+ ## Principles
33
+
34
+ ### Guard clauses
35
+
36
+ Validate and exit early. Prefer flat control flow over deep nesting. Keep the happy path at the end, at the shallowest indent level.
37
+
38
+ ```js
39
+ // Bad — pyramid nesting, happy path buried
40
+ function getDiscount(user) {
41
+ if (user) {
42
+ if (user.isActive) {
43
+ if (user.hasSubscription) {
44
+ return 0.2;
45
+ } else {
46
+ return 0;
47
+ }
48
+ } else {
49
+ return 0;
50
+ }
51
+ } else {
52
+ return 0;
53
+ }
54
+ }
55
+
56
+ // Good — guard clauses, happy path at the end, shallow indent
57
+ function getDiscount(user) {
58
+ if (!user) return 0;
59
+ if (!user.isActive) return 0;
60
+ if (!user.hasSubscription) return 0;
61
+ return 0.2;
62
+ }
63
+ ```
64
+
65
+ ### Fail fast
66
+
67
+ Invalid input, impossible state, or a broken dependency should fail immediately with a clear error. Prefer that over chains of fallbacks, silent defaults, or "keep going somehow."
68
+
69
+ **Relationship to "Validate once":** fail fast applies to type and state invariants that should never happen if upstream contracts hold (a required field is `null`, an enum has an impossible value). It is not license to re-check business rules the boundary already validated — that's re-validation, not fail-fast. If a downstream failure reveals that an upstream contract was violated, fix or harden the boundary; do not add a second check that quietly duplicates it.
70
+
71
+ ```js
72
+ // Bad — silent fallback hides a broken invariant
73
+ function getShippingCost(order) {
74
+ const zone = ZONES[order.zoneId] || ZONES.default; // hides a bad zoneId
75
+ return zone.baseCost;
76
+ }
77
+
78
+ // Good — fails immediately with a clear error
79
+ function getShippingCost(order) {
80
+ const zone = ZONES[order.zoneId];
81
+ if (!zone) throw new Error(`Unknown zoneId: ${order.zoneId}`);
82
+ return zone.baseCost;
83
+ }
84
+ ```
85
+
86
+ ### SRP
87
+
88
+ A function, class, or module has one reason to change. If it does two jobs, split it.
89
+
90
+ ```js
91
+ // Bad — validates, transforms, persists, and notifies all in one place
92
+ async function saveUser(data) {
93
+ if (!data.email) throw new Error('email required');
94
+ const normalized = { ...data, email: data.email.trim().toLowerCase() };
95
+ await db.users.insert(normalized);
96
+ await mailer.send(normalized.email, 'welcome');
97
+ }
98
+
99
+ // Good — each unit has a single reason to change
100
+ function validateUser(data) {
101
+ if (!data.email) throw new Error('email required');
102
+ }
103
+ function normalizeUser(data) {
104
+ return { ...data, email: data.email.trim().toLowerCase() };
105
+ }
106
+ async function createUser(data) {
107
+ validateUser(data);
108
+ const user = normalizeUser(data);
109
+ await db.users.insert(user);
110
+ await notifyWelcome(user.email);
111
+ return user;
112
+ }
113
+ ```
114
+
115
+ ### DRY
116
+
117
+ Do not copy logic that means the same thing. Extract only when duplication is real — not as premature abstraction.
118
+
119
+ **Practical rule:** extract when at least 2 of these 3 conditions hold:
120
+
121
+ - The same logic appears **3 or more times** (twice is tolerated; the third confirms the pattern).
122
+ - A future change to the business rule would need to be applied in **all** places at once (if not, it's not real duplication, just coincidence).
123
+ - The copied code represents the **same domain concept**, not just similar-looking code.
124
+
125
+ **Not real duplication (do not extract):**
126
+
127
+ - Two validations that look alike today but belong to different concepts (e.g. validating a user's email vs a vendor's email) — they'll likely diverge later.
128
+ - Boilerplate required by the surrounding structure (e.g. two handlers with the same shape because the calling convention requires it), where the shape is imposed from outside and not a domain decision.
129
+
130
+ **Example:**
131
+
132
+ ```ts
133
+ // Bad: extracted prematurely, only one real use
134
+ function formatName(x: string) { return x.trim().toUpperCase(); }
135
+
136
+ // Good: appears 3 times with the same domain meaning → extract
137
+ function normalizeTicketStatus(status: string): TicketStatus { ... }
138
+ ```
139
+
140
+ ### KISS
141
+
142
+ Ship the simplest solution that solves the current problem. No extra layers, hooks, or configurability "just in case."
143
+
144
+ ```js
145
+ // Bad — configurability nobody asked for, added "just in case"
146
+ function formatPrice(
147
+ value,
148
+ { currency = 'USD', locale = 'en-US', showSymbol = true, roundingStrategy = 'nearest' } = {},
149
+ ) {
150
+ // ...unneeded logic for a single real use case
151
+ }
152
+
153
+ // Good — solves the current problem, nothing more
154
+ function formatPrice(value) {
155
+ return `$${value.toFixed(2)}`;
156
+ }
157
+ ```
158
+
159
+ ### YAGNI (You Aren't Gonna Need It)
160
+
161
+ Build only what the current requirement needs. Don't add fields, params, branches, or abstractions for a future case that hasn't arrived. This differs from KISS: KISS is about keeping the _chosen_ solution simple; YAGNI is about not building things nobody asked for yet.
162
+
163
+ ```js
164
+ // Bad — speculative support for a case that doesn't exist yet
165
+ function createInvoice(order, { supportsRecurring = false, supportsMultiCurrency = false } = {}) {
166
+ // ...branches for features no client uses today
167
+ }
168
+
169
+ // Good — build for the requirement that actually exists
170
+ function createInvoice(order) {
171
+ return { total: order.total, items: order.items };
172
+ }
173
+ ```
174
+
175
+ ### Composition over inheritance
176
+
177
+ Prefer composing small, focused units (functions, objects, mixins) over deep inheritance chains. Inheritance couples subclasses to implementation details of their parent and tends to break when requirements diverge.
178
+
179
+ ```js
180
+ // Bad — inheritance forces unrelated behavior onto every subclass
181
+ class Animal {
182
+ makeSound() {
183
+ throw new Error('not implemented');
184
+ }
185
+ fly() {
186
+ throw new Error('not implemented');
187
+ }
188
+ }
189
+ class Dog extends Animal {
190
+ makeSound() {
191
+ return 'Woof';
192
+ }
193
+ // forced to inherit `fly`, which makes no sense for a Dog
194
+ }
195
+
196
+ // Good — compose only the behaviors that apply
197
+ const canBark = { makeSound: () => 'Woof' };
198
+ const canFly = { fly: () => 'Flying' };
199
+ const dog = { ...canBark };
200
+ const bird = { ...canBark, ...canFly };
201
+ ```
202
+
203
+ ### Law of Demeter (don't talk to strangers)
204
+
205
+ A unit should only interact with its immediate collaborators, not reach through them to grab something several levels deep. Deep chains couple you to structure that isn't yours to know.
206
+
207
+ ```js
208
+ // Bad — reaches through three levels of internal structure
209
+ function getCityName(user) {
210
+ return user.address.city.name;
211
+ }
212
+
213
+ // Good — ask the object for what you need, let it own its structure
214
+ function getCityName(user) {
215
+ return user.getCityName();
216
+ }
217
+ ```
218
+
219
+ ### Command Query Separation (CQS)
220
+
221
+ A function either **does** something (command, causes a side effect) or **returns** something (query) — not both. Mixing the two makes call sites unpredictable: you can't tell if calling something is safe to do twice.
222
+
223
+ ```js
224
+ // Bad — returns a value AND causes a side effect
225
+ function getNextId(counter) {
226
+ counter.value++; // side effect hidden inside a "getter"
227
+ return counter.value;
228
+ }
229
+
230
+ // Good — separate the query from the command
231
+ function peekNextId(counter) {
232
+ return counter.value + 1;
233
+ }
234
+ function incrementCounter(counter) {
235
+ counter.value++;
236
+ }
237
+ ```
238
+
239
+ ### Explicit error handling
240
+
241
+ Let errors surface with context instead of swallowing them. An empty or generic `catch` hides the real failure and makes debugging production issues much harder.
242
+
243
+ ```js
244
+ // Bad — swallows the error, no context, execution continues as if nothing happened
245
+ async function loadUser(id) {
246
+ try {
247
+ return await api.getUser(id);
248
+ } catch (e) {
249
+ return null; // caller has no idea a failure occurred
250
+ }
251
+ }
252
+
253
+ // Good — the error surfaces with context, caller decides how to handle it
254
+ async function loadUser(id) {
255
+ try {
256
+ return await api.getUser(id);
257
+ } catch (e) {
258
+ throw new Error(`Failed to load user ${id}: ${e.message}`, { cause: e });
259
+ }
260
+ }
261
+ ```
262
+
263
+ ### Immutability by default
264
+
265
+ Prefer creating new values over mutating existing ones, especially for data passed as a parameter. Mutation hides side effects and makes state changes hard to trace, particularly in reactive systems.
266
+
267
+ ```js
268
+ // Bad — mutates the input, callers get a surprise side effect
269
+ function addItem(cart, item) {
270
+ cart.items.push(item);
271
+ return cart;
272
+ }
273
+
274
+ // Good — returns a new value, caller's original data stays untouched
275
+ function addItem(cart, item) {
276
+ return { ...cart, items: [...cart.items, item] };
277
+ }
278
+ ```
279
+
280
+ ### Null/undefined handling
281
+
282
+ Pick one convention and apply it consistently: e.g. `undefined` for "not yet set" and `null` for "explicitly empty," or vice versa — the specific choice matters less than not mixing both for the same meaning. Don't use `null`/`undefined` as a stand-in for a business state that deserves its own explicit value.
283
+
284
+ ```js
285
+ // Bad — null is overloaded to mean three different things
286
+ function getDiscount(user) {
287
+ if (!user) return null; // no user
288
+ if (!user.plan) return null; // no plan
289
+ if (user.plan.discount === 0) return null; // legitimately zero discount
290
+ }
291
+
292
+ // Good — each case is explicit, zero is a real value
293
+ function getDiscount(user) {
294
+ if (!user || !user.plan) return 0;
295
+ return user.plan.discount;
296
+ }
297
+ ```
298
+
299
+ ### Testability as a design constraint
300
+
301
+ Code that's easy to test is usually well-designed: side effects are isolated, dependencies are injected rather than hardcoded, and units do one thing. If a function is hard to test, that's often a signal the design needs to change, not a signal to skip the test.
302
+
303
+ ```js
304
+ // Bad — hardcoded dependency, can't test without hitting the real clock/API
305
+ function isSubscriptionExpired(subscription) {
306
+ return subscription.expiresAt < new Date();
307
+ }
308
+
309
+ // Good — dependency is injected, trivial to test with a fixed date
310
+ function isSubscriptionExpired(subscription, now = new Date()) {
311
+ return subscription.expiresAt < now;
312
+ }
313
+ ```
314
+
315
+ ### Dependency direction
316
+
317
+ Inner layers (domain/business logic) must not depend on outer layers (frameworks, databases, HTTP clients). Outer layers depend inward, never the reverse. This keeps business rules testable and portable independent of infrastructure choices.
318
+
319
+ ```js
320
+ // Bad — domain logic imports directly from an infrastructure detail
321
+ import { MysqlConnection } from '../infra/mysql';
322
+ function calculateInvoiceTotal(invoiceId) {
323
+ const invoice = new MysqlConnection().query('SELECT * FROM invoices WHERE id = ?', [invoiceId]);
324
+ return invoice.items.reduce((sum, item) => sum + item.price, 0);
325
+ }
326
+
327
+ // Good — domain logic depends only on the shape of the data, not its source
328
+ function calculateInvoiceTotal(invoice) {
329
+ return invoice.items.reduce((sum, item) => sum + item.price, 0);
330
+ }
331
+ ```
332
+
333
+ ### Clear names
334
+
335
+ Variables, parameters, functions, and types must say what they hold or do. Prefer domain words over vague fillers (`data`, `info`, `item`, `temp`, `result`, `obj`, `val`, `x`). If the honest name is long, that is fine — a short vague name is not.
336
+
337
+ ```js
338
+ // Bad — vague names that hide the domain meaning
339
+ function process(data) {
340
+ const temp = data.filter((x) => x.val > 0);
341
+ return temp;
342
+ }
343
+
344
+ // Good — names reveal role and domain meaning
345
+ function getActiveSubscriptions(subscriptions) {
346
+ return subscriptions.filter((subscription) => subscription.remainingDays > 0);
347
+ }
348
+ ```
349
+
350
+ ### Comments
351
+
352
+ Do not leave comments that add no value. Prefer clear names and structure so the code explains itself. Comment only what is genuinely important and non-obvious: why a trade-off was made, a constraint the reader would miss, or a hazard that names alone cannot carry. Delete narration, restatements of the next line, and leftover TODOs that no longer mean anything.
353
+
354
+ **Examples:**
355
+
356
+ ```ts
357
+ // Bad — narrates the obvious
358
+ // increment the counter by 1
359
+ counter++;
360
+
361
+ // Bad — restates what the name already says
362
+ // get the user by id
363
+ const user = getUserById(id);
364
+
365
+ // Good — explains a non-obvious trade-off
366
+ // We poll instead of using a webhook because the provider doesn't
367
+ // guarantee single delivery; downstream dedupe would cost more than polling.
368
+ setInterval(checkPaymentStatus, 5000);
369
+
370
+ // Good — warns of a hazard the name can't carry
371
+ // WARNING: this endpoint is only idempotent if `externalId` comes from
372
+ // the client; if we generate it ourselves, retries create duplicate records.
373
+ ```
374
+
375
+ **Quick test before writing a comment:** if deleting it leaves the code just as clear, the comment isn't earning its place.
376
+
377
+ ### No magic strings
378
+
379
+ Do not hard-code domain or protocol literals inline (status values, roles, path fragments, error codes, event names). Name them once — const, enum, shared contract, or map — and reuse that name. Exception: one-off strings with no reuse and no domain meaning (e.g. a single log label) may stay inline if a named constant would only obscure them.
380
+
381
+ ```js
382
+ // Bad — domain literals scattered across the codebase
383
+ if (order.status === 'pending_payment') {
384
+ /* ... */
385
+ }
386
+ // ...elsewhere, in a different file
387
+ if (order.status === 'pending_payment') {
388
+ /* ... */
389
+ }
390
+
391
+ // Good — named once, reused everywhere
392
+ const ORDER_STATUS = {
393
+ PENDING_PAYMENT: 'pending_payment',
394
+ PAID: 'paid',
395
+ CANCELLED: 'cancelled',
396
+ };
397
+ if (order.status === ORDER_STATUS.PENDING_PAYMENT) {
398
+ /* ... */
399
+ }
400
+ ```
401
+
402
+ ### SOLID
403
+
404
+ Apply with judgment. Favor single responsibility and inversion of dependencies (ports/adapters) where variation is real. Do not force every SOLID letter into every small file.
405
+
406
+ ```js
407
+ // Bad — the use case depends directly on a concrete implementation
408
+ class SendWelcomeEmail {
409
+ async execute(user) {
410
+ await new SmtpMailer().send(user.email, 'welcome'); // coupled to SMTP
411
+ }
412
+ }
413
+
414
+ // Good — depends on an interface (port), not the implementation
415
+ class SendWelcomeEmail {
416
+ constructor(mailer) {
417
+ this.mailer = mailer;
418
+ } // mailer implements MailerPort
419
+ async execute(user) {
420
+ await this.mailer.send(user.email, 'welcome');
421
+ }
422
+ }
423
+ ```
424
+
425
+ ### Validate once
426
+
427
+ Validate at the boundary (the first function, layer, or contract that owns the input). Downstream code should trust that contract — do not re-check the same emptiness, type, or range in helpers, use cases, or adapters further in.
428
+
429
+ Bad smell: hand-rolled parsers that trim/null-check/re-parse what the boundary already guarantees, or a second layer validating the same rule again "just in case."
430
+
431
+ When a shared schema (or equivalent single contract) defines the input, that schema is the **single source of truth**. Do not stack a second validator for the same invariants. Documentation types or interface shapes may mirror the contract, but they must not re-enforce the same rules.
432
+
433
+ ```php
434
+ // Bad — the FormRequest already validates, and the Service re-checks the same rule
435
+ class UpdateProfileRequest extends FormRequest {
436
+ public function rules() {
437
+ return ['email' => 'required|email'];
438
+ }
439
+ }
440
+ class ProfileService {
441
+ public function update(array $data) {
442
+ if (empty($data['email'])) { // redundant re-validation
443
+ throw new \InvalidArgumentException('email required');
444
+ }
445
+ $this->user->update($data);
446
+ }
447
+ }
448
+
449
+ // Good — the Service trusts the contract already validated by the FormRequest
450
+ class ProfileService {
451
+ public function update(array $validated) {
452
+ $this->user->update($validated);
453
+ }
454
+ }
455
+ ```
456
+
457
+ | Do | Don't |
458
+ | ---------------------------------------------------------------------------------- | ---------------------------------------------------------- |
459
+ | Parse once at the edge; pass a typed command/query inward | Re-parse or re-check the same rules in every layer |
460
+ | One validation approach per input | Two mechanisms validating the same body/query |
461
+ | Keep docs/interface metadata separate from the validation contract when both exist | Duplicate the same type/range/required rules in two places |
462
+
463
+ When in doubt: **fail fast, keep it flat, keep it small.**
@@ -0,0 +1,211 @@
1
+ # Java Coding Guidelines
2
+
3
+ This is the Java version of `GUIDELINES_TEMPLATE.md`. It preserves the same
4
+ principles and example scenarios using explicit Java types, immutable values,
5
+ and layered application boundaries. The shared baseline remains mandatory.
6
+
7
+ ## Quick reference
8
+
9
+ | Do | Don't |
10
+ | --- | --- |
11
+ | Guard invalid input early | Bury happy paths in nested `if/else` branches |
12
+ | Throw a precise exception now | Substitute a default and hide invalid state |
13
+ | Give a class one responsibility | Validate, transform, persist, and notify together |
14
+ | Extract repeated domain behavior | Add a hierarchy for a one-off method |
15
+ | Build only current behavior | Add speculative flags and configuration |
16
+ | Prefer immutable records/value objects | Mutate data passed by a caller |
17
+ | Validate once at an adapter | Re-validate the same command in every layer |
18
+ | Depend on ports at real seams | Construct database/HTTP clients in domain code |
19
+
20
+ ## Principles
21
+
22
+ ### Guard clauses
23
+
24
+ The discount example keeps the happy path shallow.
25
+
26
+ ```java
27
+ double getDiscount(User user) {
28
+ if (user == null) return 0.0;
29
+ if (!user.active()) return 0.0;
30
+ if (!user.hasSubscription()) return 0.0;
31
+ return 0.2;
32
+ }
33
+ ```
34
+
35
+ ### Fail fast
36
+
37
+ An unknown shipping zone is invalid state, not a reason to use a default.
38
+
39
+ ```java
40
+ int shippingCost(String zoneId, Map<String, Zone> zones) {
41
+ var zone = zones.get(zoneId);
42
+ if (zone == null) throw new IllegalArgumentException("Unknown zoneId: " + zoneId);
43
+ return zone.baseCost();
44
+ }
45
+ ```
46
+
47
+ ### SRP
48
+
49
+ The user flow separates normalization, repository work, and notification.
50
+
51
+ ```java
52
+ final class CreateUser {
53
+ private final UserRepository users;
54
+ private final Mailer mailer;
55
+ User create(String email) {
56
+ if (email.isBlank()) throw new IllegalArgumentException("email required");
57
+ var user = users.save(new User(normalizeEmail(email)));
58
+ mailer.send(user.email(), "welcome");
59
+ return user;
60
+ }
61
+ private String normalizeEmail(String email) { return email.trim().toLowerCase(Locale.ROOT); }
62
+ }
63
+ ```
64
+
65
+ ### DRY
66
+
67
+ Extract the third repeated ticket-status normalization, not a premature helper.
68
+
69
+ ```java
70
+ enum TicketStatus { OPEN, CLOSED }
71
+ TicketStatus normalizeTicketStatus(String value) { return TicketStatus.valueOf(value.toUpperCase(Locale.ROOT)); }
72
+ ```
73
+
74
+ ### KISS
75
+
76
+ Keep price and invoice examples limited to today's scope.
77
+
78
+ ```java
79
+ String formatPrice(BigDecimal value) { return "$" + value.setScale(2); }
80
+ ```
81
+
82
+ ### YAGNI (You Aren't Gonna Need It)
83
+
84
+ Build the invoice requirement that exists today. Do not add currency strategies,
85
+ recurring branches, event listeners, or cache layers without a real requirement.
86
+
87
+ ```java
88
+ Invoice createInvoice(Order order) { return new Invoice(order.total(), order.items()); }
89
+ ```
90
+
91
+ ### Composition over inheritance
92
+
93
+ Compose behavior rather than inherit an irrelevant `fly()` operation.
94
+
95
+ ```java
96
+ record Dog(BarkBehavior bark) { String makeSound() { return bark.sound(); } }
97
+ interface BarkBehavior { String sound(); }
98
+ ```
99
+
100
+ ### Law of Demeter
101
+
102
+ Ask the user for its city, not its address's city's name.
103
+
104
+ ```java
105
+ String cityName(User user) { return user.cityName(); }
106
+ ```
107
+
108
+ ### Command Query Separation
109
+
110
+ Peeking and incrementing a counter are separate methods.
111
+
112
+ ```java
113
+ int peekNextId(Counter counter) { return counter.value() + 1; }
114
+ void incrementCounter(Counter counter) { counter.increment(); }
115
+ ```
116
+
117
+ ### Explicit error handling
118
+
119
+ Retain a cause and add user-specific context; do not catch and return null.
120
+
121
+ ```java
122
+ User loadUser(String id, UserApi api) {
123
+ try { return api.get(id); }
124
+ catch (ApiException error) { throw new IllegalStateException("Failed to load user " + id, error); }
125
+ }
126
+ ```
127
+
128
+ ### Immutability by default
129
+
130
+ The cart scenario returns a new record.
131
+
132
+ ```java
133
+ record Cart(List<Item> items) {}
134
+ Cart addItem(Cart cart, Item item) {
135
+ var items = new ArrayList<>(cart.items());
136
+ items.add(item);
137
+ return new Cart(List.copyOf(items));
138
+ }
139
+ ```
140
+
141
+ ### Null handling
142
+
143
+ Prefer explicit absence with `Optional` at boundaries and preserve a zero
144
+ discount as a real value.
145
+
146
+ ```java
147
+ double getDiscount(Optional<User> user) {
148
+ return user.flatMap(User::plan).map(Plan::discount).orElse(0.0);
149
+ }
150
+ ```
151
+
152
+ ### Testability as a design constraint
153
+
154
+ Inject `Clock` instead of asking the system clock inside domain behavior.
155
+
156
+ ```java
157
+ boolean isSubscriptionExpired(Subscription subscription, Clock clock) {
158
+ return subscription.expiresAt().isBefore(Instant.now(clock));
159
+ }
160
+ ```
161
+
162
+ ### Dependency direction
163
+
164
+ Invoice calculation receives an invoice rather than an SQL/JPA dependency.
165
+
166
+ ```java
167
+ int calculateInvoiceTotal(Invoice invoice) {
168
+ return invoice.items().stream().mapToInt(InvoiceItem::price).sum();
169
+ }
170
+ ```
171
+
172
+ ### Clear names
173
+
174
+ Use `activeSubscriptions`, `remainingDays`, and `externalId`, never `data`,
175
+ `temp`, or `obj`.
176
+
177
+ ### Comments
178
+
179
+ Comments explain a delivery trade-off or an idempotency hazard, not a statement
180
+ whose name already explains it.
181
+
182
+ ### No magic strings
183
+
184
+ Model order states as an enum.
185
+
186
+ ```java
187
+ enum OrderStatus { PENDING_PAYMENT, PAID }
188
+ if (order.status() == OrderStatus.PENDING_PAYMENT) payments.request(order);
189
+ ```
190
+
191
+ ### SOLID
192
+
193
+ The welcome use case depends on a mailer port, not SMTP.
194
+
195
+ ```java
196
+ interface Mailer { void send(String email, String template); }
197
+ final class SendWelcomeEmail { private final Mailer mailer; SendWelcomeEmail(Mailer mailer) { this.mailer = mailer; } }
198
+ ```
199
+
200
+ ### Validate once
201
+
202
+ An HTTP/message adapter builds a validated command. The application service
203
+ trusts it and never repeats the same null, type, and range checks.
204
+
205
+ ```java
206
+ record UpdateProfile(String email) {}
207
+ void updateProfile(UpdateProfile command, UserRepository users) { users.update(command); }
208
+ ```
209
+
210
+ Use one validation contract per body/query. When in doubt: **fail fast, keep it
211
+ flat, keep it small.**