Getting Started

This page assumes you’ve read What is Conta Fork Yeah? and The Forking Model.

Adding fork support to an entity

The minimal recipe to make a domain entity forkable. The example below is a Ninja that lives under a Dojo (the root) and is keyed by season.

1. Define an IdentifierKey

Pick a short, stable key value.

public enum NinjaKey implements IdentifierKey {
    Dojo("dj"),
    Ninja("nj"),
    ;

    private final String keyValue;
    NinjaKey(String keyValue) { this.keyValue = keyValue; }

    @Override public String keyValue() { return keyValue; }

    @Override public IdentifierKey parentKey() {
        return this == Dojo ? null : Dojo;
    }
}

2. Add a ForkSources field

ForkSources is small and serialises cleanly as JSON; the typical persistence pattern is a JSON column:

@JdbcTypeCode(SqlTypes.JSON)
@Column(name = "forked_from")
private ForkSources forkedFrom;

3. Implement ForkAware

public class Ninja implements ForkAware<Ninja> {

    @Override
    public Identifier toIdentifier() {
        return Identifier.of(NinjaKey.Ninja, getUuid());
    }

    @Override
    public Optional<Identifier> resolveParentIdentifier() {
        return Optional.ofNullable(getDojo()).map(Dojo::toIdentifier);
    }

    @Override
    public ForkSources getForkedFrom() {
        return forkedFrom;
    }

    @Override
    public Forker<Ninja> fork() {
        return new NinjaForker(this);
    }

    @Override
    public Ninja toForkRef(@NonNull ForkMode mode) {
        var ref = new Ninja();
        ref.setUuid(getUuid());
        ref.setForkedFrom(toForkSources(mode));
        return ref;
    }
}

toForkRef returns a minimal shell — UUID and lineage only — for use as a reference from a forked sibling.

4. Implement the Forker

public class NinjaForker implements Forker<Ninja> {

    private final Ninja original;
    public NinjaForker(Ninja original) { this.original = original; }

    @Override
    public Ninja toNext() {
        var n = new Ninja();
        n.setUuid(UUID.randomUUID());
        n.setSeason(original.getSeason() + 1);
        n.setForkedFrom(original.toForkSources(ForkMode.ToNext));
        n.setDojo(ForkRefAware.ofNullable(original.getDojo(), ForkMode.ToNext));
        n.setRank(original.getRank());
        return n;
    }
}

Implement only the modes you actually support. The default toCopy/toNext/toPrevious throw UnsupportedOperationException, which is the correct behaviour for unsupported modes.

5. Define a fork command

Commands that initiate a fork implement ForkEnabled:

public class ForkNinjaCommand implements TenantCommand, ForkEnabled {

    @NonNull @NotNull @Valid private ForkSource forkFrom;
    private int season;
}

6. Wire it up in the service

public Ninja createFork(ForkNinjaCommand cmd) {

    var forkFrom = ForkSource.valid(cmd.getForkFrom());

    var source = repository.findById(forkFrom.getIdentifier().uuid1())
        .orElseThrow(() -> Problem.of(ForkMode.toProblemCode(forkFrom))
            .withInfo("forkFrom", forkFrom)
            .toThrowable());

    if (!forkFrom.getForkMode().sourceYearMatchesTargetYear(source.getSeason(), cmd.getSeason())) {
        throw Problem.of(ForkMode.toProblemCode(forkFrom))
            .withInfo("sourcePeriod", source.getSeason(), "targetPeriod", cmd.getSeason())
            .toThrowable();
    }

    var ctx = ForkContext.of(forkFrom, cmd);
    var forked = source.fork(forkFrom.getForkMode());
    return repository.save(forked);
}

That’s the full minimal setup. For nested entities and partial-success handling, see Bulk Forking.