code-ai-installer 1.4.0 → 1.5.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.
- package/.agents/skills/design_patterns_reference/SKILL.md +694 -0
- package/.agents/skills/tanstack_beast_practices/SKILL.md +3 -0
- package/AGENTS.md +1 -0
- package/agents/architect.md +55 -0
- package/locales/en/.agents/skills/design_patterns_reference/SKILL.md +694 -0
- package/locales/en/.agents/skills/tanstack_beast_practices/SKILL.md +3 -0
- package/locales/en/AGENTS.md +1 -0
- package/locales/en/agents/architect.md +56 -1
- package/package.json +1 -1
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: design_patterns_reference
|
|
3
|
+
description: Design patterns reference with DO/DON'T examples — SOLID, DRY/KISS/YAGNI, GoF (Strategy, Observer, Factory, Adapter, Facade, Decorator, Command, State, Template Method), architectural (Repository, Service Layer, DI, Event-Driven, CQRS), microservices (Saga, Circuit Breaker). Language-agnostic pseudocode. Use when designing modules, reviewing architecture, or when asked "which pattern to apply".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Design Patterns Reference
|
|
7
|
+
|
|
8
|
+
DO/DON'T reference for design patterns. Language-agnostic pseudocode.
|
|
9
|
+
|
|
10
|
+
**Sections:**
|
|
11
|
+
1. [SOLID](#1-solid)
|
|
12
|
+
2. [Fundamental Principles](#2-fundamental-principles)
|
|
13
|
+
3. [GoF Patterns](#3-gof-patterns)
|
|
14
|
+
4. [Architectural Patterns](#4-architectural-patterns)
|
|
15
|
+
5. [Microservices Patterns](#5-microservices-patterns)
|
|
16
|
+
6. [Pattern Selection Guide](#6-pattern-selection)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. SOLID
|
|
21
|
+
|
|
22
|
+
### S — Single Responsibility Principle (SRP)
|
|
23
|
+
> A class/module has exactly one reason to change.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
// ✅ DO: each class — one responsibility
|
|
27
|
+
class UserValidator
|
|
28
|
+
validate(user) → ValidationResult
|
|
29
|
+
|
|
30
|
+
class UserRepository
|
|
31
|
+
save(user) → void
|
|
32
|
+
|
|
33
|
+
class EmailNotifier
|
|
34
|
+
sendWelcome(user) → void
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
// ❌ DON'T: one class does everything
|
|
39
|
+
class UserManager
|
|
40
|
+
validate(user) → ...
|
|
41
|
+
saveToDb(user) → ...
|
|
42
|
+
sendEmail(user) → ...
|
|
43
|
+
generateReport() → ...
|
|
44
|
+
// Changing email logic breaks validation and DB
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### O — Open/Closed Principle (OCP)
|
|
48
|
+
> Open for extension, closed for modification.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
// ✅ DO: new type = new class, without modifying existing code
|
|
52
|
+
interface DiscountStrategy
|
|
53
|
+
calculate(order) → number
|
|
54
|
+
|
|
55
|
+
class PercentDiscount implements DiscountStrategy
|
|
56
|
+
calculate(order) → order.total * this.percent
|
|
57
|
+
|
|
58
|
+
class FixedDiscount implements DiscountStrategy
|
|
59
|
+
calculate(order) → this.amount
|
|
60
|
+
|
|
61
|
+
class OrderService
|
|
62
|
+
applyDiscount(order, strategy: DiscountStrategy)
|
|
63
|
+
order.discount = strategy.calculate(order)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
// ❌ DON'T: every new discount type — editing if/switch
|
|
68
|
+
class OrderService
|
|
69
|
+
applyDiscount(order, type)
|
|
70
|
+
if type == "percent" → ...
|
|
71
|
+
else if type == "fixed" → ...
|
|
72
|
+
else if type == "seasonal" → ... // modifying this method every time
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### L — Liskov Substitution Principle (LSP)
|
|
76
|
+
> Subtypes can be substituted for the base type without breaking behavior.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
// ✅ DO: subtypes honestly implement the contract
|
|
80
|
+
interface Shape
|
|
81
|
+
area() → number
|
|
82
|
+
|
|
83
|
+
class Rectangle implements Shape
|
|
84
|
+
area() → this.width * this.height
|
|
85
|
+
|
|
86
|
+
class Circle implements Shape
|
|
87
|
+
area() → π * this.radius²
|
|
88
|
+
|
|
89
|
+
// Any Shape can be passed to calculateTotalArea(shapes[])
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
// ❌ DON'T: subtype violates parent's contract
|
|
94
|
+
class Rectangle
|
|
95
|
+
setWidth(w), setHeight(h)
|
|
96
|
+
|
|
97
|
+
class Square extends Rectangle
|
|
98
|
+
setWidth(w) → this.width = w; this.height = w // surprise: setWidth changes height
|
|
99
|
+
// Code that expects Rectangle breaks
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### I — Interface Segregation Principle (ISP)
|
|
103
|
+
> Many small interfaces are better than one fat one.
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
// ✅ DO: client depends only on needed methods
|
|
107
|
+
interface Readable
|
|
108
|
+
read() → Data
|
|
109
|
+
|
|
110
|
+
interface Writable
|
|
111
|
+
write(data) → void
|
|
112
|
+
|
|
113
|
+
interface Deletable
|
|
114
|
+
delete(id) → void
|
|
115
|
+
|
|
116
|
+
class FileStore implements Readable, Writable, Deletable
|
|
117
|
+
class ReadOnlyCache implements Readable
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
// ❌ DON'T: one fat interface — clients depend on what they don't need
|
|
122
|
+
interface Storage
|
|
123
|
+
read() → Data
|
|
124
|
+
write(data) → void
|
|
125
|
+
delete(id) → void
|
|
126
|
+
backup() → void
|
|
127
|
+
migrate() → void
|
|
128
|
+
|
|
129
|
+
class SimpleCache implements Storage
|
|
130
|
+
backup() → throw "Not supported" // contract violation
|
|
131
|
+
migrate() → throw "Not supported"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### D — Dependency Inversion Principle (DIP)
|
|
135
|
+
> Depend on abstractions, not on concrete implementations.
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
// ✅ DO: business logic depends on an interface
|
|
139
|
+
interface PaymentGateway
|
|
140
|
+
charge(amount) → Result
|
|
141
|
+
|
|
142
|
+
class OrderService
|
|
143
|
+
constructor(gateway: PaymentGateway) // abstraction injection
|
|
144
|
+
checkout(order) → this.gateway.charge(order.total)
|
|
145
|
+
|
|
146
|
+
// Easy to swap: StripeGateway, PayPalGateway, TestGateway
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
// ❌ DON'T: direct dependency on a concrete implementation
|
|
151
|
+
class OrderService
|
|
152
|
+
checkout(order)
|
|
153
|
+
stripe = new StripeClient(API_KEY) // hardcoded to Stripe
|
|
154
|
+
stripe.charge(order.total)
|
|
155
|
+
// Cannot test, cannot replace
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 2. Fundamental Principles
|
|
161
|
+
|
|
162
|
+
### DRY — Don't Repeat Yourself
|
|
163
|
+
```
|
|
164
|
+
// ✅ DO: reusable function
|
|
165
|
+
function formatCurrency(amount, currency)
|
|
166
|
+
return currency.symbol + amount.toFixed(2)
|
|
167
|
+
|
|
168
|
+
// Used in Invoice, Cart, Report
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
// ❌ DON'T: copy-pasting the same logic
|
|
173
|
+
// In Invoice: "$" + amount.toFixed(2)
|
|
174
|
+
// In Cart: "$" + amount.toFixed(2)
|
|
175
|
+
// In Report: "$" + amount.toFixed(2)
|
|
176
|
+
// Changing the format → editing 3+ places
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
> ⚠️ **Caution:** DRY is about knowledge, not code. Two identical code snippets with different reasons to change are NOT duplication.
|
|
180
|
+
|
|
181
|
+
### KISS — Keep It Simple, Stupid
|
|
182
|
+
```
|
|
183
|
+
// ✅ DO: simple and readable solution
|
|
184
|
+
function isAdult(age)
|
|
185
|
+
return age >= 18
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
// ❌ DON'T: overengineering
|
|
190
|
+
function isAdult(age)
|
|
191
|
+
ageValidator = new AgeValidatorFactory.create("adult")
|
|
192
|
+
rule = ageValidator.getRuleEngine().getRule("minimum")
|
|
193
|
+
return rule.evaluate(new AgeContext(age))
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### YAGNI — You Aren't Gonna Need It
|
|
197
|
+
```
|
|
198
|
+
// ✅ DO: implement what is needed now
|
|
199
|
+
class UserService
|
|
200
|
+
getUser(id) → User
|
|
201
|
+
createUser(data) → User
|
|
202
|
+
|
|
203
|
+
// ❌ DON'T: "just in case"
|
|
204
|
+
class UserService
|
|
205
|
+
getUser(id) → User
|
|
206
|
+
createUser(data) → User
|
|
207
|
+
exportToXml() → ... // nobody asked
|
|
208
|
+
syncWithLdap() → ... // no requirement
|
|
209
|
+
generatePdfReport() → ... // "might come in handy"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Composition over Inheritance
|
|
213
|
+
```
|
|
214
|
+
// ✅ DO: composition — flexible, testable
|
|
215
|
+
class Logger
|
|
216
|
+
log(msg) → ...
|
|
217
|
+
|
|
218
|
+
class HttpClient
|
|
219
|
+
constructor(logger: Logger)
|
|
220
|
+
get(url) → this.logger.log(...); fetch(url)
|
|
221
|
+
|
|
222
|
+
class CachedHttpClient
|
|
223
|
+
constructor(client: HttpClient, cache: Cache)
|
|
224
|
+
get(url) → cache.get(url) ?? client.get(url)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
// ❌ DON'T: deep inheritance hierarchy
|
|
229
|
+
class BaseClient → class HttpClient → class AuthHttpClient → class CachedAuthHttpClient
|
|
230
|
+
// 4 levels of inheritance — fragile, hard to modify
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Fail Fast
|
|
234
|
+
```
|
|
235
|
+
// ✅ DO: validate inputs immediately
|
|
236
|
+
function transfer(from, to, amount)
|
|
237
|
+
if amount <= 0 → throw InvalidAmountError
|
|
238
|
+
if from.balance < amount → throw InsufficientFundsError
|
|
239
|
+
// ... main logic only if everything is valid
|
|
240
|
+
|
|
241
|
+
// ❌ DON'T: error surfaces deep inside
|
|
242
|
+
function transfer(from, to, amount)
|
|
243
|
+
from.balance -= amount // may go negative
|
|
244
|
+
to.balance += amount // data already corrupted
|
|
245
|
+
if from.balance < 0 → ... // too late!
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 3. GoF Patterns
|
|
251
|
+
|
|
252
|
+
### Strategy — swappable algorithms
|
|
253
|
+
```
|
|
254
|
+
// ✅ DO: algorithm as an injectable strategy
|
|
255
|
+
interface SortStrategy
|
|
256
|
+
sort(items[]) → items[]
|
|
257
|
+
|
|
258
|
+
class BubbleSort implements SortStrategy
|
|
259
|
+
class QuickSort implements SortStrategy
|
|
260
|
+
class MergeSort implements SortStrategy
|
|
261
|
+
|
|
262
|
+
class DataProcessor
|
|
263
|
+
constructor(strategy: SortStrategy)
|
|
264
|
+
process(data) → this.strategy.sort(data)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**When:** multiple algorithm variants, runtime selection.
|
|
268
|
+
|
|
269
|
+
### Observer — change notifications
|
|
270
|
+
```
|
|
271
|
+
// ✅ DO: subscribe/notify without tight coupling
|
|
272
|
+
class EventBus
|
|
273
|
+
subscribers = Map<string, Function[]>
|
|
274
|
+
subscribe(event, handler)
|
|
275
|
+
publish(event, data) → subscribers[event].forEach(h => h(data))
|
|
276
|
+
|
|
277
|
+
// Components subscribe without knowing about each other
|
|
278
|
+
bus.subscribe("order.created", sendEmail)
|
|
279
|
+
bus.subscribe("order.created", updateInventory)
|
|
280
|
+
bus.subscribe("order.created", logAudit)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**When:** one event source, many consumers.
|
|
284
|
+
|
|
285
|
+
### Factory Method — creation without binding to a specific class
|
|
286
|
+
```
|
|
287
|
+
// ✅ DO: factory selects the implementation
|
|
288
|
+
class NotificationFactory
|
|
289
|
+
static create(type) →
|
|
290
|
+
if type == "email" → new EmailNotification()
|
|
291
|
+
if type == "sms" → new SmsNotification()
|
|
292
|
+
if type == "push" → new PushNotification()
|
|
293
|
+
throw UnknownTypeError
|
|
294
|
+
|
|
295
|
+
// Client doesn't know about specific classes
|
|
296
|
+
notification = NotificationFactory.create(user.preference)
|
|
297
|
+
notification.send(message)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**When:** object type determined at runtime; client should not know the specifics.
|
|
301
|
+
|
|
302
|
+
### Adapter — compatibility for incompatible interfaces
|
|
303
|
+
```
|
|
304
|
+
// ✅ DO: adapter wraps a foreign interface into yours
|
|
305
|
+
interface PaymentGateway
|
|
306
|
+
charge(amount, currency) → Result
|
|
307
|
+
|
|
308
|
+
class StripeAdapter implements PaymentGateway
|
|
309
|
+
constructor(stripeClient)
|
|
310
|
+
charge(amount, currency) →
|
|
311
|
+
this.stripeClient.createPaymentIntent({
|
|
312
|
+
amount: amount * 100, // Stripe accepts cents
|
|
313
|
+
currency: currency
|
|
314
|
+
})
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**When:** integrating with an external API/library whose interface doesn't match yours.
|
|
318
|
+
|
|
319
|
+
### Facade — simple interface to a complex subsystem
|
|
320
|
+
```
|
|
321
|
+
// ✅ DO: facade hides complexity
|
|
322
|
+
class OrderFacade
|
|
323
|
+
constructor(inventory, payment, shipping, notification)
|
|
324
|
+
|
|
325
|
+
placeOrder(cart, user) →
|
|
326
|
+
inventory.reserve(cart.items)
|
|
327
|
+
payment.charge(user, cart.total)
|
|
328
|
+
shipping.createShipment(user.address, cart.items)
|
|
329
|
+
notification.sendConfirmation(user, cart)
|
|
330
|
+
|
|
331
|
+
// Client calls one method instead of four subsystems
|
|
332
|
+
orderFacade.placeOrder(cart, user)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**When:** complex subsystem of multiple components; client needs a simple entry point.
|
|
336
|
+
|
|
337
|
+
### Decorator — dynamic behavior extension
|
|
338
|
+
```
|
|
339
|
+
// ✅ DO: wrappers add behavior without modifying the original
|
|
340
|
+
interface DataSource
|
|
341
|
+
read() → Data
|
|
342
|
+
write(data) → void
|
|
343
|
+
|
|
344
|
+
class FileDataSource implements DataSource
|
|
345
|
+
class EncryptionDecorator implements DataSource
|
|
346
|
+
constructor(source: DataSource)
|
|
347
|
+
write(data) → this.source.write(encrypt(data))
|
|
348
|
+
read() → decrypt(this.source.read())
|
|
349
|
+
|
|
350
|
+
class CompressionDecorator implements DataSource
|
|
351
|
+
constructor(source: DataSource)
|
|
352
|
+
write(data) → this.source.write(compress(data))
|
|
353
|
+
|
|
354
|
+
// Combining: compression + encryption + file
|
|
355
|
+
source = new CompressionDecorator(new EncryptionDecorator(new FileDataSource()))
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**When:** need to combine behaviors in various combinations without a subclass explosion.
|
|
359
|
+
|
|
360
|
+
### Command — action encapsulated as an object
|
|
361
|
+
```
|
|
362
|
+
// ✅ DO: each action = object with execute/undo
|
|
363
|
+
interface Command
|
|
364
|
+
execute() → void
|
|
365
|
+
undo() → void
|
|
366
|
+
|
|
367
|
+
class AddItemCommand implements Command
|
|
368
|
+
constructor(cart, item)
|
|
369
|
+
execute() → this.cart.add(this.item)
|
|
370
|
+
undo() → this.cart.remove(this.item)
|
|
371
|
+
|
|
372
|
+
class CommandHistory
|
|
373
|
+
stack = []
|
|
374
|
+
execute(cmd) → cmd.execute(); stack.push(cmd)
|
|
375
|
+
undo() → stack.pop().undo()
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**When:** undo/redo, operation queues, deferred execution.
|
|
379
|
+
|
|
380
|
+
### State — behavior depends on state
|
|
381
|
+
```
|
|
382
|
+
// ✅ DO: each state = separate class
|
|
383
|
+
interface OrderState
|
|
384
|
+
next(order) → void
|
|
385
|
+
cancel(order) → void
|
|
386
|
+
|
|
387
|
+
class PendingState implements OrderState
|
|
388
|
+
next(order) → order.setState(new PaidState())
|
|
389
|
+
cancel(order) → order.setState(new CancelledState())
|
|
390
|
+
|
|
391
|
+
class PaidState implements OrderState
|
|
392
|
+
next(order) → order.setState(new ShippedState())
|
|
393
|
+
cancel(order) → throw "Cannot cancel paid order"
|
|
394
|
+
|
|
395
|
+
class Order
|
|
396
|
+
state: OrderState = new PendingState()
|
|
397
|
+
next() → this.state.next(this)
|
|
398
|
+
cancel() → this.state.cancel(this)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
```
|
|
402
|
+
// ❌ DON'T: switch/if on string status
|
|
403
|
+
class Order
|
|
404
|
+
status = "pending"
|
|
405
|
+
next()
|
|
406
|
+
if status == "pending" → status = "paid"
|
|
407
|
+
else if status == "paid" → status = "shipped"
|
|
408
|
+
// grows infinitely with new statuses
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**When:** object with multiple states and different behavior in each.
|
|
412
|
+
|
|
413
|
+
### Template Method — algorithm skeleton with overridable steps
|
|
414
|
+
```
|
|
415
|
+
// ✅ DO: base class defines the skeleton, steps are overridden
|
|
416
|
+
abstract class DataImporter
|
|
417
|
+
import(source) // template method
|
|
418
|
+
data = this.read(source)
|
|
419
|
+
validated = this.validate(data)
|
|
420
|
+
transformed = this.transform(validated)
|
|
421
|
+
this.save(transformed)
|
|
422
|
+
|
|
423
|
+
abstract read(source) → RawData
|
|
424
|
+
abstract validate(data) → ValidData
|
|
425
|
+
abstract transform(data) → FinalData
|
|
426
|
+
save(data) → db.insert(data) // shared implementation
|
|
427
|
+
|
|
428
|
+
class CsvImporter extends DataImporter
|
|
429
|
+
read(source) → parseCsv(source)
|
|
430
|
+
validate(data) → validateCsvRows(data)
|
|
431
|
+
transform(data) → mapCsvToEntities(data)
|
|
432
|
+
|
|
433
|
+
class JsonImporter extends DataImporter
|
|
434
|
+
read(source) → parseJson(source)
|
|
435
|
+
validate(data) → validateJsonSchema(data)
|
|
436
|
+
transform(data) → mapJsonToEntities(data)
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**When:** same algorithm skeleton, different implementation details.
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## 4. Architectural Patterns
|
|
444
|
+
|
|
445
|
+
### Repository — data access isolation
|
|
446
|
+
```
|
|
447
|
+
// ✅ DO: repository encapsulates storage access
|
|
448
|
+
interface UserRepository
|
|
449
|
+
findById(id) → User | null
|
|
450
|
+
findByEmail(email) → User | null
|
|
451
|
+
save(user) → User
|
|
452
|
+
delete(id) → void
|
|
453
|
+
|
|
454
|
+
class PostgresUserRepository implements UserRepository
|
|
455
|
+
findById(id) → db.query("SELECT ... WHERE id = $1", [id])
|
|
456
|
+
|
|
457
|
+
class MongoUserRepository implements UserRepository
|
|
458
|
+
findById(id) → collection.findOne({ _id: id })
|
|
459
|
+
|
|
460
|
+
// Business logic doesn't know about SQL/Mongo
|
|
461
|
+
class UserService
|
|
462
|
+
constructor(repo: UserRepository)
|
|
463
|
+
getUser(id) → this.repo.findById(id)
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
```
|
|
467
|
+
// ❌ DON'T: SQL in business logic
|
|
468
|
+
class UserService
|
|
469
|
+
getUser(id)
|
|
470
|
+
result = db.query("SELECT * FROM users WHERE id = $1", [id])
|
|
471
|
+
// Bound to PostgreSQL, cannot test without DB
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Service Layer — business logic not in controllers
|
|
475
|
+
```
|
|
476
|
+
// ✅ DO: controller → service → repository
|
|
477
|
+
class OrderController
|
|
478
|
+
constructor(orderService)
|
|
479
|
+
handleCreateOrder(req)
|
|
480
|
+
order = this.orderService.create(req.body)
|
|
481
|
+
return Response(201, order)
|
|
482
|
+
|
|
483
|
+
class OrderService
|
|
484
|
+
constructor(orderRepo, paymentGateway, inventory)
|
|
485
|
+
create(data)
|
|
486
|
+
this.inventory.reserve(data.items)
|
|
487
|
+
order = Order.create(data)
|
|
488
|
+
this.paymentGateway.charge(order.total)
|
|
489
|
+
this.orderRepo.save(order)
|
|
490
|
+
return order
|
|
491
|
+
|
|
492
|
+
// ❌ DON'T: business logic in controller
|
|
493
|
+
class OrderController
|
|
494
|
+
handleCreateOrder(req)
|
|
495
|
+
db.query("INSERT INTO orders ...") // SQL in controller
|
|
496
|
+
stripe.charge(...) // payment in controller
|
|
497
|
+
sendEmail(...) // and email too
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Dependency Injection (DI)
|
|
501
|
+
```
|
|
502
|
+
// ✅ DO: dependencies passed from outside
|
|
503
|
+
class Application
|
|
504
|
+
start()
|
|
505
|
+
db = new PostgresConnection(config.db)
|
|
506
|
+
userRepo = new PostgresUserRepository(db)
|
|
507
|
+
emailService = new SmtpEmailService(config.smtp)
|
|
508
|
+
userService = new UserService(userRepo, emailService)
|
|
509
|
+
controller = new UserController(userService)
|
|
510
|
+
server.register(controller)
|
|
511
|
+
|
|
512
|
+
// Each component receives dependencies via constructor
|
|
513
|
+
// Easy to test: substitute mock repository
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
```
|
|
517
|
+
// ❌ DON'T: components create their own dependencies
|
|
518
|
+
class UserService
|
|
519
|
+
repo = new PostgresUserRepository(new PostgresConnection("hardcoded"))
|
|
520
|
+
email = new SmtpEmailService("smtp://hardcoded")
|
|
521
|
+
// Cannot test without real PostgreSQL and SMTP
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Event-Driven — reacting to events
|
|
525
|
+
```
|
|
526
|
+
// ✅ DO: loose coupling through events
|
|
527
|
+
// Service creates order and publishes event
|
|
528
|
+
class OrderService
|
|
529
|
+
createOrder(data)
|
|
530
|
+
order = Order.create(data)
|
|
531
|
+
orderRepo.save(order)
|
|
532
|
+
eventBus.publish("order.created", { orderId: order.id })
|
|
533
|
+
|
|
534
|
+
// Independent handlers subscribed to the event
|
|
535
|
+
class InventoryHandler
|
|
536
|
+
on("order.created") → reserveItems(event.orderId)
|
|
537
|
+
|
|
538
|
+
class NotificationHandler
|
|
539
|
+
on("order.created") → sendConfirmation(event.orderId)
|
|
540
|
+
|
|
541
|
+
class AnalyticsHandler
|
|
542
|
+
on("order.created") → trackConversion(event.orderId)
|
|
543
|
+
|
|
544
|
+
// Adding a new handler → not a single line in OrderService changes
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
```
|
|
548
|
+
// ❌ DON'T: OrderService knows about all dependent components
|
|
549
|
+
class OrderService
|
|
550
|
+
createOrder(data)
|
|
551
|
+
order = Order.create(data)
|
|
552
|
+
orderRepo.save(order)
|
|
553
|
+
inventoryService.reserve(order) // tight coupling
|
|
554
|
+
notificationService.send(order) // tight coupling
|
|
555
|
+
analyticsService.track(order) // tight coupling
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### CQRS — Command Query Responsibility Segregation
|
|
559
|
+
```
|
|
560
|
+
// ✅ DO: separate models for writing and reading
|
|
561
|
+
// Command side — normalized model; business rules
|
|
562
|
+
class CreateOrderCommand
|
|
563
|
+
execute(data)
|
|
564
|
+
order = Order.create(data)
|
|
565
|
+
orderRepo.save(order)
|
|
566
|
+
eventBus.publish("order.created", order)
|
|
567
|
+
|
|
568
|
+
// Query side — denormalized model; optimized for reading
|
|
569
|
+
class OrderQueryService
|
|
570
|
+
getOrderSummary(id) →
|
|
571
|
+
readDb.query("SELECT ... FROM order_summaries WHERE id = $1", [id])
|
|
572
|
+
|
|
573
|
+
getOrdersByUser(userId, page) →
|
|
574
|
+
readDb.query("SELECT ... FROM user_orders_view WHERE user_id = $1 LIMIT ...", [userId])
|
|
575
|
+
|
|
576
|
+
// Writes update the read model via events
|
|
577
|
+
on("order.created") → updateOrderSummaryView(event)
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**When:** read and write patterns differ significantly; high read load; denormalization needed for fast queries.
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
## 5. Microservices Patterns
|
|
585
|
+
|
|
586
|
+
### Saga — distributed transactions via compensations
|
|
587
|
+
```
|
|
588
|
+
// ✅ DO: each step has a compensation (rollback)
|
|
589
|
+
class OrderSaga
|
|
590
|
+
steps = [
|
|
591
|
+
{ action: reserveInventory, compensate: releaseInventory },
|
|
592
|
+
{ action: chargePayment, compensate: refundPayment },
|
|
593
|
+
{ action: createShipment, compensate: cancelShipment },
|
|
594
|
+
{ action: sendConfirmation, compensate: sendCancellation },
|
|
595
|
+
]
|
|
596
|
+
|
|
597
|
+
execute(orderData)
|
|
598
|
+
completed = []
|
|
599
|
+
for step in steps
|
|
600
|
+
try
|
|
601
|
+
step.action(orderData)
|
|
602
|
+
completed.push(step)
|
|
603
|
+
catch error
|
|
604
|
+
// Roll back all completed steps in reverse order
|
|
605
|
+
for s in completed.reverse()
|
|
606
|
+
s.compensate(orderData)
|
|
607
|
+
throw SagaFailedError(error)
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
**When:** transaction spans multiple services; cannot use distributed 2PC.
|
|
611
|
+
|
|
612
|
+
### Circuit Breaker — protection against cascading failures
|
|
613
|
+
```
|
|
614
|
+
// ✅ DO: Circuit Breaker before calling an external service
|
|
615
|
+
class CircuitBreaker
|
|
616
|
+
state = CLOSED // normal operation
|
|
617
|
+
failureCount = 0
|
|
618
|
+
threshold = 5
|
|
619
|
+
resetTimeout = 30s
|
|
620
|
+
|
|
621
|
+
call(fn)
|
|
622
|
+
if state == OPEN
|
|
623
|
+
if elapsed > resetTimeout → state = HALF_OPEN
|
|
624
|
+
else → throw ServiceUnavailableError // don't send request
|
|
625
|
+
|
|
626
|
+
try
|
|
627
|
+
result = fn()
|
|
628
|
+
if state == HALF_OPEN → state = CLOSED; failureCount = 0
|
|
629
|
+
return result
|
|
630
|
+
catch error
|
|
631
|
+
failureCount++
|
|
632
|
+
if failureCount >= threshold → state = OPEN; startTimer()
|
|
633
|
+
throw error
|
|
634
|
+
|
|
635
|
+
// Usage
|
|
636
|
+
breaker = new CircuitBreaker()
|
|
637
|
+
userData = breaker.call(() => externalUserApi.getUser(id))
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
**When:** dependency on an external service that may fail; preventing cascading failure.
|
|
641
|
+
|
|
642
|
+
### Mediator — central coordinator for interactions
|
|
643
|
+
```
|
|
644
|
+
// ✅ DO: components communicate through mediator, not directly
|
|
645
|
+
class OrderMediator
|
|
646
|
+
handlers = Map<string, Handler>
|
|
647
|
+
|
|
648
|
+
register(commandType, handler)
|
|
649
|
+
send(command)
|
|
650
|
+
handler = this.handlers.get(command.type)
|
|
651
|
+
return handler.handle(command)
|
|
652
|
+
|
|
653
|
+
// Registration
|
|
654
|
+
mediator.register("CreateOrder", new CreateOrderHandler(orderRepo, inventory))
|
|
655
|
+
mediator.register("CancelOrder", new CancelOrderHandler(orderRepo, payment))
|
|
656
|
+
|
|
657
|
+
// Call — controller doesn't know about specific handlers
|
|
658
|
+
class OrderController
|
|
659
|
+
handleCreate(req) → mediator.send(new CreateOrderCommand(req.body))
|
|
660
|
+
handleCancel(req) → mediator.send(new CancelOrderCommand(req.params.id))
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
**When:** many interacting components; need to centralize coordination and remove direct dependencies.
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
## 6. Pattern Selection
|
|
668
|
+
|
|
669
|
+
| Problem | Pattern | Example |
|
|
670
|
+
|---------|---------|---------|
|
|
671
|
+
| Multiple algorithm variants | Strategy | Sorting algorithms, discount calculations |
|
|
672
|
+
| Reacting to changes | Observer / Event-Driven | Notifications, audit, analytics |
|
|
673
|
+
| Conditional object creation | Factory Method | Notifications: email/sms/push |
|
|
674
|
+
| Integration with foreign API | Adapter | Stripe/PayPal wrapper |
|
|
675
|
+
| Complex subsystem | Facade | Order placement (inventory + payment + shipping) |
|
|
676
|
+
| Behavior combinations | Decorator | Caching + encryption + logging |
|
|
677
|
+
| Undo/redo, queues | Command | Text editor, batch operations |
|
|
678
|
+
| Multiple object states | State | Order workflow, tasks |
|
|
679
|
+
| Same skeleton, different details | Template Method | CSV/JSON/XML import |
|
|
680
|
+
| Storage isolation | Repository | DB access without SQL in business logic |
|
|
681
|
+
| Business logic not in controllers | Service Layer | REST API |
|
|
682
|
+
| Loose component coupling | DI | Dependency injection via constructor |
|
|
683
|
+
| Different read/write models | CQRS | Dashboard + admin panel |
|
|
684
|
+
| Distributed transactions | Saga | Order: inventory → payment → shipping |
|
|
685
|
+
| Protection against cascading failures | Circuit Breaker | External APIs |
|
|
686
|
+
| Component coordination | Mediator | Request → Handler routing |
|
|
687
|
+
|
|
688
|
+
---
|
|
689
|
+
|
|
690
|
+
## See also
|
|
691
|
+
- `architecture_doc` → where to document chosen patterns
|
|
692
|
+
- `architecture_compliance_review` → verifying adherence to architecture
|
|
693
|
+
- `adr_log` → recording pattern selection decisions
|
|
694
|
+
- `code_review_checklist` → verifying pattern implementation
|
|
@@ -7,6 +7,9 @@ description: TanStack (Query v5, Table v8, Virtual v3): caching, invalidation, p
|
|
|
7
7
|
|
|
8
8
|
Copy-paste patterns for predictable work with data, tables, and virtualization.
|
|
9
9
|
|
|
10
|
+
> [!IMPORTANT]
|
|
11
|
+
> **MCP priority:** If the `tanstack-docs` MCP server is available, **always use its tools first** (`tanstack_doc`, `tanstack_search_docs`, `listTanStackAddOns`, `getAddOnDetails`, `createTanStackApplication`) — they provide up-to-date documentation directly from official TanStack sources. Use the patterns in this skill as a fallback or as additional architectural context (fetcher/adapter/hook, queryKey factory, optimistic updates, virtualization) when the MCP is unavailable or does not contain the required information.
|
|
12
|
+
|
|
10
13
|
**Sections:**
|
|
11
14
|
1. [Query: fetcher/adapter architecture](#1-query-architecture)
|
|
12
15
|
2. [Query: caching and staleTime](#2-query-cache)
|