@ryuenn3123/agentic-senior-core 1.8.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/.agent-context/blueprints/api-nextjs.md +184 -0
- package/.agent-context/blueprints/aspnet-api.md +247 -0
- package/.agent-context/blueprints/ci-github-actions.md +226 -0
- package/.agent-context/blueprints/ci-gitlab.md +200 -0
- package/.agent-context/blueprints/fastapi-service.md +210 -0
- package/.agent-context/blueprints/go-service.md +217 -0
- package/.agent-context/blueprints/graphql-grpc-api.md +51 -0
- package/.agent-context/blueprints/infrastructure-as-code.md +62 -0
- package/.agent-context/blueprints/kubernetes-manifests.md +76 -0
- package/.agent-context/blueprints/laravel-api.md +223 -0
- package/.agent-context/blueprints/nestjs-logic.md +247 -0
- package/.agent-context/blueprints/observability.md +227 -0
- package/.agent-context/blueprints/spring-boot-api.md +218 -0
- package/.agent-context/policies/llm-judge-threshold.json +20 -0
- package/.agent-context/profiles/platform.md +13 -0
- package/.agent-context/profiles/regulated.md +13 -0
- package/.agent-context/profiles/startup.md +13 -0
- package/.agent-context/prompts/init-project.md +86 -0
- package/.agent-context/prompts/refactor.md +45 -0
- package/.agent-context/prompts/review-code.md +47 -0
- package/.agent-context/review-checklists/architecture-review.md +70 -0
- package/.agent-context/review-checklists/frontend-usability.md +33 -0
- package/.agent-context/review-checklists/performance-audit.md +65 -0
- package/.agent-context/review-checklists/pr-checklist.md +97 -0
- package/.agent-context/review-checklists/release-operations.md +29 -0
- package/.agent-context/review-checklists/security-audit.md +113 -0
- package/.agent-context/rules/api-docs.md +186 -0
- package/.agent-context/rules/architecture.md +198 -0
- package/.agent-context/rules/database-design.md +202 -0
- package/.agent-context/rules/efficiency-vs-hype.md +143 -0
- package/.agent-context/rules/error-handling.md +234 -0
- package/.agent-context/rules/event-driven.md +226 -0
- package/.agent-context/rules/frontend-architecture.md +66 -0
- package/.agent-context/rules/git-workflow.md +200 -0
- package/.agent-context/rules/microservices.md +174 -0
- package/.agent-context/rules/naming-conv.md +141 -0
- package/.agent-context/rules/performance.md +168 -0
- package/.agent-context/rules/realtime.md +47 -0
- package/.agent-context/rules/security.md +195 -0
- package/.agent-context/rules/testing.md +178 -0
- package/.agent-context/stacks/csharp.md +149 -0
- package/.agent-context/stacks/go.md +181 -0
- package/.agent-context/stacks/java.md +135 -0
- package/.agent-context/stacks/php.md +178 -0
- package/.agent-context/stacks/python.md +153 -0
- package/.agent-context/stacks/ruby.md +80 -0
- package/.agent-context/stacks/rust.md +86 -0
- package/.agent-context/stacks/typescript.md +317 -0
- package/.agent-context/state/architecture-map.md +25 -0
- package/.agent-context/state/dependency-map.md +32 -0
- package/.agent-override.md +36 -0
- package/.agents/workflows/init-project.md +29 -0
- package/.agents/workflows/refactor.md +29 -0
- package/.agents/workflows/review-code.md +29 -0
- package/.cursorrules +140 -0
- package/.gemini/instructions.md +97 -0
- package/.github/ISSUE_TEMPLATE/v1.7-frontend-work-item.yml +54 -0
- package/.github/copilot-instructions.md +104 -0
- package/.github/workflows/benchmark-detection.yml +38 -0
- package/.github/workflows/frontend-usability-gate.yml +36 -0
- package/.github/workflows/release-gate.yml +32 -0
- package/.github/workflows/sbom-compliance.yml +32 -0
- package/.windsurfrules +106 -0
- package/AGENTS.md +131 -0
- package/CONTRIBUTING.md +136 -0
- package/LICENSE +21 -0
- package/README.md +239 -0
- package/bin/agentic-senior-core.js +1147 -0
- package/mcp.json +29 -0
- package/package.json +50 -0
- package/scripts/detection-benchmark.mjs +138 -0
- package/scripts/frontend-usability-audit.mjs +87 -0
- package/scripts/generate-sbom.mjs +61 -0
- package/scripts/init-project.ps1 +105 -0
- package/scripts/init-project.sh +131 -0
- package/scripts/llm-judge.mjs +664 -0
- package/scripts/release-gate.mjs +116 -0
- package/scripts/validate.mjs +554 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Error Handling — Never Swallow, Always Context
|
|
2
|
+
|
|
3
|
+
> A swallowed error is a silent production incident waiting to happen.
|
|
4
|
+
> When it explodes at 3 AM, you won't know where to look.
|
|
5
|
+
|
|
6
|
+
## The Three Commandments
|
|
7
|
+
|
|
8
|
+
1. **Never swallow an error.** Every error must be logged, re-thrown, or explicitly handled.
|
|
9
|
+
2. **Always add context.** A stack trace is not enough — include WHAT was happening and WITH WHAT data.
|
|
10
|
+
3. **Fail fast at boundaries.** Validate early, reject bad data before it travels deep into the system.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Swallowed Error Detection (Instant Rejection)
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
❌ DEATH PENALTY: Empty catch block
|
|
18
|
+
try {
|
|
19
|
+
await processPayment(order);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
// silently swallowed — payment may have failed, user has no idea
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
❌ DEATH PENALTY: Catch and log only (no re-throw or recovery)
|
|
25
|
+
try {
|
|
26
|
+
await processPayment(order);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.log('error:', error); // Logged to a void nobody reads
|
|
29
|
+
// Execution continues as if nothing happened
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
✅ CORRECT: Handle, log with context, re-throw or recover
|
|
33
|
+
try {
|
|
34
|
+
await processPayment(order);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
logger.error('Payment processing failed', {
|
|
37
|
+
orderId: order.id,
|
|
38
|
+
userId: order.userId,
|
|
39
|
+
amount: order.total,
|
|
40
|
+
error: error instanceof Error ? error.message : String(error),
|
|
41
|
+
});
|
|
42
|
+
throw new PaymentFailedError(order.id, { cause: error });
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Typed Error Codes
|
|
49
|
+
|
|
50
|
+
### Rule: Use Explicit Error Types, Not Generic Errors
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
❌ BANNED: Generic errors
|
|
54
|
+
throw new Error('Not found');
|
|
55
|
+
throw new Error('Permission denied');
|
|
56
|
+
throw new Error('Invalid input');
|
|
57
|
+
|
|
58
|
+
✅ REQUIRED: Typed, domain-specific errors
|
|
59
|
+
throw new NotFoundError('User', userId);
|
|
60
|
+
throw new ForbiddenError('Cannot delete other user\'s order');
|
|
61
|
+
throw new ValidationError('Email format is invalid', { field: 'email' });
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Error Class Pattern (Any Language)
|
|
65
|
+
```typescript
|
|
66
|
+
// Base application error
|
|
67
|
+
class AppError extends Error {
|
|
68
|
+
constructor(
|
|
69
|
+
message: string,
|
|
70
|
+
public readonly code: string,
|
|
71
|
+
public readonly statusCode: number,
|
|
72
|
+
public readonly context?: Record<string, unknown>,
|
|
73
|
+
) {
|
|
74
|
+
super(message);
|
|
75
|
+
this.name = this.constructor.name;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Specific errors
|
|
80
|
+
class NotFoundError extends AppError {
|
|
81
|
+
constructor(resource: string, id: string | number) {
|
|
82
|
+
super(`${resource} not found: ${id}`, 'NOT_FOUND', 404, { resource, id });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class ValidationError extends AppError {
|
|
87
|
+
constructor(message: string, context?: Record<string, unknown>) {
|
|
88
|
+
super(message, 'VALIDATION_ERROR', 400, context);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
class ForbiddenError extends AppError {
|
|
93
|
+
constructor(reason: string) {
|
|
94
|
+
super(reason, 'FORBIDDEN', 403);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Structured Logging
|
|
102
|
+
|
|
103
|
+
### Rule: Logs Must Include Context
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
❌ USELESS:
|
|
107
|
+
logger.error('Something went wrong');
|
|
108
|
+
logger.info('Processing...');
|
|
109
|
+
console.log(error);
|
|
110
|
+
|
|
111
|
+
✅ USEFUL:
|
|
112
|
+
logger.error('Order processing failed', {
|
|
113
|
+
traceId: req.traceId,
|
|
114
|
+
userId: currentUser.id,
|
|
115
|
+
orderId: order.id,
|
|
116
|
+
action: 'processOrder',
|
|
117
|
+
error: error.message,
|
|
118
|
+
stack: error.stack,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
logger.info('Payment completed', {
|
|
122
|
+
traceId: req.traceId,
|
|
123
|
+
orderId: order.id,
|
|
124
|
+
amount: payment.amount,
|
|
125
|
+
provider: payment.provider,
|
|
126
|
+
durationMs: Date.now() - startTime,
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Required Log Fields
|
|
131
|
+
| Field | When | Purpose |
|
|
132
|
+
|-------|------|---------|
|
|
133
|
+
| `traceId` / `requestId` | Always (in request context) | Correlate logs across services |
|
|
134
|
+
| `userId` | When authenticated | Who triggered the action |
|
|
135
|
+
| `action` | Always | What was happening |
|
|
136
|
+
| `error` + `stack` | On errors | What went wrong |
|
|
137
|
+
| `durationMs` | On slow operations | Performance visibility |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Error Response Format (APIs)
|
|
142
|
+
|
|
143
|
+
### Rule: NEVER Leak Internal Details
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
❌ BANNED response to client:
|
|
147
|
+
{
|
|
148
|
+
"error": "ER_NO_SUCH_TABLE: Table 'mydb.user_sessions' doesn't exist",
|
|
149
|
+
"stack": "Error: at Query.execute (/app/node_modules/mysql..."
|
|
150
|
+
}
|
|
151
|
+
// Congratulations, you just told the attacker your DB name and framework
|
|
152
|
+
|
|
153
|
+
✅ REQUIRED response to client:
|
|
154
|
+
{
|
|
155
|
+
"error": {
|
|
156
|
+
"code": "INTERNAL_ERROR",
|
|
157
|
+
"message": "An unexpected error occurred. Please try again.",
|
|
158
|
+
"traceId": "abc-123-def"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Internal details go to your logs, not to the client
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Error Response Mapping
|
|
165
|
+
| Internal Error | HTTP Status | Client Message |
|
|
166
|
+
|---------------|-------------|----------------|
|
|
167
|
+
| `ValidationError` | 400 | Show specific field errors |
|
|
168
|
+
| `AuthenticationError` | 401 | "Invalid credentials" (never specify which field) |
|
|
169
|
+
| `ForbiddenError` | 403 | "Insufficient permissions" |
|
|
170
|
+
| `NotFoundError` | 404 | "Resource not found" |
|
|
171
|
+
| `ConflictError` | 409 | "Resource already exists" |
|
|
172
|
+
| `RateLimitError` | 429 | "Too many requests" |
|
|
173
|
+
| Unhandled errors | 500 | "Internal error" + traceId for support |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Fail-Fast at Boundaries
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
// ✅ Validate at the entry point, fail before going deeper
|
|
181
|
+
async function createOrder(req: Request) {
|
|
182
|
+
// Step 1: Validate input IMMEDIATELY
|
|
183
|
+
const parsed = CreateOrderSchema.safeParse(req.body);
|
|
184
|
+
if (!parsed.success) {
|
|
185
|
+
throw new ValidationError('Invalid order data', parsed.error.flatten());
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Step 2: Now use the validated, typed data
|
|
189
|
+
const order = await orderService.create(parsed.data);
|
|
190
|
+
return order;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ❌ Don't let bad data travel deep before failing
|
|
194
|
+
async function createOrder(req: Request) {
|
|
195
|
+
const data = req.body; // Unvalidated!
|
|
196
|
+
const order = await orderService.create(data);
|
|
197
|
+
// Service calls repository, repository tries to insert...
|
|
198
|
+
// Database throws a cryptic constraint violation 5 layers deep
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Retry Strategy
|
|
205
|
+
|
|
206
|
+
When retrying failed operations:
|
|
207
|
+
|
|
208
|
+
1. **Only retry transient errors** (network timeouts, 503s) — NEVER retry validation errors or auth failures
|
|
209
|
+
2. **Use exponential backoff** — 100ms → 200ms → 400ms → 800ms
|
|
210
|
+
3. **Set a maximum retry count** (typically 3)
|
|
211
|
+
4. **Log every retry attempt** with attempt number and error
|
|
212
|
+
5. **Add jitter** to prevent thundering herd
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
async function withRetry<T>(
|
|
216
|
+
operation: () => Promise<T>,
|
|
217
|
+
maxAttempts: number = 3,
|
|
218
|
+
baseDelayMs: number = 100,
|
|
219
|
+
): Promise<T> {
|
|
220
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
221
|
+
try {
|
|
222
|
+
return await operation();
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (attempt === maxAttempts || !isTransientError(error)) {
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
const delay = baseDelayMs * Math.pow(2, attempt - 1) + Math.random() * 100;
|
|
228
|
+
logger.warn('Retrying operation', { attempt, maxAttempts, delayMs: delay });
|
|
229
|
+
await sleep(delay);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
throw new Error('Unreachable');
|
|
233
|
+
}
|
|
234
|
+
```
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Event-Driven Architecture — React to Facts, Don't Poll for Changes
|
|
2
|
+
|
|
3
|
+
> If your services are constantly asking "Did anything change?", your architecture is broken.
|
|
4
|
+
> Events are the nervous system of a distributed system.
|
|
5
|
+
|
|
6
|
+
## When to Use Event-Driven Architecture
|
|
7
|
+
|
|
8
|
+
### Use Events When:
|
|
9
|
+
1. Multiple services need to react to the same state change
|
|
10
|
+
2. You need temporal decoupling (producer doesn't wait for consumer)
|
|
11
|
+
3. Audit trail / event history is a business requirement
|
|
12
|
+
4. Systems need to scale producers and consumers independently
|
|
13
|
+
5. Eventual consistency is acceptable
|
|
14
|
+
|
|
15
|
+
### Don't Use Events When:
|
|
16
|
+
- You need an immediate, synchronous response
|
|
17
|
+
- The system is a simple CRUD application with 1-2 services
|
|
18
|
+
- You can't invest in proper infrastructure (message broker, monitoring)
|
|
19
|
+
- Your team has no experience with async debugging
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Core Patterns
|
|
24
|
+
|
|
25
|
+
### 1. Pub/Sub (Publish-Subscribe)
|
|
26
|
+
|
|
27
|
+
Producer emits an event. Zero or more consumers react independently.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
┌──────────┐
|
|
31
|
+
│ Consumer A│ (Send email)
|
|
32
|
+
└────▲──────┘
|
|
33
|
+
┌──────────┐ │
|
|
34
|
+
│ Producer │──→ EVENT BUS ──→┌──────────┐
|
|
35
|
+
│ (Order │ │ │ Consumer B│ (Update inventory)
|
|
36
|
+
│ Service) │ │ └──────────┘
|
|
37
|
+
└──────────┘ │
|
|
38
|
+
┌───▼──────┐
|
|
39
|
+
│ Consumer C│ (Analytics)
|
|
40
|
+
└──────────┘
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Rules:**
|
|
44
|
+
- Producer does NOT know about consumers (fire-and-forget)
|
|
45
|
+
- Each consumer processes independently (failure in one doesn't affect others)
|
|
46
|
+
- Consumers MUST be idempotent (same event processed twice = same result)
|
|
47
|
+
|
|
48
|
+
### 2. CQRS (Command Query Responsibility Segregation)
|
|
49
|
+
|
|
50
|
+
Separate the write model (commands) from the read model (queries).
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
┌─────────────┐ ┌─────────────┐
|
|
54
|
+
│ Write Side │ Events │ Read Side │
|
|
55
|
+
│ (Commands) │ ──────────────→ │ (Queries) │
|
|
56
|
+
│ │ │ │
|
|
57
|
+
│ Rich domain │ │ Denormalized │
|
|
58
|
+
│ model │ │ read models │
|
|
59
|
+
│ Normalized │ │ Optimized │
|
|
60
|
+
│ schema │ │ for queries │
|
|
61
|
+
└─────────────┘ └─────────────┘
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**When to use CQRS:**
|
|
65
|
+
- Read/write ratio is heavily skewed (100:1 reads to writes)
|
|
66
|
+
- Read and write models have fundamentally different shapes
|
|
67
|
+
- You need different scaling for reads vs writes
|
|
68
|
+
|
|
69
|
+
**When NOT to use CQRS:**
|
|
70
|
+
- Simple CRUD with no complex queries
|
|
71
|
+
- Read and write models are identical (just use a repository)
|
|
72
|
+
- You don't have the team capacity to maintain two models
|
|
73
|
+
|
|
74
|
+
### 3. Event Sourcing
|
|
75
|
+
|
|
76
|
+
Store the full history of state changes as immutable events, not just the current state.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
Traditional: User { name: "Jane", email: "jane@new.com" }
|
|
80
|
+
→ You only know the current state
|
|
81
|
+
|
|
82
|
+
Event Sourced:
|
|
83
|
+
1. UserCreated { name: "Jane", email: "jane@old.com" }
|
|
84
|
+
2. EmailChanged { email: "jane@mid.com" }
|
|
85
|
+
3. EmailChanged { email: "jane@new.com" }
|
|
86
|
+
→ You know the full history, can rebuild any point in time
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**When to use Event Sourcing:**
|
|
90
|
+
- Audit trail is a regulatory requirement (finance, healthcare)
|
|
91
|
+
- "Time travel" queries are needed (what was the state on March 1?)
|
|
92
|
+
- Complex domain with many state transitions
|
|
93
|
+
- Combined with CQRS for complex read requirements
|
|
94
|
+
|
|
95
|
+
**When NOT to use Event Sourcing:**
|
|
96
|
+
- Simple CRUD applications (overkill)
|
|
97
|
+
- Team has no experience with event stores
|
|
98
|
+
- No clear business need for historical state
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Event Design Rules
|
|
103
|
+
|
|
104
|
+
### Naming: Past Tense, Domain Language
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
❌ BANNED:
|
|
108
|
+
"UpdateOrder" → Commands, not events
|
|
109
|
+
"ORDER_UPDATE" → Screaming snake in an event name
|
|
110
|
+
"data" → Meaningless
|
|
111
|
+
|
|
112
|
+
✅ REQUIRED:
|
|
113
|
+
"OrderPlaced" → Past tense, describes what happened
|
|
114
|
+
"PaymentProcessed" → Specific, clear domain action
|
|
115
|
+
"InventoryReserved" → Business language, not technical
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Event Schema: Include Context
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// ❌ BANNED: Minimal event with no context
|
|
122
|
+
{ type: "OrderPlaced", orderId: "123" }
|
|
123
|
+
|
|
124
|
+
// ✅ REQUIRED: Rich event with all necessary context
|
|
125
|
+
{
|
|
126
|
+
eventId: "evt_abc123", // Unique, for idempotency
|
|
127
|
+
eventType: "OrderPlaced", // What happened
|
|
128
|
+
aggregateId: "order_123", // Which entity
|
|
129
|
+
aggregateType: "Order", // Entity type
|
|
130
|
+
timestamp: "2026-03-11T...", // When it happened
|
|
131
|
+
version: 1, // Schema version
|
|
132
|
+
correlationId: "req_xyz", // Trace across services
|
|
133
|
+
causationId: "cmd_456", // What caused this event
|
|
134
|
+
data: { // The event payload
|
|
135
|
+
orderId: "order_123",
|
|
136
|
+
userId: "user_789",
|
|
137
|
+
items: [...],
|
|
138
|
+
totalAmount: 99.99,
|
|
139
|
+
currency: "USD"
|
|
140
|
+
},
|
|
141
|
+
metadata: { // Operational metadata
|
|
142
|
+
producerService: "order-service",
|
|
143
|
+
producerVersion: "2.1.0"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Event Versioning
|
|
149
|
+
|
|
150
|
+
Events are immutable — once published, they can't change. Handle evolution with:
|
|
151
|
+
|
|
152
|
+
1. **Weak schema (preferred):** Consumers ignore unknown fields, use defaults for missing fields
|
|
153
|
+
2. **Upcasting:** Transform old events to new schema on read
|
|
154
|
+
3. **New event type:** If the change is breaking, create `OrderPlacedV2`
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
BANNED: Changing the schema of an existing event in a breaking way
|
|
158
|
+
BANNED: Removing fields from events
|
|
159
|
+
ALLOWED: Adding optional fields with defaults
|
|
160
|
+
ALLOWED: Creating a new event type for breaking changes
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Infrastructure Choices
|
|
166
|
+
|
|
167
|
+
| Need | Recommended | Avoid |
|
|
168
|
+
|------|-----------|-------|
|
|
169
|
+
| General pub/sub | Apache Kafka, NATS, RabbitMQ | Custom TCP sockets |
|
|
170
|
+
| Cloud-native | AWS EventBridge, GCP Pub/Sub, Azure Service Bus | Polling a database table |
|
|
171
|
+
| Simple/local | Redis Streams, NATS | ZeroMQ for production events |
|
|
172
|
+
| Event store | EventStoreDB, Kafka (with compaction) | Relational DB as event store (unless simple) |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Reliability Patterns
|
|
177
|
+
|
|
178
|
+
### Outbox Pattern (Transactional Events)
|
|
179
|
+
|
|
180
|
+
Ensure events are published reliably alongside database writes:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
1. Write to database AND outbox table in a single transaction
|
|
184
|
+
2. Background process reads outbox and publishes to message broker
|
|
185
|
+
3. Mark outbox entry as published
|
|
186
|
+
|
|
187
|
+
This guarantees: if the DB write succeeds, the event WILL be published.
|
|
188
|
+
No more "DB updated but event lost" bugs.
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Dead Letter Queue (DLQ)
|
|
192
|
+
|
|
193
|
+
Messages that fail processing after N retries go to a DLQ:
|
|
194
|
+
- Monitor DLQ size — it should be near zero
|
|
195
|
+
- Alert when DLQ grows
|
|
196
|
+
- Investigate and reprocess failed messages
|
|
197
|
+
- Never ignore a growing DLQ
|
|
198
|
+
|
|
199
|
+
### Idempotency (Non-Negotiable)
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
EVERY consumer MUST handle duplicate events safely.
|
|
203
|
+
|
|
204
|
+
Techniques:
|
|
205
|
+
1. Idempotency key: Store processed eventIds, skip if seen
|
|
206
|
+
2. Natural idempotency: Operations that are naturally safe to repeat
|
|
207
|
+
(e.g., SET status = 'paid' is idempotent; INCREMENT balance is NOT)
|
|
208
|
+
3. Optimistic locking: Use version numbers to detect conflicts
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## The Event-Driven Checklist
|
|
214
|
+
|
|
215
|
+
Before implementing event-driven patterns:
|
|
216
|
+
|
|
217
|
+
- [ ] Business justification exists (not just "it's modern")
|
|
218
|
+
- [ ] Team understands eventual consistency trade-offs
|
|
219
|
+
- [ ] Message broker selected and provisioned
|
|
220
|
+
- [ ] Event schema defined with versioning strategy
|
|
221
|
+
- [ ] All consumers are idempotent
|
|
222
|
+
- [ ] Dead letter queue configured with monitoring
|
|
223
|
+
- [ ] Distributed tracing in place (OpenTelemetry)
|
|
224
|
+
- [ ] Outbox pattern used for transactional events (if needed)
|
|
225
|
+
- [ ] Consumer failure handling defined (retry, DLQ, alert)
|
|
226
|
+
- [ ] Event catalog maintained (what events exist, who produces/consumes)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Frontend Architecture & Composition Patterns
|
|
2
|
+
|
|
3
|
+
> A complex UI is built from simple, mathematically robust functions. State is dangerous; isolate it.
|
|
4
|
+
|
|
5
|
+
## 1. File Structure (Feature-Driven Design)
|
|
6
|
+
Organize your application by feature domain, not by file type.
|
|
7
|
+
- **BANNED:** Monolithic directories like `/components` (with 500 files), `/hooks`, `/api`.
|
|
8
|
+
- **REQUIRED (Feature Sliced):**
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
features/
|
|
12
|
+
authentication/
|
|
13
|
+
api/ #(login, logout fetchers)
|
|
14
|
+
components/ #(LoginForm, ProfileView)
|
|
15
|
+
hooks/ #(useAuth, useSession)
|
|
16
|
+
store.ts #(Zustand slice)
|
|
17
|
+
types.ts #(Zod schemas)
|
|
18
|
+
components/ #(Global shared UI like Button, Modal)
|
|
19
|
+
lib/ #(Axios instance, utility wrappers)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 2. Separation of State and UI (Smart vs. Dumb)
|
|
23
|
+
- **Dumb Components (Presentational):** Receive data via `props`, emit events via callbacks (`onAction`). They do not know about the network, global context, or databases.
|
|
24
|
+
- **Smart Components (Containers):** Connect to global state (Redux/Zustand), fetch data (React Query), and pass it down.
|
|
25
|
+
- **Rule:** An intricate UI layout component should NEVER contain a `fetch` or `useQuery` call.
|
|
26
|
+
|
|
27
|
+
## 3. Server State vs. Client State
|
|
28
|
+
Modern frontend frameworks differentiate between remote and local data.
|
|
29
|
+
- **Server State (Async, Cached):** Data belonging to the database. MUST be managed by tools like `TanStack Query` (React Query) or `SWR`.
|
|
30
|
+
- **Client State (Sync, Ephemeral):** UI toggles, modal states, form drafts. Manage via `useState`, `useContext`, or `Zustand`.
|
|
31
|
+
- **BANNED:** Storing API responses in a global Redux/Zustand store (e.g., `dispatch(setUsers(data))`). Use React Query instead.
|
|
32
|
+
|
|
33
|
+
## 4. The Composition Pattern (Avoiding Prop Drilling)
|
|
34
|
+
If a component takes more than 5 props, or if props are passed down through 3+ intermediate components, the architecture is broken.
|
|
35
|
+
- **BANNED:** `<Layout user={user} theme={theme} onLogout={handleLogout} />`
|
|
36
|
+
- **REQUIRED:** Use React's `children` prop and composition.
|
|
37
|
+
```tsx
|
|
38
|
+
// ✅ Clean composition
|
|
39
|
+
<Layout>
|
|
40
|
+
<Sidebar user={user} />
|
|
41
|
+
<Content onLogout={handleLogout} />
|
|
42
|
+
</Layout>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 5. Explicit Component Contracts (Typing)
|
|
46
|
+
Every component **MUST** have an explicit, exported interface for its props.
|
|
47
|
+
- **BANNED:** `const Button = (props: any) => ...`
|
|
48
|
+
- **REQUIRED:** Prefix handlers with `on` and booleans with `is/has`.
|
|
49
|
+
```typescript
|
|
50
|
+
export interface ButtonProps {
|
|
51
|
+
variant: 'primary' | 'secondary';
|
|
52
|
+
isLoading?: boolean;
|
|
53
|
+
onClick: () => void;
|
|
54
|
+
children: React.ReactNode;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 6. Form Handling & Validation
|
|
59
|
+
Never write manual state bindings for complex forms.
|
|
60
|
+
- **Rule:** All forms MUST use a robust library (`react-hook-form` is the standard) combined with a schema validator (`Zod`).
|
|
61
|
+
- **BANNED:** Creating 5 `useState` variables for 5 input fields.
|
|
62
|
+
|
|
63
|
+
## 7. Performance & Re-renders
|
|
64
|
+
React is fast until you break it.
|
|
65
|
+
- **Rule:** Do not pass newly instantiated objects or arrow functions directly into dependency arrays (`useEffect`) or memoized components (`React.memo`) unless wrapped in `useMemo`/`useCallback`.
|
|
66
|
+
- **Rule:** Never execute expensive mapping/filtering inside the render path blindly without memoization.
|