Code Monkey home page Code Monkey logo

arez's Introduction

Arez

Arez

Build Status codecov GWT3/J2CL compatible

Arez is a simple, efficient and scalable state management library for client-side applications. Arez is powered by the reactive programming model found in spreadsheets, a dataflow programming model. Arez tracks usage of observable data and notifies observers when the observable data changes. The library scales from simple, flat domain models to complex, highly inter-connected graph-like domain models. Arez trades a higher memory usage for a faster execution speed and better developer experience. Arez is optimized for developer happiness by eliminating the need to monitor state changes. Instead, applications react to state changes on demand.

Arez is under heavy development, and sometimes the documentation does not keep up to date. However the goal of the toolkit is to be easy to use, and this includes clear and concise documentation. If something is unclear please report it as a bug because it is a bug. If a new user has a hard time, then we need to fix the problem.

For more information about Arez, please see the Website. For the source code and project support, please visit the GitHub project.

Contributing

Arez was released as open source so others could benefit from the project. We are thankful for any contributions from the community. A Code of Conduct has been put in place and a Contributing document is under development.

License

Arez is licensed under Apache License, Version 2.0.

Credit

  • Stock Software for providing significant support in building and maintaining Arez. The company was willing take a risk and base their next suite of applications on an idea and the bet paid off.

  • The toolkit began life as an attempt to port Mobx to java. Not all of the Mobx primitives made sense in a java world. There were also places where it was felt that we could improve on the Mobx model, at least for our environment. The idea of a "port" was soon discarded and Arez became a reimplementation of similar concepts. However there are some places (i.e. Transaction.completeTracking()) where the Mobx heritage is clear. Credit goes to Michel Weststrate and the Mobx team for their clean conceptual model and some inspirational talks. Later in the life of Arez, parts of the Mobx documentation acted as inspiration or were directly copied and modified (i.e. MobX's (@)computed) docs were used to seed docs/computable_values.md).

  • Other frameworks that influenced or are influencing the development of Arez include;

arez's People

Contributors

dependabot[bot] avatar realityforge avatar y2kbcm1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

arez's Issues

Generate an error if an Arez annotation is used outside ArezComponent type

Consider scanning usage of all Arez annotations in the annotation processor. If an Arez annotation appears in an unexpected place (i.e. outside of an @ArezComponent annotated class) then generate an error. This avoids the scenario where @Action annotated methods outside of an @ArezComponent annotated class are ignored but the developer is none-the-wiser.

To make this safe we may need to add a @ActAsArezComponent annotation, an annotation targeted annotation, that the annotation indicates an arez component. See @ActAsStingComponent from the sting for example of the intended usage.

The @ActAsArezComponent would be added to @ReactComponent annotation from the react4j. We may also need to introduce an annotation like@ArezComponentFragment (or @PartialArezComponent) with the @ActAsArezComponent present so we can partially define arez components.

The existing @ActAsComponent would be reasonably confusing after these changes were made and we may need to either add a @ReferenceTarget to replace it and/or enhance the annotation processor to use @ActAsArezComponent types as reference targets.

Expose CRDT elements as arez components

Take a CRDT implementation such as yjs and expose CRDT values as arez components. The Arez components could publish the changes to the "network" and allow local changes and the observers of the model would treat either set of changes as the same.

State serializer

Several applications would have benefitted if Arez supported a mechanism to serialize observable data from components. Mobx State Tree has an interesting take on this problem and it may provide inspiration if we go down the path of serializing component data as immutable JSON-like data.

This may involve:

  • adding additional lifecycle methods on the components (i.e. the equivalent of onSnapshot())
  • working out mechanisms to determine how components in components are serialized (i.e references could serialize component or serialize id reference). i.e. Is the relationship a reference or a containership relationship.
  • deserialization strategies to various mediums (i.e. JSON etc) and how do we resolve references.
  • extracting part of the logic from replicant that already does some of this.
  • Interesting way to update and transmit changes via JSON patches

Of course, it is possible that the best way to do this is to have another tool that generates Arez components based on a schema language.

Add option to remove isDisposed for void actions and @Observable setters

In large react4j applications it is common to use Arez for all reactive state including view state but due to the way react4j works, sometimes callbacks can occur after the view component has been disposed. (i.e. MouseLeave events frequently called into after the view has been disposed and quite often onSuccess and onError callbacks from async rpc requests call back to update UI state). This forces the developer to explicitly add checks such as if ( Disposable.isNotDisposed( ... ) ) { ... } which is cumbersome and error prone.

A slightly better option would be to update annotations to @Action and @Observable so that we could just skip the set or action if the containing entity is disposed.

Add parameter to @Memoize that specifies code to perform equality check

Very early on in Arez's development, it was possible to supply a function that determines whether the old memoized value and the new memoized value were the same value. However it was removed as it never seemed to be needed. The one place we are consistently seeing the need for this is when @memoize methods return native js types. It would be useful to be able to supply a function that compares whether two values are equal in these scenarios to avoid unnecessary change propagation.

Expose Arez as a TypeScript/JavaScript library

Using J2CL, it would be possible to compile a version of Arez usable by vanilla Javascript, Typescript or Closure annotated javascript.

For this to be useful it would be necessary to explicitly define a javascript API layer in a package such as arez.js.* that provides access to the ArezContext and factory methods in a fashion suitable for consumption by a javascript application. A component model for javascript applications could also be created which would most likely draw heavy inspiration from MobX.

This would allow us to use a single codebase across multiple applications and integrate with the same spy and tooling infrastructure.

Specify defaultPriority in @ArezComponent

Arez components can have @Memoize and @Observe components and they currently have a default priority of NORMAL. In some situations, it is expected that all contained active elements have the same priority. i.e. in React4j components, it is expected that active elements will default to LOWEST priority with the one exception being render that has LOW priority. This is true of many ancillary services in react4j that are used to support rendering.

In these cases, it is possible to explicitly prioritize every active element but this has proven to be complex and error-prone. React4j addresses this by generating code using @PriorityOverride to hook into Arez's internals. The Arez services supporting the react4j components do not have this luxury. To support this use case we could add a defaultPriority parameter to @ArezComponent that will be the default priority of active elements.

Consider consolidating @CascadeDispose and @ComponentDependency

The annotations @CascadeDispose and @ComponentDependency both define strategies for how to cascade a dispose of one entity to another entity. Currently, the toolkit does not support annotating a single method as both @CascadeDispose and @ComponentDependency and nor has there been any demand to introduce this functionality.

In Galdr (another component framework under development), a single Link element joins two components at runtime and controls cascading of dispose in either direction or in both directions. The Link element has two boolean fields cascadeSourceRemoveToTarget and cascadeTargetRemoveToSource that control how dispose actions travel along links.

This is something that we could experiment within Arez to see if there is any merit in collapsing these separate concepts into a single concept

Add a promise-returning "when" observer

The arez-when library was originally extracted from the Arez core. The when observer watches a condition function and when the function returns true it disposes itself and calls an effect action. Rather than directly invoking an action, the observer could instead return a Promise that is resolved when the condition is true. This would allow it to be integrated into more traditional promise-based workflow.

Of course this suggests that When.when(...) may be inappropriate as an API for all code that watches a condition. Perhaps the library could be refactored to Watcher.on(...)?

Spritz integration

Use Spritz, a reactive streaming library, to stream changes into ComputableValue instances. The stream would manually trigger ComputableValue.reportPossiblyChanged() when a new value arrives. This would ultimately allow us to add mechanisms to control when reaction tasks occur. Some obvious candidates include:

  • minimumDelay: Must wait a minimum time before re-executing
  • debounceTime: Changes are ignored for a time after executing to avoid frequent changes
  • throttleTime: Track when executed and reschedule when at least throttleTime has passed. This is similar to minimumDelay, except that the initial run of the function happens immediately.

The component model may need built-in support for this. Perhaps a @StreamingValue could be made up of a stream producing method and a value returning method. (Perhaps this support could be merged into #91 somehow).

Another useful addition may be the ability to push changes from ObservableValue instance and ComputableValue instances into streams. These changes could either be pushed inline within the READ_WRITE transaction or could be pushed as a task passed to the scheduler. This would support several alternative approaches when architecting applications.

Add @ObservableInitial annotation

Add the ability to specify the initial value of an abstract @Observable annotated property. When we need this functionality we either manually specify the field and initialize it as concrete @Observable or we mark the property as initializer = Feature.ENABLE and pass in a constant value to the constructor in the generated class.

This creates a reasonable amount of boilerplate that could be removed with another annotation.

Generate unmanaged component reference warnings when component fields are passed in

Arez currently generates warnings when we have fields with component-like types in it and similar types are not passed in via constructor parameters. The intent was to skip types that are injected but our downstream applications regularly create components and pass in entities as constructor parameters. We should refine ArezProcessor.warnOnUnmanagedComponentReferences(...) so that instead of using constructor parameters to indicate references are safe, we instead detect @ArezComponent(service=ENABLE) (or @ArezComponent(sting=ENABLE) or @ArezComponent(dagger=ENABLE)) on the type to determine whether reference is safe.

Support @CascadeDispose on abstract @Observable properties

Support @CascadeDispose on abstract @Observable properties.

There are many places in downstream users where a property is made non-abstract just so it can have @CascadeDispose applied to the field. Fix the downstream users when this fix lands

Add a DocumentVisibility component

A new top-level project arez-document-visibility should be created that captures and exposes properties from the document.visibilityState API. The simplest approach would be to make a a @computed property that captures the value of visibilityState. The computed would then update based on the 'visibilitychange' event [1] It may also be desirable to add a further computed for each sub-property of visibilityState that you want to expose so that useful names appear in WhyRun and/or dependency tracing.

https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilityState

Can arez.ArezContext be made Disposable?

Currently there is no way to dispose an ArezContext and release all resources associated with it. In applications that do not create new instances of Zone it probably is not necessary as the ArezContext is a global singleton and it would be difficult to ensure no one referenced it after it has been disposed.

However if we were to make ArezContext disposable (in the case where Zones are used) it would probably be possibly to track all components by enabling registries. A dispose would then pause the scheduler for that zone, iterate over the registries and dispose of all the resources.

If this is ever needed it would need to be a compile-time option as it is expected that it would add some overhead. Invoking dispose when the configuration is disabled would generate in invariant failure.

Consider changing @ArezComponent.requireId AUTODETECT behaviour

The default behavior of the @ArezComponent.requireId parameter, when set to AUTODETECT, is to mark it as enabled if:

  • a method annotated with the @Inverse annotation is present.
  • a method annotated with the @ComponentId annotation is present.
  • a method annotated with the @ComponentIdRef annotation is present.

This can create very confusing behavior when an id is removed from a component due to changes in one of these values. A less confusing approach may be to default AUTODETECT it to ENABLE and generate an error if one of the @Inverse, @ComponentId or @ComponentIdRef annotations is present.

To make migration of existing code easier we could also generate a suppressable warning id a user explicitly specifies ENABLE value. This would also simplify integration with downstream projects such as react4j that must have requireId=ENABLE to enable some features (being an immutable prop in the case of react4j).

Add names to zones

Zones should have a unique name. The unique name would be present in serialised forms of events when zones are enabled. This primarily enables multiple zones to operate within a single application without interfering or causing confusion to devtools

Add a Gelocation component

A new top-level project arez-geolocation should be created that captures and exposes geolocation data. The simplest approach would be to make a a @Computed property that captures the results of calling navigator.geolocation.watchPosition(...). The expectation is that this would be a single result object that contains a Position, a PositionError and a boolean flag loading. These can be further encapsulated in other @Computed properties such as isError(), getLatitude(), getLongitude

This has already been implemented as an Arez component in the FEM prototype but it could just as easily be re-written. It may also be useful to look at how react-fns [2] does this when exposing similar (same?) information in react ecosystem.

[1] https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition
[2] https://react-fns.netlify.com/docs/en/api.html#geoposition

Add TweenedValue component

Of particular use when creating annotations is a TweenedValue component. It transforms a value to a targetValue over a duration with the motion controlled by an easing function (much like the css easing function).

It likely has several observable properties (i.e. value, targetValue, transitionStart) as well as parameters that control the animation (i.e. duration, easing) and may also have boolean flags that can be observed to react on triggers (i.e. animationStarted, animationCompleted)

Standardize the mechanisms for copy annotations to component/implementation class

A few attempts have been made to build on Arez functionality in other downstream projects. One challenge is how to copy annotations to generated subclasses to trigger other annotation processors. See arez/arez-persist#4 for a description of such an issue.

It would be useful to define a mechanism by which Arez derived frameworks could cooperate and allow mixing frameworks. An annotation on an annotation style approach should do the trick. This would make all sidecar solutions a lot easier to implement.

Emit JSON descriptors when emitting Arez components

The Arez annotation processor currently performs significant work when analyzing the component and deriving the component characteristics (i.e. does it generate ids, is disposableNotifier enabled, which observable properties are present etc).

Downstream annotation processors often try to guess at the shape of the Arez component so that they can enhance the component in some way. This approach is somewhat expensive and error-prone. See arez/arez-persist#1.

A better approach may be to have Arez emit a JSON descriptor that can be consumed by downstream libraries.

Add a WindowSize component

A new top-level project arez-windowsize that exposes window.innerHeight, window.innerWidth, window.outerHeight and window.outerWidth as observable properties. The component would listen to the resize event on the window and update as appropriate.

Enhance BrainCheck integration

We should be able to configure arez/braincheck so that some invariants just report problems rather than generating an exception. i.e. View X does not observe any observables but is marked as TRACKED should be warn in most cases.

Compile-time option to pool Arez elements

Currently when there is major state changes in Arez (i.e. changes between data rich apps in web apps) this can produce a lot of garbage to be collected by the runtime.

One way around this would be to support pooling of ObservableValue, ComputedValue and Observer instances. When the components are disposed we could put them in a CircularBuffer that we could use to recycle them. It would need to be a compile-time option to avoid overhead for apps that do not need this capability.

It is unclear how beneficial this would be and would need to do some significant performance analysis and measuring before enabling this capability but it has some merit for some applications.

Extract Component API

Arez is really made up of two parts. The reactive primitives and the component API that is driven by the annotations and the annotation processor. Over time the component API has grown to be more than just a thin veneer on top of the core arez primitives. It is also expected that as more capabilities are added to the API such as deeper integration with a reactive streaming API (See #108), a transport and/or serialization API (See #110) that the component API may be better extracted into a separate
project.

If so khumbu looks like a nice vacant name.

Emit component metadata from the annotation processor

Downstream toolkits often mandate a particular configuration for an arez component. This currently requires a deep analysis of the arez component model which is computationally expensive, error-prone, and brittle.

We could "fix" this by emitting a JSON descriptor beside the arez component in the ArezProcessor. Downstream toolkits could read this descriptor and perform the analysis based on the contents of the descriptor which will be much faster to parse and requires no analysis. (Sting uses a similar strategy and process binary descriptors emitted by itself at later stages).

This would fix issues such as arez/arez-persist#1. The ArezPersist annotation processor would defer processing until the descriptor is available and then use the descriptor emitted by Arez. The metadata could also be used in toolkits such as react4j where they need to look at props to determine whether the type is a candidate for being an immutable prop.

The way to implement this is probably as another module within arez that reads and writes the descriptors which could be used by arez-processor and potentially arez-persist-processor and react4j-processor etc.

Simplify creation of @Memoize methods that resolve asynchronously

In almost every app that uses Arez, there is a desire to have a @Memoize annotated method that derives the result value from an async source. i.e. The value could be the result of a remote http call. The remote call would only be made if the @Memoize annotated method was observed and any in-flight http calls should be cancelled when the @Memoize annotated method is unobserved (or when parameters for remote call have changed). Once the remote call returns it should flag the @Memoize method as changed so observers can re-run.

Ideally, Arez should provide a simple tool or example to support this use-case

Import CachedRelationship from downstream projects

Almost all of our downstream projects perform mapping from a domain model to a view model. So we may have a domain model Resource that is sourced from a remote service which is then mapped to a ResourceModel or ResourceViewModel. The ResourceViewModel is used by the UI and there may be different models such as ResourceView1Model and ResourceView2Model depending on the view being presented to the user. To model this we typically have a bunch of code like:

@ArezComponent
abstract class ResourceViewModel
{
  @ComponentDependency
  final Resource _resource;
  ...
  ResourceViewModel(Resource resource) { _resource = resource; }
  ...
  public int getSomeStatistic() { return ... call method on _resource....; }
}

The tricky part comes when the Resource contains child components (let's say Attribute) that needs to be mapped to a client-side view model (let's say AttributeViewModel) and may need to be filtered.

Downstream applications all have a similar bit of code that used to do this that has been copied from project to project:

public final class CachedRelationship<F, T>
{
  @Nonnull
  private final Supplier<Stream<F>> _fromSupplier;
  @Nonnull
  private final Function<F, T> _factoryMethod;
  @Nullable
  private final Function<Stream<T>, Stream<T>> _toStreamFilter;
  @Nonnull
  private Map<F, T> _cache;

  public CachedRelationship( @Nonnull final Supplier<Stream<F>> fromSupplier,
                             @Nonnull final Function<F, T> factoryMethod,
                             @Nullable final Function<Stream<T>, Stream<T>> toStreamFilter )
  {
    _fromSupplier = Objects.requireNonNull( fromSupplier );
    _factoryMethod = Objects.requireNonNull( factoryMethod );
    _toStreamFilter = toStreamFilter;
    _cache = new HashMap<>();
  }

  @Nonnull
  public List<T> getRelated()
  {
    final Map<F, T> existing = _cache;
    _cache = new HashMap<>();
    final Stream<T> toStream =
      _fromSupplier.get()
        .map( from -> {
          T to = existing.computeIfAbsent( from, _factoryMethod );
          // In case the relationship is contained in a computed and has been deactivated
          // and then reactivated, but the model in which it is used does not have a @OnDeactivate
          // method that clears it out. This acts as an additional check
          if ( Disposable.isDisposed( to ) )
          {
            to = _factoryMethod.apply( from );
          }
          _cache.put( from, to );
          return to;
        } )
        /*
         * This next observe is required so that disposeOnDeactivate works.
         */
        .peek( ComponentObservable::observe );
    final Stream<T> result = null == _toStreamFilter ? toStream : _toStreamFilter.apply( toStream );
    return result.collect( Collectors.toList() );
  }

  public void clearRelated()
  {
    _cache.clear();
  }
}

And we change the ViewModel to look something like

@ArezComponent
abstract class ResourceViewModel
{
  @Nonnull
  private final CachedRelationship<Attribute, AttributeModel> _attributes =
    new CachedRelationship<>( this::getAttributeStream,
                              this::createAttributeViewModel,
                              this::orgUnitSorterStream );
  ...
  @Memoize
  public List<AttributeModel> getAttributes() { return _attributes.getRelated(); }

  @OnDeactivate
  final void onAttributesDeactivate() { _releaseModels.clearRelated(); }
  ...
}

This is somewhat error-prone the most frequent problem being that developers forget (or are too lazy) to add the onDeactivate hook. A way of fixing all these problems would be to make this support into the framework by adding two annotations. One is an abstract method ala public abstract List<AttributeModel> getAttributes(); that will be implemented by the framework and one is a method that returns a tuple for relationship data ala RelationshipMapper getAttribtuesRelationshipMapper() { return RelationshipMapper.create( this::getAttributeStream, this::createAttributeViewModel, this::orgUnitSorterStream ); } with associated annotations.

This would cut down significantly on a source of errors and significant noise inside the application code.

The only problem is coming up with a useful naming scheme for methods and annotations ... @MappedRelationship?@TransformedRelationship.... ???

Add a timer based observer

It may be possible to create another arez component that periodically runs an action every N milliseconds and updates observable state based on reading state in "external" world. This would be useful when you want to scan an external element (i.e. document.title?) that has no change events but you want to monitor for change. This would be @Computed state so that the timer is not running if you are not observing the state.

Unclear if this really could be extracted in a way that is useful and less complex than just implementing it when you need it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.