Skip to content

Design Discussions Modular View Server

chrisjstevo edited this page Oct 11, 2021 · 12 revisions

What do we mean by making the Vuu server modular?

A modular view server would allow us to add tables, rpc services, rest services, custom view port logic and UI components via a simple programmatic or declarative method.

Why is this good?

It means that when a consumer of the view server wants to use it for their project, they have to have little or no knowledge of the view server internals. They can simply reference a binary via a dependency mechanism (such as maven, gradle or node) and use that in their project, with the knowledge that the base server config is functionality is provided out of the box.

But wait, isn't the view server already modular?

Well, yes, on the server side it is already modular. For example a project could reference the binary of Vuu from a maven repository and then add a new module defied in their project as below:

  val config = VuuServerConfig(
    VuuHttp2ServerOptions()
      .withWebRoot("../vuu-ui/dist/app")
      .withSsl("vuu/src/main/resources/certs/cert.pem",
               "vuu/src/main/resources/certs/key.pem")
      .withDirectoryListings(true)
      .withPort(8443),
    VuuWebSocketOptions()
      .withUri("websocket")
      .withWsPort(8090)
  ).withModule(MyCustomModule()) //added my module here...
   .withModule(MetricsModule())
   .withModule(VuiStateModule(store))

This would create all the server side parts required of the view server with the custom MyCustomModule() supplied. (note: we should make the metrics and VuiStateModules added by default.

So what else do we need to do?

Well at the moment there is no way to bind a new UI component that we create to a module, for example if I create a market depth component and want it added to my EquityTradingModule() I can add the market depth table, but there is no easy way to make the framework aware that I have a new UI component and expose it.

To remedy this, one solution would be to declare UI components (or a UI component descriptor perhaps..) in the module alongside the new tables. For example the syntax could be something like:

object SimulationModule extends DefaultModule {

  final val NAME = "SIMUL"

  def apply()(implicit clock: Clock, lifecycle: LifecycleContainer): ViewServerModule = {
    implicit val randomNumbers = new SeededRandomNumbers(clock.now())

    val ordersModel = new ParentChildOrdersModel()

    ModuleFactory.withNamespace(NAME)
      .addTable(
          TableDef(
            name = "instruments",
            keyField = "ric",
            columns = Columns.fromNames("ric".string(), "description".string(), "bbg".string(), "isin".string(), "currency".string(), "exchange".string(), "lotSize".int()),
            VisualLinks(),
            joinFields = "ric"
          ),
          (table, vs) => new SimulatedBigInstrumentsProvider(table),
          (table, provider) => ViewPortDef(
            columns = table.getTableDef.columns,
            service = new InstrumentsService
          ), 
          components = [ 
              "/vui/nodestuff/components/marketdepth.js"
          ]
      )

This approach would tie the component to a table, or table definition, which is likely not desireable.

Another approach would be something like this:

object SimulationModule extends DefaultModule {

  final val NAME = "SIMUL"

  def apply()(implicit clock: Clock, lifecycle: LifecycleContainer): ViewServerModule = {
    implicit val randomNumbers = new SeededRandomNumbers(clock.now())

    val ordersModel = new ParentChildOrdersModel()

    ModuleFactory.withNamespace(NAME)
           .addUiComponent(Name = "MarketDepth.", Source = "/path/to/component")
           //add tables  & joins later..

This has the benefit of the UI components being separately defined outside the scope of a table but within the scope of the module. Which feels more natural if the ui components can reference more than one table at once.

How would the UI know about the components it needs to load in the browser?

There has to be a discovery mechanism similar to tables or columns. One way to do this would be to expose a rest service with the component definitions from the server, something like: https://localhost/api/components, similar to what we do with Vui state? This would return a collection of urls for the components to be downloaded from with some extra meta (todo: ask Steve what meta he'd want...)

{
   components : [
     { name : "MarketDepth",
       code:  "/api/components/<<module>>/marketdepth.js"  
     }
  ]
}

Some thoughts on the UI

What will a VUU application actually be ? In terms of the UI, right now we have a skeletal runtime shell. The only really fully-fledged piece of this is the layout system that allows a user to create compositions of components which then get persisted to the viewserver. There is some glue code that opens a data connection to the viewserver, that is then shared by components. The only component we have at the moment is the datagrid, each instance of which, when added to a layout, will be bound to a specific viewserver table instance. The list of available viewserver tables is loaded from the viewserver - this becomes our 'component palette'. A table dragged onto a layout from the palette creates a datagrid within the layout.

This is obviously the most basic functionality. The registration mechanism outlined above will give us a way to make custom functionality available through the UI. Initially, the same kind of 'palette' concept might be used to present these options, but we'd want a bit more structure. At the very least, we would want to group them, so if the above MarketDepth component is part of the EquityTrading module, then this should be clearly labelled in the UI, so we'd want a group name /module name in the metadata, or structure the metadata as a tree. I imagine some of these pieces of functionality might be standalone components, others might be collections of related components that would logically be selected together and potentially delivered in a pre-configured layout. We should indicate in the meta whether each 'feature' is a 'component' (lowest level element that can be inserted by user anywhere into a layout) or a layout, in which case it would be opened within a new tab.

The palette concept we have now will seem a bit primitive as apps get larger - at some point we'll want a more sophisticated Navigation system - likely with top-level nav, secondary nav etc, combined with a feature search. The meta will probably need to specify the entries that a particular component would contribute to the Nav. Not 100% sure how this would work - would we predefine a navigation structure and if so, how would this work with users ability to rearrange layouts dynamically ? Or would the navigation structure itself dynamically adapt to layout arrangements ?

The container creates a websocket connection to the viewserver. We want all components added to share this websocket. We need to establish a clean API for this so component authors can very easily subscribe to viewserver data. It shouldn't be too different to what we have now , but it's likely quite datagrid-oriented.

I think a good first step would be to create a few modules, including both simple components and component groups with layout, setup the metadata and we can create some super simple skeleton UI components, which will just connect to the appropriate data and display something simple. This will allow us to prototype the module deployment and loading, data connectivity, layout management, discoverability etc

The DataGrid component we use now is actually a custom component that bundles a datagrid with a filter. This can be the first component module we deploy. Rather than have it bundled as source within the app, we can try deploying it in the same way that we will deploy custom features, like our MarketDepth component. It will be referenced in layouts via its url.

Some more thoughts on the UI (Chris)

Yeah, so to answer your questions:

  1. I think just offering the grid as it is, on raw tables, is really just a demo feature. When using it in a real app, I would declare components in the module which use the grid (like "Orders") which is basically just the grid, configured to open on a specific table. Perhaps with some formatting also... Perhaps this could be declarative in the scala code, like:
    .addUiComponent("Orders", "/src/grid/DataGridWithSearch.js", params = "table=Orders;Format=Blah")

Or maybe we force the creation of an orders react component that contains just the binding and the formatting stuff, we'd have to do that anyway with a new component.

  1. When we declare in the scala code, we are implicitly in a module, like SimulationModule above, so yeah they would be in modules.

  2. Component vs Layout. I assume you mea a layout would be like "Basket Trading Layout which might contain 3 components..? If that is the case I would allow users to create, store and share layouts via the VuiStateService. I think that is easiest/most flexible. What do you reckon?

  3. Yeah, I think creating some kind of simple derivation of a base component in a derived project would be good.