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

Compare with Current View Page History

« Previous Version 7 Next »

Related JIRA issues: 

DEV-884 - Getting issue details... STATUS

DEV-885 - Getting issue details... STATUS

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