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

Compare with Current View Page History

« Previous Version 3 Next »

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. 
  • Enhance the views with the 'factory' capabilities that abstract away the communication with the session store

    FrameworkView
    public interface UiFrameworkView extends View {
    
        /**
         * Convenience method implementation.
         */
        @Override
        default Component asVaadinComponent() {
            if (this instanceof Component) {
                return (Component) this;
            } else {
                throw new RuntimeException();
            }
        }
    
        /**
         * Wrapper around {@link ComponentProvider} capabilities.
         */
        @SuppressWarnings("unchecked")
        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

@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;
    }
}





  • No labels