Skip to content

Tracked Views

Jan Wiemer edited this page Dec 30, 2020 · 10 revisions

Tracked Views

Tracked views can be used to track values derived from the original stored object and automatically keep them up to date. This avoids the need to compute the values again and again. Possible use cases for this concept could be to track cumulated values based on the stored objects, or to track indexes mapping the value of a property of the stored objects to the set of keys of all objects with this value.

Tracked View Concept

A tacked view has to implement the TrackedView<V> interface with the type parameter V set to the class of the object itself (if the stored objects have the class ObjectType the tracked view has to implement the TrackedView<ObjectType> interface). When creating the store all tracked views (instances of the view class) are registered at the tracked view registry of the store. The tracked view registry (class: TrackedViewRegistry) can be accessed from the store by calling the getTrackedViewRegistry method. All modifications done (in the commit phase) to the objects in the store are tracked at all registered views by calling the trackModification method on the view instances. This method gets the old and the new value of the object as parameter so that the differences can be tracked. This ensures that the tracked view always reflects all committed changes done to objects in the store.

If we access the view inside a transaction (by calling the getView method at the tracked view registry) a snapshot of the view is taken by creating a clone of the view. Therefore the view itself has to be clonable (the TrackedView interface extends the JacisCloneable interface). Changes committed by other transactions after the snapshot has been taken are not reflected by the snapshot. However before returning the snapshot to the caller it is updated by the changes already done in context of the current transaction. Therefore the differences of the the objects in the TX-View and the version of the object when they have been fetched from the store are tracked at the view snapshot (by calling the trackModification method). Once a snapshot of a view is taken the snapshot is stored for the current transaction and all modifications notified to the store by calling the update method are tracked at the stored snapshot to keep it up to date. Note that, even if an automatic dirty check is configured, only modifications where afterwards the update method is called are tracked after taking the view snapshot.

Tracked View: keeping it up-to-date

JACIS Tracked View 1

Traked View: taking a snapshot

JACIS Tracked View 2

Using Tracked Views

The following example of a tracked view is tracking the total balance of all stored accounts:

  public static class TotalBalanceView implements TrackedView<Account> {

    private long totalBalance = 0;

    // tracked views must be cloneable to create the snapshots...
    @SuppressWarnings("unchecked")
    @Override
    public TrackedView<Account> clone() {
      try {
        return (TrackedView<Account>) super.clone();
      } catch (CloneNotSupportedException e) {
        throw new RuntimeException("clone failed");
      }
    }

    // the method actually tracking the modification of an object
    // (for creations / deletions the oldValue / newValue is null)
    @Override
    public void trackModification(Account oldValue, Account newValue) {
      totalBalance += newValue == null ? 0 : newValue.getBalance();
      totalBalance -= oldValue == null ? 0 : oldValue.getBalance();
    }

    // An (optional) method to check the value of the view.
    // This is called after commit if configured in the JacisObjectTypeSpec passed to create the store.
    @Override
    public void checkView(List<Account> values) {
      long checkValue = values.stream().mapToLong(a -> a.getBalance()).sum();
      if (totalBalance != checkValue) {
        throw new IllegalStateException("Corrupt view! Tracked val=" + totalBalance + " check val=" + checkValue);
      }
    }

    @Override
    public void clear() {
      totalBalance = 0;
    }

    public long getTotalBalance() {
      return totalBalance;
    }

  }

The tracked view is registered after creation of the store. Later it is possible to get a snapshot of the view either inside or without a transaction.

    JacisContainer container = new JacisContainer();
    JacisObjectTypeSpec<String, Account, Account> objectTypeSpec //
        = new JacisObjectTypeSpec<>(String.class, Account.class, new JacisCloningObjectAdapter<>());
    JacisStore<String, Account> store = container.createStore(objectTypeSpec).getStore();

    // First register the tracked view
    store.getTrackedViewRegistry().registerTrackedView("totalBalanceView", new TotalBalanceView());

    // First we create some accounts to have some test data...

    container.withLocalTx(() -> {
      store.update("account1", new Account("account1").deposit(-100));
      store.update("account2", new Account("account2").deposit(10));
      store.update("account3", new Account("account3").deposit(100));
    });

    // on internalCommit the tracked view is updated automatically
    TotalBalanceView view0 = store.getTrackedViewRegistry().getView("totalBalanceView");
    System.out.println("tracked balance=" + view0.getTotalBalance());

    // inside a transaction the transaction local view of the values is respected
    container.withLocalTx(() -> {
      store.update("account1", store.get("account1").deposit(1000));
      store.update("account4", new Account("account4").deposit(101));
      // note that the getView method takes a snapshot at the time it is called...
      TotalBalanceView view1 = store.getTrackedViewRegistry().getView("totalBalanceView");
      System.out.println("tracked balance=" + view1.getTotalBalance());
      // later updates are not tracked by this snapshot
      store.update("account1", store.get("account1").deposit(1000));
      System.out.println("tracked balance old snapshot=" + view1.getTotalBalance());
      TotalBalanceView view2 = store.getTrackedViewRegistry().getView("totalBalanceView");
      System.out.println("tracked balance new snapshot=" + view2.getTotalBalance());
    });

Clustered Views

Sometimes the tracked view itself contains quite a lot values (e.g. if used as an index). In this case a drawback of the approach described above is that each time we take a snapshot the whole view including all values have to be cloned. This can be quite a lot of effort if we only need some of the values stored in the view. As an improvement in such situations the concept of clustered tracked views was invented. The idea is that only those values have to be cloned that are really needed by the application accessing the view. We can see a clustered tracked view as a map assigning a key for a sub-view (a discriminator) to an actual tracked view. Clustered tracked views have to implement the TrackedViewClustered interface declaring the methods getSubViewKeys to get all sub-view keys and getSubView to access the sub-view for a key.

To access the values the tracked view registry provides the method getSubView getting the sub-view key in addition to the view name as parameter. If the values of the sub-view are accessed in this way only the sub-view id cloned for the transactions.

Next Chapter: Transaction Adapter