You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 11 Next »

Related JIRA issues: 

DEV-884 - Getting issue details... STATUS

DEV-885 - Getting issue details... STATUS

Module View Presenter

Reasons to botherWhat changesBenefits
  • Way too many interfaces where a mere class would do (see IoC part as well)
  • The way we used it before is cumbersome and not too beneficial.
  • Concept of Model is not well defined.
  • Poor component isolation patterns
    • views know about sub-views, presenters know about sub-presenters [...and sub-views].
    • poor state management (the parent views/presenters may just have most of the child view's state/business logic).
  • We try to prefer classes over the interfaces. If we introduce interfaces, we try to keep them small and function-oriented.
  • Instead of MVP we use something like MVVMP (Model-View-ViewModel-Presenter)
    • Model is the good ol' datasource like JCR workspace.
    • View is now the entry point and the driver of the pattern. Views may compose and even interact with each other (though we we should not encourage that!)
    • ViewModel is the state of the View (all the properties that define the view in real time)
    • Presenter - optional, completely isolated part which is used to help View interact with Model/ViewModel
  • We implement the ViewModel part via so called ViewContexts - interfaces that describe a certain part of mutable state (selection, value, location etc).
    • We provide the views with the ability to bind contexts and share them with the sub-views (see more in the IoC section).
    • For the ease of use, we do not enforce the developer to actually implement the interfaces, we generate them. (question) We use some reactive technology for convenient subscription.
  • Views are more component-like and are easier to compose.
    • Interfaces/classes do not expose methods like #refresh(), that encourage external control over the view.
    • All the internal view state management happens in the view itself (no case when sub-app reacts on events and updates the state of the sub-views/components).
    • All the necessary context can be injected via ViewContexts
  • Some synergy with how client-side frameworks manage the UI's (Redux/React contexts do similar things though in more conventional way)
  • Less code to write (less noisy listener interfaces, less abstractions)

Inversion of control (IoC) capabilities in UI

Reasons to botherWhat changesOutcome
  • Too many Guice components
    • type mappings should just do the trick
  • Guice does not support generics
  • No good support for View-scoped components
    • Can only share sub-app components which is not enough often
  • Hard to share such essential objects as definitions

ViewProvider API

Special factory API that creates the view with all the following additional features.

Bean Storages for the views

Each framework view gets the bean storage (same as app/sub-app) and can store objects in there. Each view can create sub-views and their storages will form an hierarchy with the parent's which makes it easy to traverse the parent storages from the child ones.

Sharing objects between views

Each view can bind view contexts (see MVP pattern changes) and publish other objects like definitions so that all the child views can access them effortlessly

ComponentProvider for every view

Each view gets its own ComponentProvider bound to the view's UI key. Each such component provider is enhanced with two additional ParameterResolvers:

  • ViewContextParameterResolver - upon ComponentProvider#newInstance calls this one looks up the constructor dependencies from the bean storage hierarchy (e.g. shared with the mechanism above).
  • DatasourceComponentParameterResolver - provides support for the datasource related components injection (see ContentConnector changes description)

Benefits:

  • Ability to share configuration/resources between the views without coding overhead.
    • E.g. a root view can share some context; and then all the sub-views on all the levels can inject that context without any need to pass it through all the intermediate parents.
  • Better support for the pattern improvements described above.

Questions:

  • (question) How to know when to clean up the view context?
    • We only have one similar example - choosers, their context is cleaned up when dialog is closed.
    • Should we also attach this logic to maybe Vaadin attach/detach events?
    • Should we communicate that the views/sub-views should be closed manually?
  • (question) How to attach the sub-views properly?
    • should we do it manually?
    • should we pass the parent container/attachment lambda? (same might work for closing of the view as well)
  • (question) We probably require additional tooling to make the thing

Field definitions

Reasons to botherWhat changesBenefits
  • Not generic
    • value type is communicated in "JCR" style, i.e. via strings

FieldDefinition becomes generic

Better compatibility with the new, more type-safe Vaadin data-binding API's

Form definition

Reasons to botherWhat changesOutcome
  • Form definition is nailed to the concept of the tabs
    • presentation definition is entangled with the model

FormDefinition separates field/property definitions from the layout definition

  • Tabbed layout becomes just a concrete case of the layout definition
  • We provide other types of layouting possibilities - HTML/Vaadin declarative layout/Custom Component

(question) Possibly we introduce an alternative term 'editor' which is more generic than the form

Benefits

  • More flexibility
  • Possibility to define complex fields with the same definitions that we use for the forms
    • i.e. complex fields also become forms

Questions

  • (question) How to provide compatibility between the current form definitions and the new ones?
    • Resolve on programmatic level, i.e. allow special definitions that take the old one and then the app/dialog transforms them into the new ones?

ContentConnector

Reasons to botherWhat changesBenefits




Data binding in grids and tree grids

Reasons to botherWhat changes

Vaadin 8 brings in new concepts to the data binding

  • no more Item abstraction → no more JcrNodeAdapter
  • no more Container → no more JcrContainer

DataProvider

  • Container concept replacement: interface for querying the data from a datasource
  • Works with domain objects (e.g. JCR Nodes) instead of Items
  • Stateless by design (one can implement caching manually though)

PropertySet

  • Item concept replacement
  • Unlike Item is more or less stateless component itself - only describes how to interact with the domain object to access [and modify] its properties via funtional primitives (lambdas), e.g. via bean getter/setter method references.

Benefits

  • Simpler and slimmer way to connect to the datasources
  • Less memory consumption (no additional abstractions over the domain objects)
  • Easier to implement various providers (REST, ORM...)
  • Supposedly better implementation hierarchical data providers

Concerns

  • (warning) DataProvider still requires item indexing API
  • (warning) Size query API still needs to be implemented (bummer for JCR list views)
  • (question) Observation of the DataProvider changes is still to be drafted!

Complex fields

Reasons to botherWhat changesBenefits




value transformers

Reasons to botherWhat changesBenefits




Content changed events





THE FOLLOWING STILL REQUIRES GROOMING



(plus) Affected parts:
UI framework in Magnolia involves different components and abstractions that function on different levels. In the scope of the current effort we plan to target/re-work only some of them.

  • Content app de
  • Content app implementation.

Main goals

The main purpose of the effort is to simplify the existing concepts

Current state overview

Framework should help the developers to build their apps with Magnolia faster, get access to the components and features, framework though should not be all the time in the way of the developer.

The current offering of the UI framework:

  • Content app implementation 
  • Form framework 
  • Flexibility via configuration of the content apps
  • Dependency injection via Guice and factory capabilities of the ComponentProvider
  • EventBus API to share events between the UI parts

The problems:

  • Presenter-driven MVP. Was covered before, boils down to the need of having two parallel hierarchies: presenter hierarchy and view hierarchy, with presenters being aware of too much. This blocks the UI composability and responsibility isolation. 
    • Solution proposal - let the views drive the process, treat presenters as optional, view-specific detail that merely helps to separate communication with the backend.
  • Interface vs classes and IoC. Every time we'd need to introduce a component/abstraction in UI, we would try to define its interface and then provide an impl class. 
    • As a result - lot's of redundant Listener interfaces between views and presenters.
    • Each view and a presenter as a result is a Guice component mapped in the module descriptor, making it hard to override the parts with custom implementations.
    • Can we treat views more as custom Vaadin components? Can we rely more on ComponentProvider#newInstance more when creating the views and merely using the impl classes coming from e.g. definitions?
    • Possible solution: provide views with out-of-the-box component provider and #create() API, let them create sub-views easily.
  • State management. Complex UI's state might get complex and synchronising the state over several different disjoint parts of the UI is hard: leads to lot's of boilerplate code, "redundant" states (i.e. the same parameters have to be cached in different places) etc. Reactive View contexts are supposed to help with this: define the interface that describes the state of the UI (or its part), bind it to the parent view, let the child views access, listen and mutate the context.
  • Configuration sharing. We have to pass definitions from the parent view to the child ones via API or somehow else. Some parts of the configuration are handy to share app/sub-app wise though, for instance the definition of the domain/backend the UI operates over. Our current solution to this is the ContentConnector abstraction, which in turn has several problems: 
    • it is not generic
    • it only facilitates transformation API between different item representations (id ↔ full item ↔ url fragment)
    • it does not provide actual data binding API. Whenever we need to bind e.g. Grid to a datasource - some additional logic is needed. At best we can re-use the content connector definition in some form, to construct Vaadin data binding layer

      As a solution, it might be reasonable to allow the UI's to 'publish' the generic data source definitions, so that they are available to the sub-views and whenever one needs to bind to the backend - it can consume the ds definition and either construct smth ad-hoc or inject a ds components (e.g. DataProvider) automatically constructed from the current definition (later about this).

How to share context and provide injectable components

  • Rely less on the Guice injection when it comes to the views. 
    • Views rarely require 'components' with specific lifecycle (typically they just need an instance of smth).
    • Views, especially the ones that are bound to generic data, require generic instances to be provided. Guice does not support that easily (and our XML configs completely block that).
  • Enhance the views with the 'factory' capabilities that abstract away the communication with the session store.

    FrameworkView
    public interface UiFrameworkView extends View {
    
        ...
    
        /**
         * Wrapper around {@link ComponentProvider} capabilities.
         */
        default <T> T create(Class type, Object... args) {
            return getComponentProvider().newInstance((Class<T>) type, args);
        }
    
        default ViewProvider getViewProvider() {
            return new ViewProvider.Impl(getCurrentViewReference());
        }
    
        default ComponentProvider getComponentProvider() {
            return new ViewComponentProvider(getCurrentViewReference());
        }
    
        default UiContextReference getCurrentViewReference() {
            return ViewContextKeyRegistry.access()
                    .lookUp(this)
                    .orElseGet(() -> CurrentUiContextReference.get().getUiContextReference());
        }
    
        default <T extends ViewContext> T bindContext(Class<? extends T> contextClass) {
            final T context = new ViewContextProxy().createViewContext(contextClass);
            SessionStore.access().getBeanStore(getCurrentViewReference()).put(contextClass, context);
            return context;
        }
    
        default void bindDatasource(Object definition) {
            SessionStore.access().getBeanStore(getCurrentViewReference()).put(DatasourceHolder.class, new DatasourceHolder(definition));
        }
    }
    
    
    

How to bind to different data sources

The following could be a looser and more flexible replacement for the ContentConnector abstraction. Instead of having the monolithic interface, we rather could facilitate a DatasourceSupport that could provide data-binding implementations based on the exposed configuration by e.g. delegating to the registered reference implementations. 

@Singleton
public class DatasourceSupport {

    private final Map<Class, DatasourceBundle> bundles;

    @Inject
    public DatasourceSupport(Set<DatasourceBundle> bundles) {
        this.bundles = bundles
                .stream()
                .collect(toMap(
                        DatasourceBundle::supportedDataSourceType,
                        identity()));
    }

    @SuppressWarnings("unchecked")
    public <DEF> DatasourceBundle<DEF> getDatasourceBundle(DEF def) {
        Objects.requireNonNull(def);
        return Optional.ofNullable(bundles.get(def.getClass())).orElseThrow(() -> new IllegalArgumentException("No such bundle for the type " + def.getClass()));
    }
}



@Multibinding
public abstract class DatasourceBundle<DEF> {

    private final Class<DEF> type;

    public DatasourceBundle(Class<DEF> type) {
        this.type = type;
    }

    public Class<DEF> supportedDataSourceType() {
        return this.type;
    }

    public abstract <T> T lookup(Class<T> type, DEF definition);
}
Provisioning of datasource components
public class DatasourceComponentParameterResolver implements ParameterResolver {

    private final DatasourceSupport datasourceSupport;
    private final Object datasourceDefinition;

    public DatasourceComponentParameterResolver(DatasourceSupport datasourceSupport, Object datasourceDefinition) {
        this.datasourceSupport = datasourceSupport;
        this.datasourceDefinition = datasourceDefinition;
    }

    @Override
    public Object resolveParameter(ParameterInfo parameter) {
        boolean isDatasourceComponent = Stream.of(parameter.getParameterAnnotations()).anyMatch(DatasourceComponent.class::isInstance);
        if (isDatasourceComponent) {
            return datasourceSupport
                    .getDatasourceBundle(datasourceDefinition)
                    .lookup(parameter.getParameterType(), datasourceDefinition);
        }

        return UNRESOLVED;
    }
}
Questions:

  • It is pretty clear that DataProvider and PropertySet domain-specific implementaions are required. But what else?
  • (question) optional HierarchicalDataProvider and potentially additional Hierarchy support for parent resolution (not covered by HDP interface in Vaadin).
  • (question) utilities to "serialise"/"deserialise" items to and from URL fragments.
  • (question) utility to "describe" the items (for status purposes and such).

Databinding in Grids

  • Datasource support introduction might be a nice matching for the grid databinding requirements, since what is mostly required is the DataProvider (to fetch the row data) and the PropertySet (for the column rendering).
  • Apparently it is possible to pass the PropertySet instance directly into the Grid making it aware of the underlying datasource properties, then configuration of the columns is just about assigning the captions and renderers.



  • No labels