Back

Product code

Product decisions
are the code.

When the cloud handles the engineering underneath, a feature can be just its business rules and follow-up actions. These examples show what that leaves your agent to write.

You build in natural language. Fluxzero chose Java for the code underneath: a proven backend language that agents speak fluently. Your code remains easy to extend as your product grows.

01 · Making changes

Only product logic.
Working at any scale.

You decide how a feature should work. Fluxzero keeps its rules intact when many customers act at once. The same logic continues to work as usage grows, without engineering work for your agent.

ReserveTicket.java One rule, two changes
record ReserveTicket(
    @NotNull ReservationId reservationId,
    @NotNull TicketId ticketId) {

    @AssertLegal
    void assertAvailable(Ticket ticket) {
        if (ticket.status() != AVAILABLE) {
            throw TicketErrors.notAvailable;
        }
    }

    @Apply
    Ticket reserve(Ticket ticket) {
        return ticket.withStatus(RESERVED);
    }

    @Apply
    Reservation createReservation(User user) {
        return Reservation.awaitingPayment(
            reservationId, ticketId, user.id());
    }
}
State loads automatically
Fluxzero provides the product state and which customer is making the change.
Everything changes together
If something changes while the rule runs, it runs again before the changes are saved.
Many people, one safe result
Each decision is kept correct even when many customers act at once.

02 · Keeping track

Your product changes.
Its history stays.

Define the parts of your product and how they fit together. Fluxzero keeps the information up to date and preserves its history. Your app can use that history directly in its features.

Show.java Root model
@Model
record Show(
    @EntityId ShowId showId,
    String name,
    ShowStatus status) {}
Ticket.java Child of Show
@Model
record Ticket(
    @EntityId TicketId ticketId,
    @Parent(pathInParent = "tickets")
    ShowId showId,
    TicketDetails details,
    @With TicketStatus status) {}
Reservation.java Child of Ticket
@Model
record Reservation(
    @EntityId ReservationId reservationId,
    @Parent(pathInParent = "reservations")
    TicketId ticketId,
    String customerId,
    @With ReservationStatus status,
    @With @ProtectData String contactEmail) {}
Keep track of everything
Each part of the product is tracked along with its relationships.
Stored automatically
Your code needs no storage logic. The information is loaded and saved automatically.
History preserved
Fluxzero keeps every previous state of the product available.

03 · Testing behavior

Know it works.
Before customers use it.

Your agent tests that features keep working as you build. That includes the edge cases. Fluxzero runs the same product code and rules in tests as in production, so you can ship with confidence.

ReserveTicketTest.java Web request to automatic cancellation
@Test
void cancelsWhenPaymentIsTooLate() {
    fixture
        .whenPostByUser("customer-42",
            "/api/reservations", "/ticketing/reserve.json")
        .expectEvents(ReserveTicket.class)
        .andThen()
        .whenTimeElapses(Duration.ofMinutes(15))
        .expectEvents(ExpireReservation.class);
}
Production behavior
The scenario runs through the same rules as the real application.
Real circumstances
The person and moment described by the test are supplied automatically.
Visible outcome
Success and expected rejections are checked without infrastructure or mocks.

05 · Set a schedule

Your product follows up.
Even after a restart.

Scheduling is part of almost every application. Fluxzero makes it easy to schedule and test what should happen later. Scheduled work survives restarts and deployments, with your rules checked when it runs.

ReservationTimers.java Start and cancel a deadline
@Component
class ReservationTimers {
    @HandleEvent
    void start(ReserveTicket event) {
        Fluxzero.scheduleCommand(
            new ExpireReservation(event.reservationId()),
            ScheduleId.of("expire", event.reservationId()),
            Duration.ofMinutes(15));
    }

    @HandleEvent
    void stop(ConfirmReservation event) {
        Fluxzero.cancelSchedule(
            ScheduleId.of("expire", event.reservationId()));
    }
}
ExpireReservation.java Remove hold and release ticket
record ExpireReservation(@NotNull ReservationId reservationId) {

    @AssertLegal
    void assertAwaitingPayment(Reservation reservation) {
        if (reservation.status() != AWAITING_PAYMENT) {
            throw ReservationErrors.notAwaitingPayment;
        }
    }

    @Apply
    Reservation remove(Reservation reservation) {
        return null;
    }

    @Apply
    Ticket release(Ticket ticket) {
        return ticket.withStatus(AVAILABLE);
    }
}
Scheduled work preserved
Scheduled actions survive application restarts and deployments.
Current rules checked
Fluxzero loads the latest data and checks whether the scheduled change is still allowed.
Cancellation handled
Your code names the follow-up to cancel. It is removed from the schedule.

06 · Run a workflow

Workflows pick up
where they left off.

Some workflows need to wait for an external service before continuing. Define what happens when the response arrives. Fluxzero preserves progress and connects each response to the right process, even after a restart.

PaymentProcess.java Wait for a payment response
@Stateful
record PaymentProcess(
    @EntityId ReservationId reservationId,
    @Association String pspReference) {

    @HandleEvent
    static PaymentProcess start(PaymentStarted event) {
        return new PaymentProcess(
            event.reservationId(), event.pspReference());
    }

    @HandleEvent
    PaymentProcess complete(PaymentSucceeded event) {
        Fluxzero.sendCommandAndWait(
            new ConfirmReservation(reservationId));
        return null;
    }
}
Progress preserved
Each workflow’s progress is preserved, including after a restart.
Responses connected
The reference on an incoming response connects it to the right waiting process.
Ready to continue
Fluxzero loads that process and runs the next step with its saved state.

07 · Access and privacy

You decide who may do what.
Fluxzero enforces it.

Define the access rules that fit your product. Fluxzero enforces them before allowing a change. Information marked as private stays protected and can be erased without losing the surrounding history.

UpdateContactEmail.java Ownership and protected data
@RequiresUser
record UpdateContactEmail(
    @NotNull ReservationId reservationId,
    @NotBlank @Email @ProtectData
    String contactEmail) {

    @AssertLegal
    void assertOwner(Reservation reservation, User user) {
        if (!reservation.customerId()
                .equals(user.id())) {
            throw ReservationErrors.notOwner;
        }
    }

    @Apply
    Reservation update(Reservation reservation) {
        return reservation.withContactEmail(contactEmail);
    }
}
Identity supplied
Your rule receives the authenticated customer making the change.
Access checked first
When sign-in is required, unauthenticated requests are rejected before the feature runs.
Private data stays erasable
Fluxzero stores protected values separately, so they can be erased while the surrounding history remains.

08 · Reach in through the web

Public web endpoints.
Private applications.

Your web endpoints live in Fluxzero. Your application has no public endpoint of its own and securely pulls requests when it can handle them. This keeps it out of reach of direct network attacks. Excess traffic waits in Fluxzero instead of flooding the application.

ReservationApi.java Web request to command
@Component
@Path("/api/reservations")
class ReservationApi {
    @HandlePost
    ReservationId reserve(@Valid ReservationRequest request) {
        var reservationId = Fluxzero.generateId(
            ReservationId.class);

        Fluxzero.sendCommandAndWait(
            new ReserveTicket(
                reservationId, request.ticketId()));
        return reservationId;
    }
}
Input checked
Fluxzero checks the submitted data against your requirements before running the feature.
Identity carried through
The feature receives the customer making the request without extra code to pass that identity along.
Response handled
The result is sent back to the interface, with errors turned into the appropriate web response.

09 · Connect your features

The same code.
Wherever the work runs.

Send a request without choosing which application will handle it. Fluxzero delivers it and returns the result. Whether the handler runs in your own app or another one, the calling code stays the same.

TicketApi.java Requests without routing code
@Component
@Path("/api/tickets")
class TicketApi {
    @HandleGet
    List<Ticket> available(
            @QueryParam ShowId showId,
            @QueryParam String section) {
        return Fluxzero.queryAndWait(
            new FindAvailableTickets(showId, section));
    }
}
Web handlers anywhere
The web handler can run in any connected application. The public endpoint stays the same.
Queries across applications
The query handler can run alongside it or in another app. The call stays the same.
One request, more uses
Other apps can observe the same web request for analytics while the endpoint handler returns the response.

10 · Scale independently

Each part can scale
independently.

Not every part of your application is equally busy. With separate consumers, parts run and scale independently. Fluxzero keeps their progress separate and distributes the work, so capacity can grow where it is needed.

FraudDetection.java Fraud checks with their own capacity
@Component
@Consumer(name = "fraud-detection", threads = 4)
class FraudDetection {
    @HandleEvent
    void check(ConfirmReservation event) {
        FraudAssessment assessment = Fluxzero.queryAndWait(
            new CheckForFraud(event.reservationId()));

        if (assessment.requiresReview()) {
            Fluxzero.sendAndForgetCommand(
                new ReviewReservation(
                    event.reservationId(), assessment));
        }
    }
}
Capacity where it counts
Give demanding work its own consumer. Add threads or app instances as demand grows.
Independent progress
Each consumer keeps its own place. Slower work does not hold up other consumers.
Work shared automatically
Fluxzero distributes the work across instances of the same consumer.

11 · Using the past

New features can be built
from historical data.

Fluxzero preserves your product’s history. Use it to add features retroactively, with the information as it was at the time. The same code processes earlier activity and continues with new activity.

CustomerRewards.java Retroactive event handler
@Component
@Consumer(name = "customer-rewards", minIndex = 0)
class CustomerRewards {
    @HandleEvent
    void reward(
            ConfirmReservation event,
            Reservation reservation) {
        Fluxzero.assertAndApply(new GrantReward(
            event.reservationId(), reservation.customerId(), 100));
    }
}
History supplied
Your new feature receives recorded activity from the point you choose.
Earlier state reconstructed
Fluxzero supplies the data as it was then, without your code having to rebuild it.
Progress remembered
Progress is tracked, and processing resumes after interruptions.

The result

Your product is
the entire application.

Your agent writes what the product should do. Fluxzero takes responsibility for making it run. What remains is an application entirely devoted to the product, from the first feature to a fully distributed system.