Three independently ugly bugs that all fed the same log flood:
* Electron's `WebFrameMain.send` writes every "Render frame was disposed"
to stderr via `console.error`. When the worker fans a second
agent-event past a reloading renderer, we produced thousands of
identical lines and stalled the main loop on stderr I/O. Added a
`mainFrame.detached` guard so the send is short-circuited before
Electron's own catch block fires, and installed a targeted filter in
`suppress-warnings.ts` (now actually imported from the entry point)
so the specific `console.error("Error sending from webFrameMain:"…)`
noise never reaches the terminal. Any other payload still surfaces.
* `dispatchAgentMessage` accepted the human-reader sentinel as a wake
target when an agent replied to `__board__` — `wakeAgentIfPossible`
then called `db.getAgent("__board__")`, threw, and the dispatcher
kept repeating on every send. Strip `HUMAN_SENDER_ID` at
`resolveMessageWakeTargets`, and keep a defense-in-depth guard in the
heartbeat so a stray caller (future automation fan-out) cannot
reintroduce the crash.
* Pi's skills loader rejects any SKILL.md whose frontmatter `name`
diverges from its parent directory. The four bundled `my-writing-style`
copies declared `name: writing-style-skill`, so every install emitted
a warning and dropped the skill. They also lived as orphan gitlinks
(mode 160000 without .gitmodules), and the global `*.md` gitignore
hid every other SKILL.md from version control. Fixed the frontmatter
to match the dir, converted the dangling submodules to plain trees,
and allowlisted `builtin-skills/**/*.md` so a fresh clone carries the
catalogue instead of silently losing it.
91 lines
2.5 KiB
Markdown
91 lines
2.5 KiB
Markdown
---
|
|
title: Entities Must Not Know How They Are Persisted
|
|
impact: CRITICAL
|
|
impactDescription: enables database migration without domain changes
|
|
tags: entity, persistence, database, ignorance
|
|
---
|
|
|
|
## Entities Must Not Know How They Are Persisted
|
|
|
|
Entities should have no awareness of databases, ORMs, or storage mechanisms. Persistence is an infrastructure detail that must not leak into the domain.
|
|
|
|
**Incorrect (entity aware of persistence):**
|
|
|
|
```java
|
|
// domain/entities/Product.java
|
|
@Entity
|
|
@Table(name = "products")
|
|
public class Product {
|
|
@Id
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
private Long id;
|
|
|
|
@Column(name = "product_name", nullable = false)
|
|
private String name;
|
|
|
|
@ManyToOne(fetch = FetchType.LAZY)
|
|
@JoinColumn(name = "category_id")
|
|
private Category category;
|
|
|
|
@Version
|
|
private Long version;
|
|
|
|
// Entity is coupled to JPA, cannot exist without Hibernate
|
|
}
|
|
```
|
|
|
|
**Correct (persistence-ignorant entity):**
|
|
|
|
```java
|
|
// domain/entities/Product.java
|
|
public class Product {
|
|
private final ProductId id;
|
|
private String name;
|
|
private CategoryId categoryId;
|
|
private Money price;
|
|
|
|
public Product(ProductId id, String name, CategoryId categoryId, Money price) {
|
|
this.id = id;
|
|
this.name = validateName(name);
|
|
this.categoryId = categoryId;
|
|
this.price = price;
|
|
}
|
|
|
|
public void updatePrice(Money newPrice, PricingPolicy policy) {
|
|
if (!policy.allowsPrice(newPrice)) {
|
|
throw new InvalidPriceException(newPrice);
|
|
}
|
|
this.price = newPrice;
|
|
}
|
|
|
|
private String validateName(String name) {
|
|
if (name == null || name.isBlank()) {
|
|
throw new InvalidProductNameException();
|
|
}
|
|
return name.trim();
|
|
}
|
|
}
|
|
|
|
// infrastructure/persistence/jpa/JpaProductEntity.java
|
|
@Entity
|
|
@Table(name = "products")
|
|
class JpaProductEntity {
|
|
@Id private Long id;
|
|
@Column private String name;
|
|
// ORM mapping isolated to infrastructure
|
|
}
|
|
|
|
// infrastructure/persistence/jpa/ProductMapper.java
|
|
class ProductMapper {
|
|
Product toDomain(JpaProductEntity entity) { /* ... */ }
|
|
JpaProductEntity toJpa(Product product) { /* ... */ }
|
|
}
|
|
```
|
|
|
|
**Benefits:**
|
|
- Switch from SQL to NoSQL without touching domain
|
|
- Entity tests don't require database setup
|
|
- Domain model expresses business concepts, not database schema
|
|
|
|
Reference: [Clean Architecture - Database is a Detail](https://www.oreilly.com/library/view/clean-architecture-a/9780134494272/ch30.xhtml)
|