com.almworks.jira.structure.api.pull.VersionedDataSource Maven / Gradle / Ivy
Show all versions of structure-api Show documentation
package com.almworks.jira.structure.api.pull;
import com.atlassian.annotations.PublicApi;
import org.jetbrains.annotations.NotNull;
/**
* {@code VersionedDataSource} establishes a way for sources of some data to version their content, to allow
* caching and incremental updates.
*
* Data source uses {@link DataVersion} to tag every internal update of the data. Clients request the updates
* to the data through {@link #getUpdate(DataVersion)} method, passing the last version of the data they have seen.
* Data source replies with a sub-class of {@link VersionedDataUpdate}, which carries the updated data and
* the new version.
*
* A data source may provide incremental updates in case the difference between the state of the data on the
* client and in the source can be identified. {@code DataVersion} class enables that through the use of {@code signature}
* field, which identifies the sequence (or "version space") for the incremental numeric versions, stored in
* the {@code version} field. See {@link DataVersion}.
*
* Pseudo-code for working with {@code VersionedDataSource} would look like this:
*
*
* VersionedDataSource<MyDataUpdate> source = ...;
* DataVersion lastVersionSeen = DataVersion.ZERO;
* while (...) { // this could be event-based or user-initiated periodical updates
* MyDataUpdate update = source.getUpdate(lastVersionSeen);
* if (update.isEmpty()) {
* // usually, do nothing -- nothing has changed, we have the latest version
* } else if (update.isFull()) {
* // full update -- previous state can be removed, and new state taken fully from the update
* ...
* } else if (update.isIncremental()) {
* // incremental update -- the new state can be produced by taking the difference from the update and applying it to the old state
* ...
* }
* lastVersionSeen = update.getVersion(); // update lastVersionSeen so next time we request updates based on what we have
* }
*
*
* Of course, the cycle can be replaced with anything else, like polling. It does not have to be periodic, but
* the longer time has passed since the update, the less is the chance that incremental update is possible.
*
* Laziness a.k.a. Pull Architecture
*
* An important aspect of most services providing {@code VersionDataSource} is laziness. They are ready to answer
* the calls to {@link #getUpdate(DataVersion)}, but if that requires some calculations or getting data from other
* sources, those activities get delayed until the moment {@link #getUpdate(DataVersion)} is called. That lets us
* keep many data sources, with potentially costly update operations, in memory, and spent resources only on those
* that are actually requested by someone.
*
* In the same spirit, data sources can be chained. Say, data source A combines output from data sources B and C.
* It subscribes to both B and C and keeps track of the last seen version in both data sources. When it receives
* {@code getUpdate()} call, it proceeds to call {@code getUpdate()} from B and C and then combine the result.
* Neither data source has to do anything until a user or an active component actually calls {@code A.getUpdate()}.
*
* @param type of the data
*/
@PublicApi
public interface VersionedDataSource {
/**
* Returns an update based on the version of the data that the client has.
*
* When the caller does not yet have previous state and its version, use {@link DataVersion#ZERO}. Full update
* is guaranteed in this case.
*
* If data source depends on other data sources or has pending changes, this call will cause the
* source to become up-to-date and perform any necessary calculations.
*
* @param fromVersion version of the data that is known to the caller
* @return an update that can be applied at the caller site to get the up-to-date state
*/
@NotNull
T getUpdate(@NotNull DataVersion fromVersion);
/**
* Returns the current version of the data without triggering data source's recalculation.
*
* This can only be used for diagnostics or similar purposes —
* correct continuous update algorithm would use {@link #getUpdate(DataVersion)} only.
* There are two reasons for that:
*
* - if you call {@code getCurrentVersion()} and then {@link #getUpdate(DataVersion)}, a concurrent modification
* may happen in between, and
* - if data source needs to do some calculations to come up with a newer version of data, this call will not
* trigger it (unlike {@link #getUpdate(DataVersion)}.
*
*
* @return the most recent version of the data known to the source
*/
@NotNull
DataVersion getCurrentVersion();
}