@ryuenn3123/agentic-senior-core 1.9.0 → 1.9.1
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/.agent-context/blueprints/mobile-app.md +21 -21
- package/.agent-context/policies/llm-judge-threshold.json +19 -19
- package/.agent-context/profiles/platform.md +13 -13
- package/.agent-context/profiles/regulated.md +13 -13
- package/.agent-context/profiles/startup.md +13 -13
- package/.agent-context/review-checklists/frontend-skill-parity.md +28 -28
- package/.agent-context/review-checklists/frontend-usability.md +33 -33
- package/.agent-context/review-checklists/release-operations.md +29 -29
- package/.agent-context/skills/README.md +62 -62
- package/.agent-context/skills/backend/README.md +67 -67
- package/.agent-context/skills/backend/architecture.md +360 -360
- package/.agent-context/skills/backend/data-access.md +230 -230
- package/.agent-context/skills/backend/errors.md +137 -137
- package/.agent-context/skills/backend/validation.md +116 -116
- package/.agent-context/skills/backend.md +28 -28
- package/.agent-context/skills/cli/README.md +49 -49
- package/.agent-context/skills/cli/init.md +37 -37
- package/.agent-context/skills/cli/output.md +35 -35
- package/.agent-context/skills/cli/upgrade.md +37 -37
- package/.agent-context/skills/cli.md +28 -28
- package/.agent-context/skills/distribution/README.md +18 -18
- package/.agent-context/skills/distribution/compatibility.md +31 -31
- package/.agent-context/skills/distribution/publish.md +36 -36
- package/.agent-context/skills/distribution/rollback.md +31 -31
- package/.agent-context/skills/distribution.md +28 -28
- package/.agent-context/skills/frontend/README.md +35 -35
- package/.agent-context/skills/frontend/accessibility.md +107 -107
- package/.agent-context/skills/frontend/motion.md +66 -66
- package/.agent-context/skills/frontend/performance.md +62 -62
- package/.agent-context/skills/frontend/ui-architecture.md +128 -128
- package/.agent-context/skills/frontend.md +29 -29
- package/.agent-context/skills/fullstack/README.md +18 -18
- package/.agent-context/skills/fullstack/contracts.md +52 -52
- package/.agent-context/skills/fullstack/end-to-end.md +41 -41
- package/.agent-context/skills/fullstack/feature-slicing.md +64 -64
- package/.agent-context/skills/fullstack.md +26 -26
- package/.agent-context/skills/index.json +107 -107
- package/.agent-context/skills/review-quality/README.md +18 -18
- package/.agent-context/skills/review-quality/benchmark.md +29 -29
- package/.agent-context/skills/review-quality/planning.md +37 -37
- package/.agent-context/skills/review-quality/security.md +33 -33
- package/.agent-context/skills/review-quality.md +27 -27
- package/.agent-context/stacks/flutter.md +16 -16
- package/.agent-context/stacks/react-native.md +16 -16
- package/.agent-context/state/architecture-map.md +25 -25
- package/.agent-context/state/benchmark-analysis.json +431 -431
- package/.agent-context/state/benchmark-thresholds.json +10 -10
- package/.agent-context/state/benchmark-watchlist.json +19 -19
- package/.agent-context/state/dependency-map.md +32 -32
- package/.agent-context/state/skill-platform.json +38 -38
- package/.agent-override.md +36 -36
- package/.cursorrules +140 -140
- package/.github/ISSUE_TEMPLATE/v1.7-frontend-work-item.yml +54 -54
- package/.github/workflows/benchmark-detection.yml +38 -38
- package/.github/workflows/benchmark-intelligence.yml +50 -50
- package/.github/workflows/frontend-usability-gate.yml +36 -36
- package/.github/workflows/publish.yml +32 -0
- package/.github/workflows/release-gate.yml +32 -32
- package/.github/workflows/sbom-compliance.yml +32 -32
- package/.windsurfrules +106 -106
- package/AGENTS.md +181 -181
- package/README.md +318 -318
- package/bin/agentic-senior-core.js +1556 -1556
- package/mcp.json +92 -92
- package/package.json +1 -1
- package/scripts/benchmark-gate.mjs +121 -121
- package/scripts/benchmark-intelligence.mjs +140 -140
- package/scripts/detection-benchmark.mjs +138 -138
- package/scripts/frontend-usability-audit.mjs +87 -87
- package/scripts/generate-sbom.mjs +61 -61
- package/scripts/init-project.ps1 +104 -104
- package/scripts/llm-judge.mjs +664 -664
- package/scripts/release-gate.mjs +116 -116
- package/scripts/skill-tier-policy.mjs +75 -75
- package/scripts/validate.mjs +636 -636
|
@@ -1,361 +1,361 @@
|
|
|
1
|
-
# Backend Architecture
|
|
2
|
-
|
|
3
|
-
**Tier:** EXPERT | **Source:** awesome-copilot (layering) + antigravity (microservices) + minimax (project structure)
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
Backend architecture defines how code is organized, how concerns separate, and how services scale. Three critical decisions:
|
|
8
|
-
1. **Layered separation** - Transport (HTTP) vs Service (logic) vs Repository (data)
|
|
9
|
-
2. **Monolith or microservices** - When to split, when to keep together
|
|
10
|
-
3. **Dependency direction** - Which layers can import which
|
|
11
|
-
|
|
12
|
-
Wrong choices here create spaghetti code, circular dependencies, and costly rewrites. Right choices enable independent scaling, testing, and team autonomy.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Part 1: Layered Architecture (Transport -> Service -> Repository)
|
|
17
|
-
|
|
18
|
-
### The Model
|
|
19
|
-
|
|
20
|
-
Clean architecture separates concerns into independent layers:
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
┌─────────────────────────────────┐
|
|
24
|
-
│ HTTP / Handlers / Middleware │ <- TRANSPORT LAYER
|
|
25
|
-
├─────────────────────────────────┤
|
|
26
|
-
│ Business Logic / Orchestration │ <- SERVICE LAYER
|
|
27
|
-
├─────────────────────────────────┤
|
|
28
|
-
│ Data Access / Queries / Caching│ <- REPOSITORY LAYER
|
|
29
|
-
├─────────────────────────────────┤
|
|
30
|
-
│ External APIs / Databases │ <- INFRASTRUCTURE
|
|
31
|
-
└─────────────────────────────────┘
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
**Dependency Rule:** Layer below can NEVER import layer above.
|
|
35
|
-
- Transport CAN import Service
|
|
36
|
-
- Service CAN import Repository
|
|
37
|
-
- Repository can NEVER import Service
|
|
38
|
-
- Service can NEVER import Transport
|
|
39
|
-
|
|
40
|
-
### TRANSPORT LAYER (HTTP Handlers, Middleware)
|
|
41
|
-
|
|
42
|
-
**Responsibility:** Parse HTTP requests, serialize responses, handle authentication, logging middleware.
|
|
43
|
-
|
|
44
|
-
**What goes here:**
|
|
45
|
-
- Request validation (type, format checks)
|
|
46
|
-
- Middleware (auth, CORS, rate limiting, logging)
|
|
47
|
-
- Route handlers (receive request -> call service -> return response)
|
|
48
|
-
- Response serialization (JSON, XML, protobuf)
|
|
49
|
-
|
|
50
|
-
**What does NOT go here:**
|
|
51
|
-
- Business logic (validation rules, calculations, state transitions)
|
|
52
|
-
- Database queries
|
|
53
|
-
- Feature flags, configuration decisions
|
|
54
|
-
|
|
55
|
-
**Example (Node.js + Express):**
|
|
56
|
-
```javascript
|
|
57
|
-
// CORRECT: Transport layer
|
|
58
|
-
app.post('/payments/charge',
|
|
59
|
-
authenticate, // Middleware
|
|
60
|
-
validateRequest(chargeSchema), // Format check
|
|
61
|
-
async (req, res) => {
|
|
62
|
-
const result = await paymentService.charge({
|
|
63
|
-
amount: req.body.amount,
|
|
64
|
-
customerId: req.body.customerId,
|
|
65
|
-
idempotencyKey: req.headers['idempotency-key']
|
|
66
|
-
});
|
|
67
|
-
res.json(result);
|
|
68
|
-
}
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
// Service layer has business logic:
|
|
72
|
-
async function charge({ amount, customerId, idempotencyKey }) {
|
|
73
|
-
// Check customer credit, calculate fees, call repository
|
|
74
|
-
// NO HTTP HERE
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**Anti-Pattern:** Business logic in handler
|
|
79
|
-
|
|
80
|
-
```javascript
|
|
81
|
-
// WRONG: Business logic in transport
|
|
82
|
-
app.post('/payments/charge', async (req, res) => {
|
|
83
|
-
const customer = await db.query('SELECT * FROM customers WHERE id = ?', customerId);
|
|
84
|
-
if (customer.balance < amount) throw new Error('Insufficient funds'); // <- Logic?
|
|
85
|
-
const fee = amount * 0.03 + 0.30; // <- Math in handler?
|
|
86
|
-
const charged = await db.query('UPDATE customers SET balance = balance - ?', amount + fee);
|
|
87
|
-
// ...
|
|
88
|
-
});
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### SERVICE LAYER (Business Logic, Orchestration)
|
|
92
|
-
|
|
93
|
-
**Responsibility:** Business rules, data transformations, orchestration across repositories, feature flags, error handling.
|
|
94
|
-
|
|
95
|
-
**What goes here:**
|
|
96
|
-
- Validation rules (customer eligibility, amount limits)
|
|
97
|
-
- Business calculations (fees, commissions, discounts)
|
|
98
|
-
- Orchestration (call repo A, then repo B, handle failure)
|
|
99
|
-
- Idempotency keys for distributed transactions
|
|
100
|
-
- Feature flags / circuit breakers
|
|
101
|
-
|
|
102
|
-
**What does NOT go here:**
|
|
103
|
-
- Database queries (use Repository)
|
|
104
|
-
- HTTP parsing, serialization (use Transport)
|
|
105
|
-
- External API calls directly (wrap in Repository-like abstraction)
|
|
106
|
-
|
|
107
|
-
**Example:**
|
|
108
|
-
```javascript
|
|
109
|
-
class PaymentService {
|
|
110
|
-
constructor(customerRepo, paymentRepo, ledgerRepo) {
|
|
111
|
-
this.customerRepo = customerRepo;
|
|
112
|
-
this.paymentRepo = paymentRepo;
|
|
113
|
-
this.ledgerRepo = ledgerRepo;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async charge({ amount, customerId, idempotencyKey }) {
|
|
117
|
-
// Check idempotency first (prevent double-charge)
|
|
118
|
-
const existing = await this.paymentRepo.findByIdempotencyKey(idempotencyKey);
|
|
119
|
-
if (existing) return existing; // Already charged
|
|
120
|
-
|
|
121
|
-
// Business validation
|
|
122
|
-
const customer = await this.customerRepo.findById(customerId);
|
|
123
|
-
if (!customer) throw new NotFoundError('Customer not found');
|
|
124
|
-
if (customer.status !== 'active') throw new BusinessError('Account inactive');
|
|
125
|
-
if (amount < 50 || amount > 100000) throw new ValidationError('Amount out of range');
|
|
126
|
-
|
|
127
|
-
// Calculate fees
|
|
128
|
-
const fee = this._calculateFee(amount, customer.tier);
|
|
129
|
-
const total = amount + fee;
|
|
130
|
-
|
|
131
|
-
// Orchestrate transaction
|
|
132
|
-
try {
|
|
133
|
-
const payment = await this.paymentRepo.create({
|
|
134
|
-
customerId,
|
|
135
|
-
amount,
|
|
136
|
-
fee,
|
|
137
|
-
total,
|
|
138
|
-
idempotencyKey,
|
|
139
|
-
status: 'pending'
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
await this.ledgerRepo.debit({
|
|
143
|
-
customerId,
|
|
144
|
-
amount: total,
|
|
145
|
-
reason: `Payment ${payment.id}`,
|
|
146
|
-
paymentId: payment.id
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
await this.paymentRepo.update(payment.id, { status: 'completed' });
|
|
150
|
-
return payment;
|
|
151
|
-
} catch (err) {
|
|
152
|
-
await this.paymentRepo.update(payment.id, { status: 'failed', error: err.message });
|
|
153
|
-
throw err;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
_calculateFee(amount, tier) {
|
|
158
|
-
const baseRate = { silver: 0.029, gold: 0.019, platinum: 0.009 }[tier];
|
|
159
|
-
const flatFee = { silver: 0.50, gold: 0.30, platinum: 0 }[tier];
|
|
160
|
-
return Math.round(amount * baseRate * 100) / 100 + flatFee;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### REPOSITORY LAYER (Data Access, Queries)
|
|
166
|
-
|
|
167
|
-
**Responsibility:** Data retrieval, persistence, query optimization, caching, connection pooling.
|
|
168
|
-
|
|
169
|
-
**What goes here:**
|
|
170
|
-
- Database queries (SELECT, INSERT, UPDATE, DELETE)
|
|
171
|
-
- Prepared statements, parameterized queries
|
|
172
|
-
- Indexes, query optimization
|
|
173
|
-
- Batch operations
|
|
174
|
-
- Query caching (cache-aside, write-through)
|
|
175
|
-
- Connection pooling
|
|
176
|
-
|
|
177
|
-
**What does NOT go here:**
|
|
178
|
-
- Business logic (decisions based on data)
|
|
179
|
-
- HTTP handling
|
|
180
|
-
- External API calls (unless wrapping as data source)
|
|
181
|
-
|
|
182
|
-
**Example:**
|
|
183
|
-
```javascript
|
|
184
|
-
class PaymentRepository {
|
|
185
|
-
constructor(db) {
|
|
186
|
-
this.db = db;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async findByIdempotencyKey(key) {
|
|
190
|
-
return this.db.one(
|
|
191
|
-
'SELECT * FROM payments WHERE idempotency_key = $1',
|
|
192
|
-
[key] // Parameterized query
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async create(payment) {
|
|
197
|
-
return this.db.one(
|
|
198
|
-
`INSERT INTO payments
|
|
199
|
-
(customer_id, amount, fee, total, idempotency_key, status, created_at)
|
|
200
|
-
VALUES ($1, $2, $3, $4, $5, $6, NOW())
|
|
201
|
-
RETURNING *`,
|
|
202
|
-
[payment.customerId, payment.amount, payment.fee, payment.total,
|
|
203
|
-
payment.idempotencyKey, payment.status]
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async update(id, updates) {
|
|
208
|
-
const fields = [];
|
|
209
|
-
const values = [];
|
|
210
|
-
let paramCount = 1;
|
|
211
|
-
for (const [key, val] of Object.entries(updates)) {
|
|
212
|
-
fields.push(`${key} = $${paramCount++}`);
|
|
213
|
-
values.push(val);
|
|
214
|
-
}
|
|
215
|
-
values.push(id);
|
|
216
|
-
return this.db.one(
|
|
217
|
-
`UPDATE payments SET ${fields.join(', ')} WHERE id = $${paramCount} RETURNING *`,
|
|
218
|
-
values
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
---
|
|
225
|
-
|
|
226
|
-
## Part 2: Monolith vs Microservices (Decision Framework)
|
|
227
|
-
|
|
228
|
-
### When to Stay Monolithic
|
|
229
|
-
|
|
230
|
-
**Keep monolith if:**
|
|
231
|
-
- Team < 30 people (communication overhead still low)
|
|
232
|
-
- Feature dependencies high (changing one feature requires touching multiple areas)
|
|
233
|
-
- Deployment frequency < weekly (update entire system at once is acceptable)
|
|
234
|
-
- Data strongly coupled (customers, orders, payments in one domain)
|
|
235
|
-
- Performance latency-sensitive < 100ms (in-process calls beat RPC)
|
|
236
|
-
|
|
237
|
-
**Monolith advantages:**
|
|
238
|
-
- Single deployment unit (easier to roll forward/back)
|
|
239
|
-
- ACID transactions easy (in same database)
|
|
240
|
-
- Debugging simpler (logs in one place, single memory space)
|
|
241
|
-
- Performance: in-memory function calls vs HTTP RPC
|
|
242
|
-
|
|
243
|
-
**Example: Monolithic e-commerce platform**
|
|
244
|
-
```
|
|
245
|
-
monolith/
|
|
246
|
-
├── transport/
|
|
247
|
-
│ ├── auth.js
|
|
248
|
-
│ ├── products.js
|
|
249
|
-
│ ├── orders.js
|
|
250
|
-
│ └── payments.js
|
|
251
|
-
├── services/
|
|
252
|
-
│ ├── authService.js
|
|
253
|
-
│ ├── productService.js
|
|
254
|
-
│ ├── orderService.js
|
|
255
|
-
│ └── paymentService.js
|
|
256
|
-
└── repositories/
|
|
257
|
-
├── userRepo.js
|
|
258
|
-
├── productRepo.js
|
|
259
|
-
├── orderRepo.js
|
|
260
|
-
└── paymentRepo.js
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
All services run in same process, same database. Easy to refactor, deploy, test.
|
|
264
|
-
|
|
265
|
-
### When to Split to Microservices
|
|
266
|
-
|
|
267
|
-
**Split ONLY if:**
|
|
268
|
-
- Team > 30 people (need autonomy + independent deployments)
|
|
269
|
-
- Services truly independent (different databases, different deployment cadences)
|
|
270
|
-
- You can tolerate 100-500ms RPC latency between services
|
|
271
|
-
- Each service has independent scaling needs
|
|
272
|
-
|
|
273
|
-
**Microservices disadvantages:**
|
|
274
|
-
- Distributed transactions (eventual consistency or sagas)
|
|
275
|
-
- Debugging spans multiple services/logs (correlation IDs mandatory)
|
|
276
|
-
- RPC latency adds up (cascade failures likely)
|
|
277
|
-
- Ops complexity increases 10x (monitoring, health checks, circuit breakers)
|
|
278
|
-
|
|
279
|
-
**Trigger for splitting:** When layered architecture + teamwork alignment breaks down.
|
|
280
|
-
|
|
281
|
-
**NOT OK to split:**
|
|
282
|
-
- Customer service and Order service can't be truly independent (customers have orders)
|
|
283
|
-
- Payment and Order service tightly coupled (can't process payment independent of order state)
|
|
284
|
-
|
|
285
|
-
**OK to split:**
|
|
286
|
-
- User authentication (separate service, used by many)
|
|
287
|
-
- Notification service (email/SMS, independent of order flow)
|
|
288
|
-
- Analytics service (read-only, independent queries)
|
|
289
|
-
|
|
290
|
-
**Strangler Fig Pattern (low-risk migration):**
|
|
291
|
-
|
|
292
|
-
Start monolithic. When it's time to extract Payment service:
|
|
293
|
-
1. Keep monolith running
|
|
294
|
-
2. Create Payment microservice alongside
|
|
295
|
-
3. Route new Payment requests -> new service
|
|
296
|
-
4. Keep old requests going to monolith's PaymentService
|
|
297
|
-
5. Over months, migrate & retire old code
|
|
298
|
-
6. Remove dependency from monolith
|
|
299
|
-
|
|
300
|
-
```javascript
|
|
301
|
-
// Monolith, gradually being strangled:
|
|
302
|
-
async function charge(customerId, amount) {
|
|
303
|
-
if (featureFlags.usePaymentMicroservice) {
|
|
304
|
-
// Call remote service
|
|
305
|
-
return await paymentMicroservice.charge({ customerId, amount });
|
|
306
|
-
} else {
|
|
307
|
-
// Old in-process service
|
|
308
|
-
return await paymentService.charge({ customerId, amount });
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
---
|
|
314
|
-
|
|
315
|
-
## Part 3: Dependency Management (ABOVE LINE)
|
|
316
|
-
|
|
317
|
-
### The Problem
|
|
318
|
-
|
|
319
|
-
As codebases grow, circular dependencies emerge:
|
|
320
|
-
- Service imports Repository
|
|
321
|
-
- Repository imports utility in Service (closes circle)
|
|
322
|
-
- Creates tight coupling, hard to test, risky refactors
|
|
323
|
-
|
|
324
|
-
### The Solution: Dependency Auditor
|
|
325
|
-
|
|
326
|
-
**Enforce dependency direction at build time** (ABOVE LINE improvement not in any benchmark repo).
|
|
327
|
-
|
|
328
|
-
**Check:**
|
|
329
|
-
1. Transport layer files import only Transport + Service
|
|
330
|
-
2. Service layer files import only Service + Repository
|
|
331
|
-
3. Repository layer files import only Repository (no Service)
|
|
332
|
-
4. No circular imports within layer
|
|
333
|
-
|
|
334
|
-
**Tool:**
|
|
335
|
-
```bash
|
|
336
|
-
npm run audit:dependencies # Pre-commit gate
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
---
|
|
340
|
-
|
|
341
|
-
## Checklist: Did You Get Architecture Right?
|
|
342
|
-
|
|
343
|
-
Before shipping a new service, verify:
|
|
344
|
-
|
|
345
|
-
- [ ] **Layer separation:** Transport doesn't contain business logic
|
|
346
|
-
- [ ] **Dependency direction:** No circles (Service imports Repo only, not vice versa)
|
|
347
|
-
- [ ] **Repository abstraction:** No business logic in SQL queries
|
|
348
|
-
- [ ] **Service orchestration:** Complex flows live in Service, not Transport
|
|
349
|
-
- [ ] **Error handling:** Errors typed + recovery strategies clear
|
|
350
|
-
- [ ] **Idempotency:** Distributed transactions have idempotency keys
|
|
351
|
-
- [ ] **Feature-based:** Related code lives together, not scattered across service/util folders
|
|
352
|
-
- [ ] **Testing:** Unit tests mock Repository (test business logic), no DB
|
|
353
|
-
- [ ] **Documentation:** README explains layer separation for this service
|
|
354
|
-
|
|
355
|
-
---
|
|
356
|
-
|
|
357
|
-
## References
|
|
358
|
-
|
|
359
|
-
- [Awesome-Copilot Architecture](https://github.com/github/awesome-copilot)
|
|
360
|
-
- [Antigravity Microservices](https://github.com/sickn33/antigravity-awesome-skills)
|
|
1
|
+
# Backend Architecture
|
|
2
|
+
|
|
3
|
+
**Tier:** EXPERT | **Source:** awesome-copilot (layering) + antigravity (microservices) + minimax (project structure)
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Backend architecture defines how code is organized, how concerns separate, and how services scale. Three critical decisions:
|
|
8
|
+
1. **Layered separation** - Transport (HTTP) vs Service (logic) vs Repository (data)
|
|
9
|
+
2. **Monolith or microservices** - When to split, when to keep together
|
|
10
|
+
3. **Dependency direction** - Which layers can import which
|
|
11
|
+
|
|
12
|
+
Wrong choices here create spaghetti code, circular dependencies, and costly rewrites. Right choices enable independent scaling, testing, and team autonomy.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Part 1: Layered Architecture (Transport -> Service -> Repository)
|
|
17
|
+
|
|
18
|
+
### The Model
|
|
19
|
+
|
|
20
|
+
Clean architecture separates concerns into independent layers:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌─────────────────────────────────┐
|
|
24
|
+
│ HTTP / Handlers / Middleware │ <- TRANSPORT LAYER
|
|
25
|
+
├─────────────────────────────────┤
|
|
26
|
+
│ Business Logic / Orchestration │ <- SERVICE LAYER
|
|
27
|
+
├─────────────────────────────────┤
|
|
28
|
+
│ Data Access / Queries / Caching│ <- REPOSITORY LAYER
|
|
29
|
+
├─────────────────────────────────┤
|
|
30
|
+
│ External APIs / Databases │ <- INFRASTRUCTURE
|
|
31
|
+
└─────────────────────────────────┘
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Dependency Rule:** Layer below can NEVER import layer above.
|
|
35
|
+
- Transport CAN import Service
|
|
36
|
+
- Service CAN import Repository
|
|
37
|
+
- Repository can NEVER import Service
|
|
38
|
+
- Service can NEVER import Transport
|
|
39
|
+
|
|
40
|
+
### TRANSPORT LAYER (HTTP Handlers, Middleware)
|
|
41
|
+
|
|
42
|
+
**Responsibility:** Parse HTTP requests, serialize responses, handle authentication, logging middleware.
|
|
43
|
+
|
|
44
|
+
**What goes here:**
|
|
45
|
+
- Request validation (type, format checks)
|
|
46
|
+
- Middleware (auth, CORS, rate limiting, logging)
|
|
47
|
+
- Route handlers (receive request -> call service -> return response)
|
|
48
|
+
- Response serialization (JSON, XML, protobuf)
|
|
49
|
+
|
|
50
|
+
**What does NOT go here:**
|
|
51
|
+
- Business logic (validation rules, calculations, state transitions)
|
|
52
|
+
- Database queries
|
|
53
|
+
- Feature flags, configuration decisions
|
|
54
|
+
|
|
55
|
+
**Example (Node.js + Express):**
|
|
56
|
+
```javascript
|
|
57
|
+
// CORRECT: Transport layer
|
|
58
|
+
app.post('/payments/charge',
|
|
59
|
+
authenticate, // Middleware
|
|
60
|
+
validateRequest(chargeSchema), // Format check
|
|
61
|
+
async (req, res) => {
|
|
62
|
+
const result = await paymentService.charge({
|
|
63
|
+
amount: req.body.amount,
|
|
64
|
+
customerId: req.body.customerId,
|
|
65
|
+
idempotencyKey: req.headers['idempotency-key']
|
|
66
|
+
});
|
|
67
|
+
res.json(result);
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Service layer has business logic:
|
|
72
|
+
async function charge({ amount, customerId, idempotencyKey }) {
|
|
73
|
+
// Check customer credit, calculate fees, call repository
|
|
74
|
+
// NO HTTP HERE
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Anti-Pattern:** Business logic in handler
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// WRONG: Business logic in transport
|
|
82
|
+
app.post('/payments/charge', async (req, res) => {
|
|
83
|
+
const customer = await db.query('SELECT * FROM customers WHERE id = ?', customerId);
|
|
84
|
+
if (customer.balance < amount) throw new Error('Insufficient funds'); // <- Logic?
|
|
85
|
+
const fee = amount * 0.03 + 0.30; // <- Math in handler?
|
|
86
|
+
const charged = await db.query('UPDATE customers SET balance = balance - ?', amount + fee);
|
|
87
|
+
// ...
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### SERVICE LAYER (Business Logic, Orchestration)
|
|
92
|
+
|
|
93
|
+
**Responsibility:** Business rules, data transformations, orchestration across repositories, feature flags, error handling.
|
|
94
|
+
|
|
95
|
+
**What goes here:**
|
|
96
|
+
- Validation rules (customer eligibility, amount limits)
|
|
97
|
+
- Business calculations (fees, commissions, discounts)
|
|
98
|
+
- Orchestration (call repo A, then repo B, handle failure)
|
|
99
|
+
- Idempotency keys for distributed transactions
|
|
100
|
+
- Feature flags / circuit breakers
|
|
101
|
+
|
|
102
|
+
**What does NOT go here:**
|
|
103
|
+
- Database queries (use Repository)
|
|
104
|
+
- HTTP parsing, serialization (use Transport)
|
|
105
|
+
- External API calls directly (wrap in Repository-like abstraction)
|
|
106
|
+
|
|
107
|
+
**Example:**
|
|
108
|
+
```javascript
|
|
109
|
+
class PaymentService {
|
|
110
|
+
constructor(customerRepo, paymentRepo, ledgerRepo) {
|
|
111
|
+
this.customerRepo = customerRepo;
|
|
112
|
+
this.paymentRepo = paymentRepo;
|
|
113
|
+
this.ledgerRepo = ledgerRepo;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async charge({ amount, customerId, idempotencyKey }) {
|
|
117
|
+
// Check idempotency first (prevent double-charge)
|
|
118
|
+
const existing = await this.paymentRepo.findByIdempotencyKey(idempotencyKey);
|
|
119
|
+
if (existing) return existing; // Already charged
|
|
120
|
+
|
|
121
|
+
// Business validation
|
|
122
|
+
const customer = await this.customerRepo.findById(customerId);
|
|
123
|
+
if (!customer) throw new NotFoundError('Customer not found');
|
|
124
|
+
if (customer.status !== 'active') throw new BusinessError('Account inactive');
|
|
125
|
+
if (amount < 50 || amount > 100000) throw new ValidationError('Amount out of range');
|
|
126
|
+
|
|
127
|
+
// Calculate fees
|
|
128
|
+
const fee = this._calculateFee(amount, customer.tier);
|
|
129
|
+
const total = amount + fee;
|
|
130
|
+
|
|
131
|
+
// Orchestrate transaction
|
|
132
|
+
try {
|
|
133
|
+
const payment = await this.paymentRepo.create({
|
|
134
|
+
customerId,
|
|
135
|
+
amount,
|
|
136
|
+
fee,
|
|
137
|
+
total,
|
|
138
|
+
idempotencyKey,
|
|
139
|
+
status: 'pending'
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
await this.ledgerRepo.debit({
|
|
143
|
+
customerId,
|
|
144
|
+
amount: total,
|
|
145
|
+
reason: `Payment ${payment.id}`,
|
|
146
|
+
paymentId: payment.id
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await this.paymentRepo.update(payment.id, { status: 'completed' });
|
|
150
|
+
return payment;
|
|
151
|
+
} catch (err) {
|
|
152
|
+
await this.paymentRepo.update(payment.id, { status: 'failed', error: err.message });
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_calculateFee(amount, tier) {
|
|
158
|
+
const baseRate = { silver: 0.029, gold: 0.019, platinum: 0.009 }[tier];
|
|
159
|
+
const flatFee = { silver: 0.50, gold: 0.30, platinum: 0 }[tier];
|
|
160
|
+
return Math.round(amount * baseRate * 100) / 100 + flatFee;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### REPOSITORY LAYER (Data Access, Queries)
|
|
166
|
+
|
|
167
|
+
**Responsibility:** Data retrieval, persistence, query optimization, caching, connection pooling.
|
|
168
|
+
|
|
169
|
+
**What goes here:**
|
|
170
|
+
- Database queries (SELECT, INSERT, UPDATE, DELETE)
|
|
171
|
+
- Prepared statements, parameterized queries
|
|
172
|
+
- Indexes, query optimization
|
|
173
|
+
- Batch operations
|
|
174
|
+
- Query caching (cache-aside, write-through)
|
|
175
|
+
- Connection pooling
|
|
176
|
+
|
|
177
|
+
**What does NOT go here:**
|
|
178
|
+
- Business logic (decisions based on data)
|
|
179
|
+
- HTTP handling
|
|
180
|
+
- External API calls (unless wrapping as data source)
|
|
181
|
+
|
|
182
|
+
**Example:**
|
|
183
|
+
```javascript
|
|
184
|
+
class PaymentRepository {
|
|
185
|
+
constructor(db) {
|
|
186
|
+
this.db = db;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async findByIdempotencyKey(key) {
|
|
190
|
+
return this.db.one(
|
|
191
|
+
'SELECT * FROM payments WHERE idempotency_key = $1',
|
|
192
|
+
[key] // Parameterized query
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async create(payment) {
|
|
197
|
+
return this.db.one(
|
|
198
|
+
`INSERT INTO payments
|
|
199
|
+
(customer_id, amount, fee, total, idempotency_key, status, created_at)
|
|
200
|
+
VALUES ($1, $2, $3, $4, $5, $6, NOW())
|
|
201
|
+
RETURNING *`,
|
|
202
|
+
[payment.customerId, payment.amount, payment.fee, payment.total,
|
|
203
|
+
payment.idempotencyKey, payment.status]
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async update(id, updates) {
|
|
208
|
+
const fields = [];
|
|
209
|
+
const values = [];
|
|
210
|
+
let paramCount = 1;
|
|
211
|
+
for (const [key, val] of Object.entries(updates)) {
|
|
212
|
+
fields.push(`${key} = $${paramCount++}`);
|
|
213
|
+
values.push(val);
|
|
214
|
+
}
|
|
215
|
+
values.push(id);
|
|
216
|
+
return this.db.one(
|
|
217
|
+
`UPDATE payments SET ${fields.join(', ')} WHERE id = $${paramCount} RETURNING *`,
|
|
218
|
+
values
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Part 2: Monolith vs Microservices (Decision Framework)
|
|
227
|
+
|
|
228
|
+
### When to Stay Monolithic
|
|
229
|
+
|
|
230
|
+
**Keep monolith if:**
|
|
231
|
+
- Team < 30 people (communication overhead still low)
|
|
232
|
+
- Feature dependencies high (changing one feature requires touching multiple areas)
|
|
233
|
+
- Deployment frequency < weekly (update entire system at once is acceptable)
|
|
234
|
+
- Data strongly coupled (customers, orders, payments in one domain)
|
|
235
|
+
- Performance latency-sensitive < 100ms (in-process calls beat RPC)
|
|
236
|
+
|
|
237
|
+
**Monolith advantages:**
|
|
238
|
+
- Single deployment unit (easier to roll forward/back)
|
|
239
|
+
- ACID transactions easy (in same database)
|
|
240
|
+
- Debugging simpler (logs in one place, single memory space)
|
|
241
|
+
- Performance: in-memory function calls vs HTTP RPC
|
|
242
|
+
|
|
243
|
+
**Example: Monolithic e-commerce platform**
|
|
244
|
+
```
|
|
245
|
+
monolith/
|
|
246
|
+
├── transport/
|
|
247
|
+
│ ├── auth.js
|
|
248
|
+
│ ├── products.js
|
|
249
|
+
│ ├── orders.js
|
|
250
|
+
│ └── payments.js
|
|
251
|
+
├── services/
|
|
252
|
+
│ ├── authService.js
|
|
253
|
+
│ ├── productService.js
|
|
254
|
+
│ ├── orderService.js
|
|
255
|
+
│ └── paymentService.js
|
|
256
|
+
└── repositories/
|
|
257
|
+
├── userRepo.js
|
|
258
|
+
├── productRepo.js
|
|
259
|
+
├── orderRepo.js
|
|
260
|
+
└── paymentRepo.js
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
All services run in same process, same database. Easy to refactor, deploy, test.
|
|
264
|
+
|
|
265
|
+
### When to Split to Microservices
|
|
266
|
+
|
|
267
|
+
**Split ONLY if:**
|
|
268
|
+
- Team > 30 people (need autonomy + independent deployments)
|
|
269
|
+
- Services truly independent (different databases, different deployment cadences)
|
|
270
|
+
- You can tolerate 100-500ms RPC latency between services
|
|
271
|
+
- Each service has independent scaling needs
|
|
272
|
+
|
|
273
|
+
**Microservices disadvantages:**
|
|
274
|
+
- Distributed transactions (eventual consistency or sagas)
|
|
275
|
+
- Debugging spans multiple services/logs (correlation IDs mandatory)
|
|
276
|
+
- RPC latency adds up (cascade failures likely)
|
|
277
|
+
- Ops complexity increases 10x (monitoring, health checks, circuit breakers)
|
|
278
|
+
|
|
279
|
+
**Trigger for splitting:** When layered architecture + teamwork alignment breaks down.
|
|
280
|
+
|
|
281
|
+
**NOT OK to split:**
|
|
282
|
+
- Customer service and Order service can't be truly independent (customers have orders)
|
|
283
|
+
- Payment and Order service tightly coupled (can't process payment independent of order state)
|
|
284
|
+
|
|
285
|
+
**OK to split:**
|
|
286
|
+
- User authentication (separate service, used by many)
|
|
287
|
+
- Notification service (email/SMS, independent of order flow)
|
|
288
|
+
- Analytics service (read-only, independent queries)
|
|
289
|
+
|
|
290
|
+
**Strangler Fig Pattern (low-risk migration):**
|
|
291
|
+
|
|
292
|
+
Start monolithic. When it's time to extract Payment service:
|
|
293
|
+
1. Keep monolith running
|
|
294
|
+
2. Create Payment microservice alongside
|
|
295
|
+
3. Route new Payment requests -> new service
|
|
296
|
+
4. Keep old requests going to monolith's PaymentService
|
|
297
|
+
5. Over months, migrate & retire old code
|
|
298
|
+
6. Remove dependency from monolith
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
// Monolith, gradually being strangled:
|
|
302
|
+
async function charge(customerId, amount) {
|
|
303
|
+
if (featureFlags.usePaymentMicroservice) {
|
|
304
|
+
// Call remote service
|
|
305
|
+
return await paymentMicroservice.charge({ customerId, amount });
|
|
306
|
+
} else {
|
|
307
|
+
// Old in-process service
|
|
308
|
+
return await paymentService.charge({ customerId, amount });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Part 3: Dependency Management (ABOVE LINE)
|
|
316
|
+
|
|
317
|
+
### The Problem
|
|
318
|
+
|
|
319
|
+
As codebases grow, circular dependencies emerge:
|
|
320
|
+
- Service imports Repository
|
|
321
|
+
- Repository imports utility in Service (closes circle)
|
|
322
|
+
- Creates tight coupling, hard to test, risky refactors
|
|
323
|
+
|
|
324
|
+
### The Solution: Dependency Auditor
|
|
325
|
+
|
|
326
|
+
**Enforce dependency direction at build time** (ABOVE LINE improvement not in any benchmark repo).
|
|
327
|
+
|
|
328
|
+
**Check:**
|
|
329
|
+
1. Transport layer files import only Transport + Service
|
|
330
|
+
2. Service layer files import only Service + Repository
|
|
331
|
+
3. Repository layer files import only Repository (no Service)
|
|
332
|
+
4. No circular imports within layer
|
|
333
|
+
|
|
334
|
+
**Tool:**
|
|
335
|
+
```bash
|
|
336
|
+
npm run audit:dependencies # Pre-commit gate
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Checklist: Did You Get Architecture Right?
|
|
342
|
+
|
|
343
|
+
Before shipping a new service, verify:
|
|
344
|
+
|
|
345
|
+
- [ ] **Layer separation:** Transport doesn't contain business logic
|
|
346
|
+
- [ ] **Dependency direction:** No circles (Service imports Repo only, not vice versa)
|
|
347
|
+
- [ ] **Repository abstraction:** No business logic in SQL queries
|
|
348
|
+
- [ ] **Service orchestration:** Complex flows live in Service, not Transport
|
|
349
|
+
- [ ] **Error handling:** Errors typed + recovery strategies clear
|
|
350
|
+
- [ ] **Idempotency:** Distributed transactions have idempotency keys
|
|
351
|
+
- [ ] **Feature-based:** Related code lives together, not scattered across service/util folders
|
|
352
|
+
- [ ] **Testing:** Unit tests mock Repository (test business logic), no DB
|
|
353
|
+
- [ ] **Documentation:** README explains layer separation for this service
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## References
|
|
358
|
+
|
|
359
|
+
- [Awesome-Copilot Architecture](https://github.com/github/awesome-copilot)
|
|
360
|
+
- [Antigravity Microservices](https://github.com/sickn33/antigravity-awesome-skills)
|
|
361
361
|
- [MiniMax Fullstack Structure](https://github.com/MiniMax-AI/skills)
|