DEV Community

ThembaTman0
ThembaTman0

Posted on

Faker Doesn't Know Your Entities Are Related, So I Built Something That Does

Faker Doesn't Know Your Entities Are Related, So I Built Something That Does

You've added a second entity to the schema, wired up a @ManyToOne, and gone back to your seed script to generate fifty more rows. Ninety seconds later, the app refuses to start: unique constraint violation, somewhere inside a loop you wrote three weeks ago at 11pm. You fix it. You restart. A different field breaks a different constraint.

This is the exact moment every Spring Boot developer eventually meets the real limit of tools like Faker. They're brilliant at generating a name, an email, an address. They have no idea the Payment sitting in front of them needs a Counterparty to already exist.

So you do what everyone does: hand-write the wiring. Create parents first. Hold onto their generated IDs. Wire them into children. Hope you didn't just violate a @NotNull somewhere in the process. It works, for a while. Then the schema changes, and the script quietly stops matching reality until the next 3am debugging session finds out the hard way.

I hit this enough times that I stopped patching the script and looked at the actual problem: the information needed to seed this correctly already exists. It's sitting right there in the entity, in the annotations you already wrote. @ManyToOne, @NotNull, @Column(unique = true), JPA already knows the shape of your data. Nothing should need to be told that twice.

That became SynthForge.

The core idea

Instead of writing a script that generates data, you annotate the entity:

@Entity @Seed(count = 50)
public class Counterparty { /* fields only */ }

@Entity @Seed(count = 200)
public class Payment {
    @ManyToOne(optional = false) private Counterparty counterparty;
}
Enter fullscreen mode Exit fullscreen mode

Start the app in a dev profile. Both tables populate, correctly ordered, on every restart. No seed method. No calling code, anywhere. The entity is the seed script.

What's actually happening underneath

Entity scanning. SynthForge reads JPA-managed attributes through the jakarta.persistence.metamodel.Metamodel API, not raw reflection. That distinction matters more than it sounds: only real persistent fields ever get touched, nothing that merely resembles one.

Relationship ordering. From the owning-side @ManyToOne/@OneToOne relationships, it builds a dependency graph and topologically sorts it. Parent rows exist before a child is ever generated to reference them. This is precisely where hand-written scripts fail first: the moment a second level of nesting shows up, manual ID-tracking becomes its own source of bugs, the thing you're debugging instead of your actual feature.

Constraint-aware generation. @NotNull, @Size, @Email, and a set of field-name heuristics (email, iban, amount, country) drive realistic values through Datafaker. Hit a @Column(unique = true) field, and instead of a constraint violation forty rows in, you get a bounded retry loop.

Idempotent restarts. A table that already has rows gets skipped. Restart against a persistent database as many times as you want; nothing duplicates.

"Isn't this just Instancio?"

Fair question, and worth answering directly rather than dodging it. Instancio, and its instancio-jpa extension, gives you an API to call from inside a test: build a graph, persist it, for that one test. It's genuinely good at that job.

SynthForge isn't called from anywhere. Annotate the entity, start the app in an enabled profile, and the database is already populated before your code runs a single line. Different tool, because it solves a different moment: application startup, not a single test method.

Where it stands

Live on Maven Central:

<dependency>
    <groupId>io.github.thembatman0</groupId>
    <artifactId>synthforge-spring</artifactId>
    <version>0.1.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Repo: github.com/ThembaTman0/synthforge

It won't fix your architecture and it won't make decisions for you. But the next time you add a foreign key at 11pm, there's a decent chance it means you get to go to bed instead.

Top comments (0)