The model layer is a complete, UI-independent application: data retrieval, editing, validation, selection, master-detail coordination and all associated state live here, exposed through the framework’s reactive classes. The UI layer renders models — it does not extend them with logic. Nothing in the model layer depends on a UI toolkit, which has two practical consequences: application logic is unit-testable without showing a window, and the same models can drive different client technologies.

1. The pieces

An EntityModel is the composition root for a single entity type: an edit model (wrapping the editor — the write path), usually a table model (with its query model — the read path), and any detail models.

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityModel invoiceModel = new SwingEntityModel(Invoice.TYPE, connectionProvider);

    // Establish master-detail relationship
    customerModel.detail().add(invoiceModel);

Master-detail relationships are expressed by linking models, to arbitrary depth — a detail model’s query condition tracks its master’s selection automatically:

    // Three-level hierarchy
    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityModel invoiceModel = new SwingEntityModel(Invoice.TYPE, connectionProvider);
    SwingEntityModel invoiceLineModel = new SwingEntityModel(InvoiceLine.TYPE, connectionProvider);

    customerModel.detail().add(invoiceModel);
    invoiceModel.detail().add(invoiceLineModel);

    // Selection cascades down the hierarchy
    Entity customer = getCustomer(connectionProvider);
    customerModel.tableModel().selection().item().set(customer);
    // Invoices for selected customer are loaded
    Entity invoice = invoiceModel.tableModel().items().included().get(0);
    invoiceModel.tableModel().selection().item().set(invoice);
    // Invoice lines for selected invoice are loaded

See Model linking for the linking configuration — what happens on selection, insert, update and delete.

2. The reactive fabric

Everything a model knows is exposed as an observable Value, State or Event — which is all a UI needs to render it, and all application logic needs to react to it:

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityEditModel editModel = customerModel.editModel();
    SwingEntityTableModel tableModel = customerModel.tableModel();

    // Edit model states
    State updateEnabled = editModel.editor().settings().updateEnabled();
    State updateMultipleEnabled = editModel.editor().settings().updateMultipleEnabled();
    ObservableState modified = editModel.editor().entity().modified();

    // Table model states
    ObservableState refreshing = tableModel.items().refresher().active();
    ObservableState hasSelection = tableModel.selection().empty().not();

    // Combine states
    ObservableState canDelete = State.and(hasSelection, refreshing.not());

Entity values are observable per attribute, so values can be bound — to input components, or across models:

    SwingEntityModel trackModel = new SwingEntityModel(Track.TYPE, connectionProvider);
    SwingEntityEditModel editModel = trackModel.editModel();

    // Bind edit model value to UI state
    EditorValue<BigDecimal> priceValue = editModel.editor().value(Track.UNITPRICE);
    ObservableState priceValid = editModel.editor().value(Track.UNITPRICE).valid();

    // React to value changes
    priceValue.addConsumer(this::updateTotalPrice);

    // React to value edits
    priceValue.edited().addConsumer(newPrice -> System.out.println("Price: " + newPrice));
    priceValid.when(false)
            .addListener(() -> System.out.println("Invalid price: " + priceValue.get()));

3. Where application logic belongs

Logic belongs in the models — reacting to edits, persistence events and selection there means it works the same regardless of which UI (or test) drives it:

    SwingEntityModel invoiceLineModel = new SwingEntityModel(InvoiceLine.TYPE, connectionProvider);

    // Update summary when details change
    PersistEvents events = invoiceLineModel.editor().events();
    events.after().insert().addConsumer(entities -> updateInvoiceTotal());
    events.after().update().addConsumer(entities -> updateInvoiceTotal());
    events.after().delete().addConsumer(entities -> updateInvoiceTotal());

The chinook demo’s models are the reference examples: value dependencies and custom persistence in InvoiceLineEditModel, value propagation in InvoiceEditModel, selection-scoped operations in TrackTableModel — each documented in the chapters that follow.

Note
Since models are UI-free, they are constructed and exercised directly in unit tests — create the model with a test connection provider, edit, insert, assert.