Skip to content

Commit b13a41c

Browse files
Merge pull request #45 from beginwebdev2002/jules-arch-update-eda-hexagonal-17167600576691628701
docs(arch): autonomous evolution of architecture blueprints
2 parents 38ae0eb + 6c89f4d commit b13a41c

2 files changed

Lines changed: 30 additions & 10 deletions

File tree

architectures/event-driven-architecture/implementation-guide.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ class PaymentEventHandler {
9090

9191

9292
### ⚠️ Problem
93-
[Analysis of the risks]
93+
Without tracking `eventId` locally, duplicate deliveries from the broker (due to consumer rebalancing, network drops, or retries) result in duplicate side effects. This leads to double-billing customers, duplicate orders, and compromised system integrity.
9494

9595

9696
### 🚀 Solution
97-
[Architectural justification of the solution]
97+
Implementing an Idempotent Receiver pattern with a unique constraint on `eventId` ensures that duplicate events are safely ignored. Wrapping the deduplication check and the business logic within an atomic database transaction guarantees state consistency even under high concurrency.
9898
---
9999
## 2. The Transactional Outbox Pattern
100100

@@ -143,11 +143,11 @@ class OrderService {
143143

144144

145145
### ⚠️ Problem
146-
[Analysis of the risks]
146+
The Dual-Write Anti-Pattern occurs when an application attempts to update a database and publish a message to a broker sequentially. Without distributed transactions, a failure between the two operations leaves the system in an inconsistent state (e.g., the DB is updated, but the event is never published).
147147

148148

149149
### 🚀 Solution
150-
[Architectural justification of the solution]
150+
The Transactional Outbox pattern guarantees atomic operations. By saving the event to a local `outbox` table within the same DB transaction as the domain change, we achieve atomicity. A separate, reliable process (like a Debezium Connector or background poller) then reads the outbox table and guarantees "at-least-once" delivery to the broker.
151151
---
152152
## 3. Strictly Typed Schemas (Schema Registry)
153153

@@ -186,15 +186,26 @@ class OrderKafkaPublisher {
186186

187187

188188
### ❌ Bad Practice
189-
[Need to fill in example of non-optimal code]
189+
```typescript
190+
class OrderKafkaPublisher {
191+
async publish(event: DomainEvent) {
192+
// ❌ Publishing raw, untyped JSON objects
193+
// Downstream consumers might break if 'amount' is suddenly renamed to 'totalAmount'
194+
await this.producer.send({
195+
topic: 'orders.created',
196+
messages: [{ key: event.aggregateId, value: JSON.stringify(event.payload) }]
197+
});
198+
}
199+
}
200+
```
190201

191202

192203
### ⚠️ Problem
193-
[Analysis of the risks]
204+
Publishing unstructured JSON couples microservices dangerously. If the upstream service changes a field name or type, downstream consumers will crash unexpectedly, causing cascading failures and data corruption across the distributed system.
194205

195206

196207
### 🚀 Solution
197-
[Architectural justification of the solution]
208+
A Schema Registry enforces a strict, versioned contract (e.g., Avro, Protobuf, JSON Schema) between producers and consumers. It serializes data efficiently, rejects non-compliant payloads at the producer level, and ensures backward/forward compatibility rules are respected across all teams.
198209
---
199210

200211
<div align="center">

architectures/hexagonal-architecture/implementation-guide.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,19 @@ import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
174174
@Entity('users')
175175
export class User {
176176
@PrimaryGeneratedColumn('uuid')
177-
public id: string;
177+
id: string;
178178

179179
@Column()
180-
public email: string;
180+
email: string;
181+
182+
@Column()
183+
status: string;
184+
185+
deactivate(): void {
186+
this.status = 'INACTIVE';
187+
}
188+
}
189+
```
181190

182191
@Column()
183192
public status: 'ACTIVE' | 'INACTIVE';
@@ -227,4 +236,4 @@ export class UserOrmEntity {
227236
```
228237

229238
### 🚀 Solution
230-
Keep the Core Domain completely pure and agnostic of any external frameworks, databases, or UI. Use mapping layers (`Adapters`) to translate the pure Domain Entities to and from database-specific representations (like ORM entities or raw SQL rows). This ensures the business logic remains portable, independently testable, and strictly focused on business rules.
239+
Isolate the Core Domain from infrastructure. The `User` entity must be a pure TypeScript class devoid of external imports or decorators. The mapping between the database rows and the Domain Entity must be handled strictly by the Secondary Adapter (e.g., via a Mapper or DTO class), ensuring the Core remains clean, testable, and completely vendor-agnostic.

0 commit comments

Comments
 (0)