The framework model layer serves as the bridge between the domain layer’s entity definitions and the UI layer’s visual components. It provides a comprehensive set of model classes that handle data retrieval, manipulation, and state management while maintaining strict separation of concerns.

1. Overview

The model layer implements a reactive MVC architecture where:

  • Models manage data and business logic through observable state

  • Views (UI layer) observe models for state changes

  • Controllers are the reactive bindings (like ComponentValue) that automatically synchronize models and views

All model components are built on Codion’s observable foundation, ensuring that any data or state change automatically propagates to interested observers.

2. Core Components

2.1. EntityModel

The EntityModel serves as the central coordinator, managing both edit and table models along with any detail models in master-detail relationships.

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

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

2.2. EntityEditModel

The EntityEditModel handles single entity CRUD operations and maintains the current entity state.

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    EntityEditModel editModel = customerModel.editModel();

    // Access entity values
    Value<String> nameValue = editModel.editor().value(Customer.FIRSTNAME);

    // Perform operations
    editModel.insert();
    editModel.update();
    editModel.delete();

2.3. EntityTableModel

The EntityTableModel manages collections of entities, providing sorting, filtering, and selection capabilities.

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    EntityTableModel tableModel = customerModel.tableModel();

    // Refresh data
    tableModel.items().refresh();

    // Access selection
    Collection<Entity> selected = tableModel.selection().items().get();

2.4. EntityQueryModel

The EntityQueryModel controls how data is fetched from the database, including conditions, limits, and ordering.

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityTableModel tableModel = customerModel.tableModel();
    EntityQueryModel queryModel = tableModel.queryModel();

    // Configure query behavior
    queryModel.limit().set(200);
    queryModel.conditionRequired().set(true);
    queryModel.orderBy().set(OrderBy.ascending(Customer.LASTNAME));

3. Observable Architecture

Every model component extends Codion’s observable foundation:

3.1. State Management

Models expose their state through observable values:

    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    EntityEditModel editModel = customerModel.editModel();
    EntityTableModel tableModel = customerModel.tableModel();

    // Edit model states
    State updateEnabled = editModel.updateEnabled();
    State updateMultipleEnabled = editModel.updateMultipleEnabled();
    ObservableState modified = editModel.editor().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());

3.2. Event System

Models emit events for all significant operations:

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

    // Listen for entity changes
    editModel.afterInsert().addConsumer(entities -> {
      System.out.println("Inserted: " + entities);
    });

    // Listen for selection changes
    tableModel.selection().item().addConsumer(selectedEntity -> {
      if (selectedEntity != null) {
        loadDetails(selectedEntity);
      }
    });

3.3. Value Observers

Entity values are observable and can be bound across models:

    SwingEntityModel trackModel = new SwingEntityModel(Track.TYPE, connectionProvider);
    EntityEditModel 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(newPrice -> updateTotalPrice(newPrice));

    // React to value edits
    priceValue.edited().addConsumer(newPrice -> System.out.println("Price: " + newPrice));

4. Model Relationships

4.1. Master-Detail Pattern

The framework supports arbitrarily deep master-detail hierarchies:

    // 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.detailModels().add(invoiceModel);
    invoiceModel.detailModels().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().visible().get(0);
    invoiceModel.tableModel().selection().item().set(invoice);
    // Invoice lines for selected invoice are loaded

5. Best Practices

5.1. Query Optimization

Always configure appropriate limits to prevent loading excessive data:

    class CustomerTableModel extends SwingEntityTableModel {

      public CustomerTableModel(EntityConnectionProvider connectionProvider) {
        super(new SwingEntityEditModel(Customer.TYPE, connectionProvider));
        // Prevent loading entire customer base
        queryModel().limit().set(100);
        queryModel().conditionRequired().set(true);
      }
    }

5.2. Event Handling

Use the event system to maintain consistency across models:

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

    // Update summary when details change
    invoiceLineModel.editModel().afterInsert().addConsumer(entities -> updateInvoiceTotal());
    invoiceLineModel.editModel().afterUpdate().addConsumer(entities -> updateInvoiceTotal());
    invoiceLineModel.editModel().afterDelete().addConsumer(entities -> updateInvoiceTotal());

5.3. Custom Data Sources

For specialized queries, consider custom data sources:

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

    // Fetch only Customers with emails by default
    tableModel.queryModel().dataSource().set(queryModel -> {
      EntityConnection connection = queryModel.connectionProvider().connection();

      return connection.select(and(
              Customer.EMAIL.isNotNull(),
              queryModel.condition().where(Conjunction.AND))
      );
    });

6. See Also