mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 13:49:36 +01:00
docs: and skills
This commit is contained in:
parent
e4f0665086
commit
ccd4ee534a
25 changed files with 10412 additions and 0 deletions
178
bin/.claude/skills/ddd-model/rules/degraded-state-pattern.md
Normal file
178
bin/.claude/skills/ddd-model/rules/degraded-state-pattern.md
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
# Degraded State Pattern
|
||||
|
||||
## Problem
|
||||
|
||||
When domain invariants evolve in production systems, old data may violate new business rules:
|
||||
|
||||
```java
|
||||
// Version 1: Email optional
|
||||
public class Customer {
|
||||
private EmailAddress email; // nullable
|
||||
}
|
||||
|
||||
// Version 2: Email becomes required (new invariant)
|
||||
public static Result<CustomerError, Customer> create(...) {
|
||||
if (email == null) {
|
||||
return Result.failure(new EmailRequiredError()); // ❌ Old data breaks!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Without a migration strategy, existing production data cannot be loaded.**
|
||||
|
||||
## Solution: Degraded State Pattern
|
||||
|
||||
Allow entities to exist in a "degraded state" where some invariants are temporarily violated. The entity:
|
||||
1. **Can be loaded** from persistence despite missing new required fields
|
||||
2. **Is flagged** as degraded with clear indication of missing fields
|
||||
3. **Blocks certain operations** until brought to valid state
|
||||
4. **Provides path to recovery** through explicit update operations
|
||||
|
||||
## When to Use
|
||||
|
||||
✅ **Use when**:
|
||||
- Adding new required fields to existing entities
|
||||
- Tightening validation rules on production data
|
||||
- Migrating data that doesn't meet current standards
|
||||
- Gradual rollout of stricter business rules
|
||||
|
||||
❌ **Don't use when**:
|
||||
- Creating new entities (always enforce current invariants)
|
||||
- Data corruption (fix at persistence layer instead)
|
||||
- Temporary technical failures (use retry/circuit breaker instead)
|
||||
|
||||
## Implementation Pattern
|
||||
|
||||
### 1. Dual Factory Methods
|
||||
|
||||
```java
|
||||
public class Customer {
|
||||
private final CustomerId id;
|
||||
private final String name;
|
||||
private EmailAddress email; // New required field
|
||||
private final boolean isDegraded;
|
||||
|
||||
/**
|
||||
* Creates NEW customer (strict validation).
|
||||
* Enforces all current invariants.
|
||||
*/
|
||||
public static Result<CustomerError, Customer> create(
|
||||
CustomerId id,
|
||||
String name,
|
||||
EmailAddress email
|
||||
) {
|
||||
// Strict validation
|
||||
if (email == null) {
|
||||
return Result.failure(new EmailRequiredError(
|
||||
"Email is required for new customers"
|
||||
));
|
||||
}
|
||||
return Result.success(new Customer(id, name, email, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstructs customer from persistence (lenient).
|
||||
* Allows loading old data that doesn't meet current invariants.
|
||||
*/
|
||||
public static Customer fromPersistence(
|
||||
CustomerId id,
|
||||
String name,
|
||||
EmailAddress email // Can be null for old data
|
||||
) {
|
||||
boolean isDegraded = (email == null);
|
||||
|
||||
if (isDegraded) {
|
||||
log.warn("Customer loaded in degraded state: id={}, missing=email", id);
|
||||
}
|
||||
|
||||
return new Customer(id, name, email, isDegraded);
|
||||
}
|
||||
|
||||
private Customer(CustomerId id, String name, EmailAddress email, boolean isDegraded) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.isDegraded = isDegraded;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Operation Gating
|
||||
|
||||
Block operations that require valid state:
|
||||
|
||||
```java
|
||||
public Result<CustomerError, Order> placeOrder(OrderDetails details) {
|
||||
// Guard: Cannot place order in degraded state
|
||||
if (isDegraded) {
|
||||
return Result.failure(new CustomerDegradedError(
|
||||
"Please complete your profile (add email) before placing orders",
|
||||
List.of("email")
|
||||
));
|
||||
}
|
||||
|
||||
// Normal business logic
|
||||
Order order = new Order(details, this.email);
|
||||
return Result.success(order);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Recovery Path
|
||||
|
||||
Provide explicit operations to exit degraded state:
|
||||
|
||||
```java
|
||||
public Result<CustomerError, Void> updateEmail(EmailAddress newEmail) {
|
||||
if (newEmail == null) {
|
||||
return Result.failure(new EmailRequiredError("Email cannot be null"));
|
||||
}
|
||||
|
||||
this.email = newEmail;
|
||||
this.isDegraded = false; // Exit degraded state
|
||||
|
||||
log.info("Customer email updated, exited degraded state: id={}", id);
|
||||
return Result.success(null);
|
||||
}
|
||||
|
||||
public boolean isDegraded() {
|
||||
return isDegraded;
|
||||
}
|
||||
|
||||
public List<String> getMissingFields() {
|
||||
List<String> missing = new ArrayList<>();
|
||||
if (email == null) {
|
||||
missing.add("email");
|
||||
}
|
||||
return missing;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
✅ **Do**:
|
||||
- Use dual factory methods (`create()` strict, `fromPersistence()` lenient)
|
||||
- Log when entities load in degraded state
|
||||
- Provide clear error messages with missing fields
|
||||
- Allow read operations and deposits (recovery paths)
|
||||
- Block critical operations until valid
|
||||
- Provide explicit recovery operations
|
||||
- Monitor degraded entity count in production
|
||||
|
||||
❌ **Don't**:
|
||||
- Allow new entities to be created in degraded state
|
||||
- Silently accept degraded state without logging
|
||||
- Block all operations (allow recovery paths)
|
||||
- Forget to provide user-facing recovery UI
|
||||
- Leave entities degraded indefinitely (migrate!)
|
||||
- Use degraded state for temporary failures
|
||||
|
||||
## Summary
|
||||
|
||||
The Degraded State Pattern enables:
|
||||
- ✅ **Zero-downtime schema evolution**
|
||||
- ✅ **Gradual migration of invariants**
|
||||
- ✅ **Clear user communication** about incomplete data
|
||||
- ✅ **Explicit recovery paths** to valid state
|
||||
- ✅ **Production safety** during schema changes
|
||||
|
||||
Use it when domain rules evolve and existing production data doesn't meet new standards.
|
||||
Loading…
Add table
Add a link
Reference in a new issue