Skip to content

LibEntity

Type-safe, state-driven metamodel for Java

Build robust domain logic with less code and more confidence.

Quick Example ​

Use the LibEntity Java DSL to define your entities, transitions, actions, and validators. LibEntity also offers an annotation-based DSL for defining entity types, actions, and validators.

java
public enum InvoiceState {
    DRAFT,
    PENDING,
    APPROVED,
    REJECTED
}

@Data
static class Invoice {
    private BigDecimal amount;
    private BigDecimal vat;
    private boolean readyForApproval;
    private String submitterId;
    private String submitterDeviceId;
    private String approverId;
    private String approvalComment;
}

@Data
static class SubmitInvoiceCommand implements ActionCommand {
    private String submitterId;
    private String submitterDeviceId;
    @Override
    public String getActionName() { return "submit"; }
}

// ... 

var invoiceEntityType = EntityType.<InvoiceState, InvoiceRequest>builder("Invoice")
    .field("amount", BigDecimal.class, f -> f
        .validateInState(InvoiceState.DRAFT, (state, request, ctx) -> {
            if (request.invoice().getAmount() != null && request.invoice().getAmount().compareTo(new BigDecimal("10000")) > 0) {
                ctx.addError("AMOUNT_TOO_LARGE", "Amount cannot exceed 10,000");
            }
        })
    )
    .field("vat",
           BigDecimal.class, f -> 
           f.validateInState(InvoiceState.DRAFT, (state, request, ctx) -> {
                if (request.invoice().getVat() == null) {
                    ctx.addError("VAT_REQUIRED", "VAT is required");
                }
            })
    ).<SubmitInvoiceCommand>action("submit", a ->
            a.allowedStates(Set.of(InvoiceState.DRAFT))
             .commandType(SubmitInvoiceCommand.class)
             .onlyIf((state, request, command) ->
                request.invoice().getAmount() != null
                    && request.invoice().getAmount().compareTo(BigDecimal.ZERO) > 0
                    && request.invoice().getAmount().compareTo(new BigDecimal("10000")) <= 0)
        .handler((state, request, command, mutator) -> {
            request.invoice().setReadyForApproval(true);
            request.invoice().setSubmitterId(command.getSubmitterId());
            request.invoice().setSubmitterDeviceId(command.getSubmitterDeviceId());
            mutator.setState(InvoiceState.PENDING_APPROVAL);
        }))
    // ... 
    .build();
java
// 1. State Enum
enum PaymentState {
    DRAFT,
    PENDING_APPROVAL,
    APPROVED
}

// 2. Request and Command Types
@Data
@AllArgsConstructor
class PaymentRequest {
    private final int amount;
}

@Data
@AllArgsConstructor
class SubmitPaymentCommand {
    private final String submitDate;
    private final String submitterId;
}

// 3. Action Handler
public class PaymentActionHandler {
    @Handle
    public void handle(PaymentState state, PaymentRequest request, SubmitPaymentCommand command) {
        // Implement state mutation logic if needed
    }

    @OnlyIf
    public boolean canSubmit(PaymentState state, PaymentRequest request, SubmitPaymentCommand command) {
        return request.getAmount() > 0;
    }
}

// 4. Entity Definition
@EntityDefinition(
    name = "Payment",
    stateEnum = PaymentState.class,
    fields = {
        @Field(
            name = "amount",
            type = int.class,
            required = true,
            validators = {SampleAmountValidator.class}
        )
    },
    actions = {
        @Action(name = "submitPayment", description = "Submit a payment", handler = PaymentActionHandler.class, command = SubmitPaymentCommand.class)
    },
    inStateValidators = {SampleAmountValidator.class},
    transitionValidators = {SampleTransitionValidator.class}
)
public class PaymentEntityConfig {}

Why LibEntity? ​

A metamodel is what allows you to move fast. Most systems are "literal", that means they are too granular, and you end up with a lot of code, a lot of testing surface, and a lot of maintenance burdern that blocks your business to react fast. LibEntity metamodel is a balance between granularity and simplicity.

While not a new concept, LibEntity's metamodel is a unique take on the idea that provides a powerful yet simple way structure your applications by packing years of experience in a simple and declarative DSL.

Get started with LibEntity today!

Released under the MIT License. Made with ☕, 💡 and lots of Java love.